13#define XRD_TRACE m_trace->
25unsigned XrdThrottleManager::GetTimerListHash() {
26 int cpu = sched_getcpu();
30 return cpu % m_timer_list_size;
35unsigned XrdThrottleManager::GetTimerListHash() {
42XrdThrottleManager::TraceID =
"ThrottleManager";
47 m_interval_length_seconds(1.0),
48 m_bytes_per_second(-1),
50 m_concurrency_limit(-1),
51 m_last_round_allocation(100*1024),
54 m_loadshed_frequency(0)
57 std::fill(m_wake_order_0.begin(), m_wake_order_0.end(), -1);
58 std::fill(m_wake_order_1.begin(), m_wake_order_1.end(), -1);
82 if (!loadshed_host.empty() && loadshed_port > 0 && loadshed_freq > 0)
85 SetLoadShed(loadshed_host, loadshed_port, loadshed_freq);
92 TRACE(
DEBUG,
"Initializing the throttle manager.");
94 m_primary_bytes_shares.resize(m_max_users);
95 m_secondary_bytes_shares.resize(m_max_users);
96 m_primary_ops_shares.resize(m_max_users);
97 m_secondary_ops_shares.resize(m_max_users);
98 for (
auto & waiter : m_waiter_info) {
99 waiter.m_manager =
this;
103 for (
int i=0; i<m_max_users; i++)
105 m_primary_bytes_shares[i] = m_last_round_allocation;
106 m_secondary_bytes_shares[i] = 0;
107 m_primary_ops_shares[i] = 10;
108 m_secondary_ops_shares[i] = 0;
113 if ((rc =
XrdSysThread::Run(&tid, XrdThrottleManager::RecomputeBootstrap,
static_cast<void *
>(
this), 0,
"Buffer Manager throttle")))
114 m_log->Emsg(
"ThrottleManager", rc,
"create throttle thread");
118std::tuple<std::string, uint16_t>
122 return std::make_tuple(
"nobody", GetUid(
"nobody"));
129 if (client->
eaAPI && client->
eaAPI->
Get(
"token.subject", user)) {
130 if (client->
vorg) user = std::string(client->
vorg) +
":" + user;
131 }
else if (client->
eaAPI) {
132 std::string request_name;
133 if (client->
eaAPI->
Get(
"request.name", request_name) && !request_name.empty()) user = request_name;
135 if (user.empty()) {user = client->
name ? client->
name :
"nobody";}
136 uint16_t uid = GetUid(user.c_str());
137 return std::make_tuple(user, uid);
145XrdThrottleManager::GetShares(
int &shares,
int &request)
151 request -= (remaining < request) ? remaining : request;
160XrdThrottleManager::StealShares(
int uid,
int &reqsize,
int &reqops)
162 if (!reqsize && !reqops)
return;
163 TRACE(BANDWIDTH,
"Stealing shares to fill request of " << reqsize <<
" bytes");
164 TRACE(IOPS,
"Stealing shares to fill request of " << reqops <<
" ops.");
166 for (
int i=uid+1; i % m_max_users == uid; i++)
168 if (reqsize) GetShares(m_secondary_bytes_shares[i % m_max_users], reqsize);
169 if (reqops) GetShares(m_secondary_ops_shares[ i % m_max_users], reqops);
172 TRACE(BANDWIDTH,
"After stealing shares, " << reqsize <<
" of request bytes remain.");
173 TRACE(IOPS,
"After stealing shares, " << reqops <<
" of request ops remain.");
184 if (m_max_open == 0 && m_max_conns == 0)
return true;
186 const std::lock_guard<std::mutex> lock(m_file_mutex);
187 auto iter = m_file_counters.find(entity);
188 unsigned long cur_open_files = 0, cur_open_conns;
190 if (iter == m_file_counters.end()) {
191 m_file_counters[entity] = 1;
192 TRACE(FILES,
"User " << entity <<
" has opened their first file");
194 }
else if (iter->second < m_max_open) {
196 cur_open_files = iter->second;
198 std::stringstream ss;
199 ss <<
"User " << entity <<
" has hit the limit of " << m_max_open <<
" open files";
200 TRACE(FILES, ss.str());
201 error_message = ss.str();
208 auto conn_iter = m_active_conns.find(entity);
209 auto conn_count_iter = m_conn_counters.find(entity);
210 if ((conn_count_iter != m_conn_counters.end()) && (conn_count_iter->second == m_max_conns) &&
211 (conn_iter == m_active_conns.end() || ((*(conn_iter->second))[pid] == 0)))
214 if (m_max_open) iter->second--;
215 std::stringstream ss;
216 ss <<
"User " << entity <<
" has hit the limit of " << m_max_conns <<
218 TRACE(CONNS, ss.str());
219 error_message = ss.str();
222 if (conn_iter == m_active_conns.end()) {
223 std::unique_ptr<std::unordered_map<pid_t, unsigned long>> conn_map(
224 new std::unordered_map<pid_t, unsigned long>());
225 (*conn_map)[pid] = 1;
226 m_active_conns[entity] = std::move(conn_map);
227 if (conn_count_iter == m_conn_counters.end()) {
228 m_conn_counters[entity] = 1;
231 m_conn_counters[entity] ++;
232 cur_open_conns = m_conn_counters[entity];
235 auto pid_iter = conn_iter->second->find(pid);
236 if (pid_iter == conn_iter->second->end() || pid_iter->second == 0) {
237 (*(conn_iter->second))[pid] = 1;
238 conn_count_iter->second++;
239 cur_open_conns = conn_count_iter->second;
241 (*(conn_iter->second))[pid] ++;
242 cur_open_conns = conn_count_iter->second;
245 TRACE(CONNS,
"User " << entity <<
" has " << cur_open_conns <<
" open connections");
247 if (m_max_open)
TRACE(FILES,
"User " << entity <<
" has " << cur_open_files <<
" open files");
261 if (m_max_open == 0 && m_max_conns == 0)
return true;
264 const std::lock_guard<std::mutex> lock(m_file_mutex);
266 auto iter = m_file_counters.find(entity);
267 if (iter == m_file_counters.end()) {
268 TRACE(FILES,
"WARNING: User " << entity <<
" closed a file but throttle plugin never saw an open file");
270 }
else if (iter->second == 0) {
271 TRACE(FILES,
"WARNING: User " << entity <<
" closed a file but throttle plugin thinks all files were already closed");
276 if (result)
TRACE(FILES,
"User " << entity <<
" closed a file; " << iter->second <<
282 auto conn_iter = m_active_conns.find(entity);
283 auto conn_count_iter = m_conn_counters.find(entity);
284 if (conn_iter == m_active_conns.end() || !(conn_iter->second)) {
285 TRACE(CONNS,
"WARNING: User " << entity <<
" closed a file on a connection we are not"
289 auto pid_iter = conn_iter->second->find(pid);
290 if (pid_iter == conn_iter->second->end()) {
291 TRACE(CONNS,
"WARNING: User " << entity <<
" closed a file on a connection we are not"
295 if (pid_iter->second == 0) {
296 TRACE(CONNS,
"WARNING: User " << entity <<
" closed a file on connection the throttle"
297 " plugin thinks was idle");
301 if (conn_count_iter == m_conn_counters.end()) {
302 TRACE(CONNS,
"WARNING: User " << entity <<
" closed a file but the throttle plugin never"
303 " observed an open file");
304 }
else if (pid_iter->second == 0) {
305 if (conn_count_iter->second == 0) {
306 TRACE(CONNS,
"WARNING: User " << entity <<
" had a connection go idle but the "
307 " throttle plugin already thought all connections were idle");
309 conn_count_iter->second--;
310 TRACE(CONNS,
"User " << entity <<
" had connection on thread " << pid <<
" go idle; "
311 << conn_count_iter->second <<
" active connections remain");
327 if (m_bytes_per_second < 0)
329 if (m_ops_per_second < 0)
331 while (reqsize || reqops)
335 GetShares(m_primary_bytes_shares[uid], reqsize);
338 TRACE(BANDWIDTH,
"Using secondary shares; request has " << reqsize <<
" bytes left.");
339 GetShares(m_secondary_bytes_shares[uid], reqsize);
340 TRACE(BANDWIDTH,
"Finished with secondary shares; request has " << reqsize <<
" bytes left.");
344 TRACE(BANDWIDTH,
"Filled byte shares out of primary; " << m_primary_bytes_shares[uid] <<
" left.");
346 GetShares(m_primary_ops_shares[uid], reqops);
349 GetShares(m_secondary_ops_shares[uid], reqops);
351 StealShares(uid, reqsize, reqops);
354 if (reqsize || reqops)
356 if (reqsize)
TRACE(BANDWIDTH,
"Sleeping to wait for throttle fairshare.");
357 if (reqops)
TRACE(IOPS,
"Sleeping to wait for throttle fairshare.");
358 m_compute_var.Wait();
359 m_loadshed_limit_hit++;
366XrdThrottleManager::UserIOAccounting()
368 std::chrono::steady_clock::duration::rep total_active_time = 0;
369 for (
size_t idx = 0; idx < m_timer_list.size(); idx++) {
370 auto &timerList = m_timer_list[idx];
371 std::unique_lock<std::mutex> lock(timerList.m_mutex);
372 auto timer = timerList.m_first;
374 auto next = timer->m_next;
375 auto uid = timer->m_owner;
376 auto &waiter = m_waiter_info[uid];
377 auto recent_duration = timer->Reset();
378 waiter.m_io_time += recent_duration.count();
380 total_active_time += recent_duration.count();
384 m_io_active_time += total_active_time;
388XrdThrottleManager::ComputeWaiterOrder()
395 auto now = std::chrono::steady_clock::now();
396 auto elapsed = now - m_last_waiter_recompute_time;
397 m_last_waiter_recompute_time = now;
398 std::chrono::duration<double> elapsed_secs = elapsed;
405 auto alpha = 1 - std::exp(-1 * elapsed_secs.count() / 10.0);
407 std::vector<double> share;
408 share.resize(m_max_users);
409 size_t users_with_waiters = 0;
412 for (
int i = 0; i < m_max_users; i++)
414 auto &waiter = m_waiter_info[i];
415 auto io_duration_rep = waiter.m_io_time.exchange(std::chrono::steady_clock::duration(0).count());
416 std::chrono::steady_clock::duration io_duration = std::chrono::steady_clock::duration(io_duration_rep);
417 std::chrono::duration<double> io_duration_secs = io_duration;
418 auto prev_concurrency = io_duration_secs.count() / elapsed_secs.count();
419 float new_concurrency = waiter.m_concurrency;
421 new_concurrency = (1 - alpha) * new_concurrency + alpha * prev_concurrency;
422 waiter.m_concurrency = new_concurrency;
423 if (new_concurrency > 0) {
424 TRACE(
DEBUG,
"User " << i <<
" has concurrency of " << new_concurrency);
428 std::lock_guard<std::mutex> lock(waiter.m_mutex);
429 waiting = waiter.m_waiting;
433 share[i] = new_concurrency;
434 TRACE(
DEBUG,
"User " << i <<
" has concurrency of " << share[i] <<
" and is waiting for " << waiting);
440 users_with_waiters++;
447 auto fair_share =
static_cast<double>(m_concurrency_limit) /
static_cast<double>(users_with_waiters);
448 std::vector<uint16_t> waiter_order;
449 waiter_order.resize(m_max_users);
454 double shares_sum = 0;
455 for (
int idx = 0; idx < m_max_users; idx++)
458 shares_sum += fair_share / share[idx];
466 auto scale_factor = (
static_cast<double>(m_max_users) -
static_cast<double>(users_with_waiters)) / shares_sum;
468 for (
int uid = 0; uid < m_max_users; uid++) {
469 if (share[uid] > 0) {
470 auto shares =
static_cast<unsigned>(scale_factor * fair_share / share[uid]) + 1;
471 TRACE(
DEBUG,
"User " << uid <<
" has " << shares <<
" shares");
472 for (
unsigned idx = 0; idx < shares; idx++)
474 waiter_order[offset % m_max_users] = uid;
479 if (offset < m_max_users) {
480 for (
size_t idx = offset; idx < m_max_users; idx++) {
481 waiter_order[idx] = -1;
485 std::shuffle(waiter_order.begin(), waiter_order.end(), std::default_random_engine());
489 auto &waiter_order_to_modify = (m_wake_order_active == 0) ? m_wake_order_1 : m_wake_order_0;
490 std::copy(waiter_order.begin(), waiter_order.end(), waiter_order_to_modify.begin());
494 m_wake_order_active = (m_wake_order_active + 1) % 2;
501 if (users_with_waiters) {
502 m_waiting_users = users_with_waiters;
503 auto io_active = m_io_active.load(std::memory_order_acquire);
504 for (
size_t idx = io_active; idx < static_cast<size_t>(m_concurrency_limit); idx++) {
511XrdThrottleManager::RecomputeBootstrap(
void *instance)
514 manager->Recompute();
519XrdThrottleManager::Recompute()
526 if (m_max_open || m_max_conns) {
527 const std::lock_guard<std::mutex> lock(m_file_mutex);
528 for (
auto iter = m_active_conns.begin(); iter != m_active_conns.end();)
530 auto & conn_count = *iter;
531 if (!conn_count.second) {
532 iter = m_active_conns.erase(iter);
535 for (
auto iter2 = conn_count.second->begin(); iter2 != conn_count.second->end();) {
536 if (iter2->second == 0) {
537 iter2 = conn_count.second->erase(iter2);
542 if (!conn_count.second->size()) {
543 iter = m_active_conns.erase(iter);
548 for (
auto iter = m_conn_counters.begin(); iter != m_conn_counters.end();) {
550 iter = m_conn_counters.erase(iter);
555 for (
auto iter = m_file_counters.begin(); iter != m_file_counters.end();) {
557 iter = m_file_counters.erase(iter);
564 TRACE(
DEBUG,
"Recomputing fairshares for throttle.");
566 ComputeWaiterOrder();
567 TRACE(
DEBUG,
"Finished recomputing fairshares for throttle; sleeping for " << m_interval_length_seconds <<
" seconds.");
590XrdThrottleManager::RecomputeInternal()
593 float intervals_per_second = 1.0/m_interval_length_seconds;
594 float total_bytes_shares = m_bytes_per_second / intervals_per_second;
595 float total_ops_shares = m_ops_per_second / intervals_per_second;
600 float active_users = 0;
602 for (
int i=0; i<m_max_users; i++)
604 int primary =
AtomicFAZ(m_primary_bytes_shares[i]);
605 if (primary != m_last_round_allocation)
609 m_secondary_bytes_shares[i] = primary;
610 primary =
AtomicFAZ(m_primary_ops_shares[i]);
612 m_secondary_ops_shares[i] = primary;
613 bytes_used += (primary < 0) ? m_last_round_allocation : (m_last_round_allocation-primary);
617 if (active_users == 0)
625 m_last_round_allocation =
static_cast<int>(total_bytes_shares / active_users);
626 int ops_shares =
static_cast<int>(total_ops_shares / active_users);
627 TRACE(BANDWIDTH,
"Round byte allocation " << m_last_round_allocation <<
" ; last round used " << bytes_used <<
".");
628 TRACE(IOPS,
"Round ops allocation " << ops_shares);
629 for (
int i=0; i<m_max_users; i++)
631 m_primary_bytes_shares[i] = m_last_round_allocation;
632 m_primary_ops_shares[i] = ops_shares;
638 int limit_hit = m_loadshed_limit_hit.exchange(0);
639 TRACE(
DEBUG,
"Throttle limit hit " << limit_hit <<
" times during last interval.");
642 m_compute_var.Lock();
643 m_stable_io_active = m_io_active.load(std::memory_order_acquire);
644 auto io_active = m_stable_io_active;
645 m_stable_io_total = m_io_total;
646 auto io_total = m_stable_io_total;
647 auto io_wait_rep = m_io_active_time.exchange(std::chrono::steady_clock::duration(0).count());
648 m_stable_io_wait += std::chrono::steady_clock::duration(io_wait_rep);
650 m_compute_var.UnLock();
652 auto io_wait_ms = std::chrono::duration_cast<std::chrono::milliseconds>(m_stable_io_wait).count();
653 TRACE(IOLOAD,
"Current IO counter is " << io_active <<
"; total IO active time is " << io_wait_ms <<
"ms.");
657 auto len = snprintf(buf, 128,
658 R
"({"event":"throttle_update","io_wait":%.4f,"io_active":%d,"io_total":%llu})",
659 static_cast<double>(io_wait_ms) / 1000.0, io_active,
static_cast<long long unsigned>(io_total));
660 auto suc = (len < 128) ? m_gstream->Insert(buf, len + 1) :
false;
663 TRACE(IOLOAD,
"Failed g-stream insertion of throttle_update record (len=" << len <<
"): " << buf);
666 m_compute_var.Broadcast();
673XrdThrottleManager::GetUid(
const std::string &username)
675 std::hash<std::string> hash_fn;
676 auto hash = hash_fn(username);
677 auto uid =
static_cast<uint16_t
>(hash % m_max_users);
678 TRACE(
DEBUG,
"Mapping user " << username <<
" to UID " << uid);
686XrdThrottleManager::NotifyOne()
688 auto &wake_order = (m_wake_order_active == 0) ? m_wake_order_0 : m_wake_order_1;
690 for (
size_t idx = 0; idx < m_max_users; ++idx)
692 auto offset = m_waiter_offset.fetch_add(1, std::memory_order_acq_rel);
693 int16_t uid = wake_order[offset % m_max_users];
698 auto &waiter_info = m_waiter_info[uid];
699 std::unique_lock<std::mutex> lock(waiter_info.m_mutex);
700 if (waiter_info.m_waiting) {
701 waiter_info.NotifyOne(std::move(lock));
713 int cur_counter = m_io_active.fetch_add(1, std::memory_order_acq_rel);
716 while (m_concurrency_limit >= 0 && cur_counter >= m_concurrency_limit)
721 if (m_waiter_info[uid].m_concurrency < 1)
725 m_loadshed_limit_hit++;
726 m_io_active.fetch_sub(1, std::memory_order_acq_rel);
727 TRACE(
DEBUG,
"ThrottleManager (user=" << uid <<
"): IO concurrency limit hit; waiting for other IOs to finish.");
728 ok = m_waiter_info[uid].Wait();
730 TRACE(
DEBUG,
"ThrottleManager (user=" << uid <<
"): timed out waiting for other IOs to finish.");
733 cur_counter = m_io_active.fetch_add(1, std::memory_order_acq_rel);
746 m_io_active_time += event_duration.count();
747 auto old_active = m_io_active.fetch_sub(1, std::memory_order_acq_rel);
748 m_waiter_info[uid].m_io_time += event_duration.count();
749 if (old_active ==
static_cast<unsigned>(m_concurrency_limit))
754 unsigned waiting_users = m_waiting_users;
755 if (waiting_users == 0) waiting_users = 1;
756 if (m_waiter_info[uid].m_concurrency < m_concurrency_limit / waiting_users)
758 std::unique_lock<std::mutex> lock(m_waiter_info[uid].m_mutex);
759 if (m_waiter_info[uid].m_waiting > 0)
761 m_waiter_info[uid].NotifyOne(std::move(lock));
779 if (m_loadshed_port == 0)
783 if (m_loadshed_limit_hit == 0)
787 if (
static_cast<unsigned>(rand()) % 100 > m_loadshed_frequency)
801 if (m_loadshed_port == 0)
805 if (opaque && opaque[0])
809 if (env.
Get(
"throttle.shed") != 0)
814 lsOpaque +=
"&throttle.shed=1";
818 lsOpaque =
"throttle.shed=1";
825 host = m_loadshed_host;
828 port = m_loadshed_port;
832XrdThrottleManager::Waiter::Wait()
834 auto timeout = std::chrono::steady_clock::now() + m_manager->m_max_wait_time;
836 std::unique_lock<std::mutex> lock(m_mutex);
838 m_cv.wait_until(lock, timeout,
839 [&] {
return m_manager->m_io_active.load(std::memory_order_acquire) <
static_cast<unsigned>(m_manager->m_concurrency_limit) || std::chrono::steady_clock::now() >= timeout; });
842 if (std::chrono::steady_clock::now() > timeout) {
#define AtomicFSub(w, x, y)
char * Get(const char *varname)
XrdSecAttr * Get(const void *sigkey)
char * vorg
Entity's virtual organization(s)
XrdSecEntityAttr * eaAPI
non-const API to attributes
char * name
Entity's name.
static int Run(pthread_t *, void *(*proc)(void *), void *arg, int opts=0, const char *desc=0)
static unsigned long Num(void)
static void Wait(int milliseconds)
void StopIOTimer(std::chrono::steady_clock::duration &event_duration, uint16_t uid)
void SetThrottles(float reqbyterate, float reqoprate, int concurrency, float interval_length)
void SetMaxOpen(unsigned long max_open)
void FromConfig(XrdThrottle::Configuration &config)
void Apply(int reqsize, int reqops, int uid)
std::tuple< std::string, uint16_t > GetUserInfo(const XrdSecEntity *client)
XrdThrottleTimer StartIOTimer(uint16_t uid, bool &ok)
void SetLoadShed(std::string &hostname, unsigned port, unsigned frequency)
friend class XrdThrottleTimer
void PrepLoadShed(const char *opaque, std::string &lsOpaque)
bool CheckLoadShed(const std::string &opaque)
void SetMaxWait(unsigned long max_wait)
void SetMaxConns(unsigned long max_conns)
XrdThrottleManager(XrdSysError *lP, XrdOucTrace *tP)
void PerformLoadShed(const std::string &opaque, std::string &host, unsigned &port)
bool CloseFile(const std::string &entity)
bool OpenFile(const std::string &entity, std::string &open_error_message)
long long GetLoadshedPort() const
long long GetThrottleDataRate() const
long long GetMaxWait() const
long long GetMaxConn() const
long long GetThrottleConcurrency() const
const std::string & GetLoadshedHost() const
long long GetMaxOpen() const
long long GetLoadshedFreq() const
long long GetThrottleIOPSRate() const
long long GetThrottleRecomputeIntervalMS() const
int GetTraceLevels() const