XRootD
XrdThrottleManager.cc
Go to the documentation of this file.
1 
2 #include "XrdThrottleManager.hh"
3 
4 #include "XrdOuc/XrdOucEnv.hh"
5 #include "XrdSec/XrdSecEntity.hh"
8 #include "XrdSys/XrdSysTimer.hh"
12 
13 #define XRD_TRACE m_trace->
15 
16 #include <algorithm>
17 #include <array>
18 #include <cmath>
19 #include <random>
20 #include <sstream>
21 
22 #if defined(__linux__)
23 
24 #include <sched.h>
25 unsigned XrdThrottleManager::GetTimerListHash() {
26  int cpu = sched_getcpu();
27  if (cpu < 0) {
28  return 0;
29  }
30  return cpu % m_timer_list_size;
31 }
32 
33 #else
34 
35 unsigned XrdThrottleManager::GetTimerListHash() {
36  return 0;
37 }
38 
39 #endif
40 
41 const char *
42 XrdThrottleManager::TraceID = "ThrottleManager";
43 
45  m_trace(tP),
46  m_log(lP),
47  m_interval_length_seconds(1.0),
48  m_bytes_per_second(-1),
49  m_ops_per_second(-1),
50  m_concurrency_limit(-1),
51  m_last_round_allocation(100*1024),
52  m_loadshed_host(""),
53  m_loadshed_port(0),
54  m_loadshed_frequency(0)
55 {
56  // Until the first recompute fills them in, no user is in the wake order.
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);
59 }
60 
61 void
63 {
64 
65  auto max_open = config.GetMaxOpen();
66  if (max_open != -1) SetMaxOpen(max_open);
67  auto max_conn = config.GetMaxConn();
68  if (max_conn != -1) SetMaxConns(max_conn);
69  auto max_wait = config.GetMaxWait();
70  if (max_wait != -1) SetMaxWait(max_wait);
71 
73  config.GetThrottleIOPSRate(),
74  config.GetThrottleConcurrency(),
75  static_cast<float>(config.GetThrottleRecomputeIntervalMS())/1000.0);
76 
77  m_trace->What = config.GetTraceLevels();
78 
79  auto loadshed_host = config.GetLoadshedHost();
80  auto loadshed_port = config.GetLoadshedPort();
81  auto loadshed_freq = config.GetLoadshedFreq();
82  if (!loadshed_host.empty() && loadshed_port > 0 && loadshed_freq > 0)
83  {
84  // Loadshed specified, so set it.
85  SetLoadShed(loadshed_host, loadshed_port, loadshed_freq);
86  }
87 }
88 
89 void
91 {
92  TRACE(DEBUG, "Initializing the throttle manager.");
93  // Initialize all our shares to zero.
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;
100  }
101 
102  // Allocate each user 100KB and 10 ops to bootstrap;
103  for (int i=0; i<m_max_users; i++)
104  {
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;
109  }
110 
111  int rc;
112  pthread_t tid;
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");
115 
116 }
117 
118 std::tuple<std::string, uint16_t>
120  // client can be null, if so, return nobody
121  if (!client) {
122  return std::make_tuple("nobody", GetUid("nobody"));
123  }
124 
125  // Try various potential "names" associated with the request, from the most
126  // specific to most generic.
127  std::string user;
128 
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;
134  }
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);
138 }
139 
140 /*
141  * Take as many shares as possible to fulfill the request; update
142  * request with current remaining value, or zero if satisfied.
143  */
144 inline void
145 XrdThrottleManager::GetShares(int &shares, int &request)
146 {
147  int remaining;
148  AtomicFSub(remaining, shares, request);
149  if (remaining > 0)
150  {
151  request -= (remaining < request) ? remaining : request;
152  }
153 }
154 
155 /*
156  * Iterate through all of the secondary shares, attempting
157  * to steal enough to fulfill the request.
158  */
159 void
160 XrdThrottleManager::StealShares(int uid, int &reqsize, int &reqops)
161 {
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.");
165 
166  for (int i=uid+1; i % m_max_users == uid; i++)
167  {
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);
170  }
171 
172  TRACE(BANDWIDTH, "After stealing shares, " << reqsize << " of request bytes remain.");
173  TRACE(IOPS, "After stealing shares, " << reqops << " of request ops remain.");
174 }
175 
176 /*
177  * Increment the number of files held open by a given entity. Returns false
178  * if the user is at the maximum; in this case, the internal counter is not
179  * incremented.
180  */
181 bool
182 XrdThrottleManager::OpenFile(const std::string &entity, std::string &error_message)
183 {
184  if (m_max_open == 0 && m_max_conns == 0) return true;
185 
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;
189  if (m_max_open) {
190  if (iter == m_file_counters.end()) {
191  m_file_counters[entity] = 1;
192  TRACE(FILES, "User " << entity << " has opened their first file");
193  cur_open_files = 1;
194  } else if (iter->second < m_max_open) {
195  iter->second++;
196  cur_open_files = iter->second;
197  } else {
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();
202  return false;
203  }
204  }
205 
206  if (m_max_conns) {
207  auto pid = XrdSysThread::Num();
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)))
212  {
213  // note: we are rolling back the increment in open files
214  if (m_max_open) iter->second--;
215  std::stringstream ss;
216  ss << "User " << entity << " has hit the limit of " << m_max_conns <<
217  " open connections";
218  TRACE(CONNS, ss.str());
219  error_message = ss.str();
220  return false;
221  }
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;
229  cur_open_conns = 1;
230  } else {
231  m_conn_counters[entity] ++;
232  cur_open_conns = m_conn_counters[entity];
233  }
234  } else {
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;
240  } else {
241  (*(conn_iter->second))[pid] ++;
242  cur_open_conns = conn_count_iter->second;
243  }
244  }
245  TRACE(CONNS, "User " << entity << " has " << cur_open_conns << " open connections");
246  }
247  if (m_max_open) TRACE(FILES, "User " << entity << " has " << cur_open_files << " open files");
248  return true;
249 }
250 
251 
252 /*
253  * Decrement the number of files held open by a given entity.
254  *
255  * Returns false if the value would have fallen below zero or
256  * if the entity isn't tracked.
257  */
258 bool
259 XrdThrottleManager::CloseFile(const std::string &entity)
260 {
261  if (m_max_open == 0 && m_max_conns == 0) return true;
262 
263  bool result = true;
264  const std::lock_guard<std::mutex> lock(m_file_mutex);
265  if (m_max_open) {
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");
269  result = false;
270  } else if (iter->second == 0) {
271  TRACE(FILES, "WARNING: User " << entity << " closed a file but throttle plugin thinks all files were already closed");
272  result = false;
273  } else {
274  iter->second--;
275  }
276  if (result) TRACE(FILES, "User " << entity << " closed a file; " << iter->second <<
277  " remain open");
278  }
279 
280  if (m_max_conns) {
281  auto pid = XrdSysThread::Num();
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"
286  " tracking");
287  return false;
288  }
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"
292  " tracking");
293  return false;
294  }
295  if (pid_iter->second == 0) {
296  TRACE(CONNS, "WARNING: User " << entity << " closed a file on connection the throttle"
297  " plugin thinks was idle");
298  } else {
299  pid_iter->second--;
300  }
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");
308  } else {
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");
312  }
313  }
314  }
315 
316  return result;
317 }
318 
319 
320 /*
321  * Apply the throttle. If there are no limits set, returns immediately. Otherwise,
322  * this applies the limits as best possible, stalling the thread if necessary.
323  */
324 void
325 XrdThrottleManager::Apply(int reqsize, int reqops, int uid)
326 {
327  if (m_bytes_per_second < 0)
328  reqsize = 0;
329  if (m_ops_per_second < 0)
330  reqops = 0;
331  while (reqsize || reqops)
332  {
333  // Subtract the requested out of the shares
334  AtomicBeg(m_compute_var);
335  GetShares(m_primary_bytes_shares[uid], reqsize);
336  if (reqsize)
337  {
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.");
341  }
342  else
343  {
344  TRACE(BANDWIDTH, "Filled byte shares out of primary; " << m_primary_bytes_shares[uid] << " left.");
345  }
346  GetShares(m_primary_ops_shares[uid], reqops);
347  if (reqops)
348  {
349  GetShares(m_secondary_ops_shares[uid], reqops);
350  }
351  StealShares(uid, reqsize, reqops);
352  AtomicEnd(m_compute_var);
353 
354  if (reqsize || reqops)
355  {
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++;
360  }
361  }
362 
363 }
364 
365 void
366 XrdThrottleManager::UserIOAccounting()
367 {
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;
373  while (timer) {
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();
379 
380  total_active_time += recent_duration.count();
381  timer = next;
382  }
383  }
384  m_io_active_time += total_active_time;
385 }
386 
387 void
388 XrdThrottleManager::ComputeWaiterOrder()
389 {
390  // Update the IO time for long-running I/O operations. This prevents,
391  // for example, a 2-minute I/O operation from causing a spike in
392  // concurrency because it's otherwise only reported at the end.
393  UserIOAccounting();
394 
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;
399  // Alpha is the decay factor for the exponential moving average. One window is 10 seconds,
400  // so every 10 seconds we decay the prior average by 1/e (that is, the weight is 64% of the
401  // total). This means the contribution of I/O load from a minute ago is 0.2% of the total.
402 
403  // The moving average will be used to determine how close the user is to their "fair share"
404  // of the concurrency limit among the users that are waiting.
405  auto alpha = 1 - std::exp(-1 * elapsed_secs.count() / 10.0);
406 
407  std::vector<double> share;
408  share.resize(m_max_users);
409  size_t users_with_waiters = 0;
410  // For each user, compute their current concurrency and determine how many waiting users
411  // total there are.
412  for (int i = 0; i < m_max_users; i++)
413  {
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;
420 
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);
425  }
426  unsigned waiting;
427  {
428  std::lock_guard<std::mutex> lock(waiter.m_mutex);
429  waiting = waiter.m_waiting;
430  }
431  if (waiting > 0)
432  {
433  share[i] = new_concurrency;
434  TRACE(DEBUG, "User " << i << " has concurrency of " << share[i] << " and is waiting for " << waiting);
435  // Handle the division-by-zero case; if we have no history of usage whatsoever, we should pretend we
436  // have at least some minimal load
437  if (share[i] == 0) {
438  share[i] = 0.1;
439  }
440  users_with_waiters++;
441  }
442  else
443  {
444  share[i] = 0;
445  }
446  }
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);
450 
451  // Calculate the share for each user. We assume the user should get a share proportional to how
452  // far above or below the fair share they are. So, a user with concurrency of 20 when the fairshare
453  // is 10 will get 0.5 shares; a user with concurrency of 5 when the fairshare is 10 will get 2.0 shares.
454  double shares_sum = 0;
455  for (int idx = 0; idx < m_max_users; idx++)
456  {
457  if (share[idx]) {
458  shares_sum += fair_share / share[idx];
459  }
460  }
461 
462  // We must quantize the overall shares into an array of 1024 elements. We do this by
463  // scaling up (or down) based on the total number of shares computed above. Note this
464  // quantization can lead to an over-provisioned user being assigned zero shares; thus,
465  // we scale based on (1024-#users) so we can give one extra share to each user.
466  auto scale_factor = (static_cast<double>(m_max_users) - static_cast<double>(users_with_waiters)) / shares_sum;
467  size_t offset = 0;
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++)
473  {
474  waiter_order[offset % m_max_users] = uid;
475  offset++;
476  }
477  }
478  }
479  if (offset < m_max_users) {
480  for (size_t idx = offset; idx < m_max_users; idx++) {
481  waiter_order[idx] = -1;
482  }
483  }
484  // Shuffle the order to randomize the wakeup order.
485  std::shuffle(waiter_order.begin(), waiter_order.end(), std::default_random_engine());
486 
487  // Copy the order to the inactive array. We do not shuffle in-place because RAtomics are
488  // not move constructible, which is a requirement for std::shuffle.
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());
491 
492  // Set the array we just modified to be the active one. Since this is a relaxed write, it could take
493  // some time for other CPUs to see the change; that's OK as this is all stochastic anyway.
494  m_wake_order_active = (m_wake_order_active + 1) % 2;
495 
496  m_waiter_offset = 0;
497 
498  // If we find ourselves below the concurrency limit because we woke up too few operations in the last
499  // interval, try waking up enough operations to fill the gap. If we race with new incoming operations,
500  // the threads will just go back to sleep.
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++) {
505  NotifyOne();
506  }
507  }
508 }
509 
510 void *
511 XrdThrottleManager::RecomputeBootstrap(void *instance)
512 {
513  XrdThrottleManager * manager = static_cast<XrdThrottleManager*>(instance);
514  manager->Recompute();
515  return NULL;
516 }
517 
518 void
519 XrdThrottleManager::Recompute()
520 {
521  while (1)
522  {
523  // The connection counter can accumulate a number of known-idle connections.
524  // We only need to keep long-term memory of idle ones. Take this chance to garbage
525  // collect old connection counters.
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();)
529  {
530  auto & conn_count = *iter;
531  if (!conn_count.second) {
532  iter = m_active_conns.erase(iter);
533  continue;
534  }
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);
538  } else {
539  iter2++;
540  }
541  }
542  if (!conn_count.second->size()) {
543  iter = m_active_conns.erase(iter);
544  } else {
545  iter++;
546  }
547  }
548  for (auto iter = m_conn_counters.begin(); iter != m_conn_counters.end();) {
549  if (!iter->second) {
550  iter = m_conn_counters.erase(iter);
551  } else {
552  iter++;
553  }
554  }
555  for (auto iter = m_file_counters.begin(); iter != m_file_counters.end();) {
556  if (!iter->second) {
557  iter = m_file_counters.erase(iter);
558  } else {
559  iter++;
560  }
561  }
562  }
563 
564  TRACE(DEBUG, "Recomputing fairshares for throttle.");
565  RecomputeInternal();
566  ComputeWaiterOrder();
567  TRACE(DEBUG, "Finished recomputing fairshares for throttle; sleeping for " << m_interval_length_seconds << " seconds.");
568  XrdSysTimer::Wait(static_cast<int>(1000*m_interval_length_seconds));
569  }
570 }
571 
572 /*
573  * The heart of the manager approach.
574  *
575  * This routine periodically recomputes the shares of each current user.
576  * Each user has a "primary" and a "secondary" share. At the end of the
577  * each time interval, the remaining primary share is moved to secondary.
578  * A user can utilize both shares; if both are gone, they must block until
579  * the next recompute interval.
580  *
581  * The secondary share can be "stolen" by any other user; so, if a user
582  * is idle or under-utilizing, their share can be used by someone else.
583  * However, they can never be completely starved, as no one can steal
584  * primary share.
585  *
586  * In this way, we violate the throttle for an interval, but never starve.
587  *
588  */
589 void
590 XrdThrottleManager::RecomputeInternal()
591 {
592  // Compute total shares for this interval;
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;
596 
597  // Compute the number of active users; a user is active if they used
598  // any primary share during the last interval;
599  AtomicBeg(m_compute_var);
600  float active_users = 0;
601  long bytes_used = 0;
602  for (int i=0; i<m_max_users; i++)
603  {
604  int primary = AtomicFAZ(m_primary_bytes_shares[i]);
605  if (primary != m_last_round_allocation)
606  {
607  active_users++;
608  if (primary >= 0)
609  m_secondary_bytes_shares[i] = primary;
610  primary = AtomicFAZ(m_primary_ops_shares[i]);
611  if (primary >= 0)
612  m_secondary_ops_shares[i] = primary;
613  bytes_used += (primary < 0) ? m_last_round_allocation : (m_last_round_allocation-primary);
614  }
615  }
616 
617  if (active_users == 0)
618  {
619  active_users++;
620  }
621 
622  // Note we allocate the same number of shares to *all* users, not
623  // just the active ones. If a new user becomes active in the next
624  // interval, we'll go over our bandwidth budget just a bit.
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++)
630  {
631  m_primary_bytes_shares[i] = m_last_round_allocation;
632  m_primary_ops_shares[i] = ops_shares;
633  }
634 
635  AtomicEnd(m_compute_var);
636 
637  // Reset the loadshed limit counter.
638  int limit_hit = m_loadshed_limit_hit.exchange(0);
639  TRACE(DEBUG, "Throttle limit hit " << limit_hit << " times during last interval.");
640 
641  // Update the IO counters
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);
649 
650  m_compute_var.UnLock();
651 
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.");
654  if (m_gstream)
655  {
656  char buf[128];
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;
661  if (!suc)
662  {
663  TRACE(IOLOAD, "Failed g-stream insertion of throttle_update record (len=" << len << "): " << buf);
664  }
665  }
666  m_compute_var.Broadcast();
667 }
668 
669 /*
670  * Do a simple hash across the username.
671  */
672 uint16_t
673 XrdThrottleManager::GetUid(const std::string &username)
674 {
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);
679  return uid;
680 }
681 
682 /*
683  * Notify a single waiter thread that it can proceed.
684  */
685 void
686 XrdThrottleManager::NotifyOne()
687 {
688  auto &wake_order = (m_wake_order_active == 0) ? m_wake_order_0 : m_wake_order_1;
689 
690  for (size_t idx = 0; idx < m_max_users; ++idx)
691  {
692  auto offset = m_waiter_offset.fetch_add(1, std::memory_order_acq_rel);
693  int16_t uid = wake_order[offset % m_max_users];
694  if (uid < 0)
695  {
696  continue;
697  }
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));
702  return;
703  }
704  }
705 }
706 
707 /*
708  * Create an IO timer object; increment the number of outstanding IOs.
709  */
711 XrdThrottleManager::StartIOTimer(uint16_t uid, bool &ok)
712 {
713  int cur_counter = m_io_active.fetch_add(1, std::memory_order_acq_rel);
714  m_io_total++;
715 
716  while (m_concurrency_limit >= 0 && cur_counter >= m_concurrency_limit)
717  {
718  // If the user has essentially no concurrency, then we let them
719  // temporarily exceed the limit. This prevents potential waits for
720  // every single read for an infrequent user.
721  if (m_waiter_info[uid].m_concurrency < 1)
722  {
723  break;
724  }
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();
729  if (!ok) {
730  TRACE(DEBUG, "ThrottleManager (user=" << uid << "): timed out waiting for other IOs to finish.");
731  return XrdThrottleTimer();
732  }
733  cur_counter = m_io_active.fetch_add(1, std::memory_order_acq_rel);
734  }
735 
736  ok = true;
737  return XrdThrottleTimer(this, uid);
738 }
739 
740 /*
741  * Finish recording an IO timer.
742  */
743 void
744 XrdThrottleManager::StopIOTimer(std::chrono::steady_clock::duration & event_duration, uint16_t uid)
745 {
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))
750  {
751  // If we are below the concurrency limit threshold and have another waiter
752  // for our user, then execute it immediately. Otherwise, we will give
753  // someone else a chance to run (as we have gotten more than our share recently).
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)
757  {
758  std::unique_lock<std::mutex> lock(m_waiter_info[uid].m_mutex);
759  if (m_waiter_info[uid].m_waiting > 0)
760  {
761  m_waiter_info[uid].NotifyOne(std::move(lock));
762  return;
763  }
764  }
765  NotifyOne();
766  }
767 }
768 
769 /*
770  * Check the counters to see if we have hit any throttle limits in the
771  * current time period. If so, shed the client randomly.
772  *
773  * If the client has already been load-shedded once and reconnected to this
774  * server, then do not load-shed it again.
775  */
776 bool
777 XrdThrottleManager::CheckLoadShed(const std::string &opaque)
778 {
779  if (m_loadshed_port == 0)
780  {
781  return false;
782  }
783  if (m_loadshed_limit_hit == 0)
784  {
785  return false;
786  }
787  if (static_cast<unsigned>(rand()) % 100 > m_loadshed_frequency)
788  {
789  return false;
790  }
791  if (opaque.empty())
792  {
793  return false;
794  }
795  return true;
796 }
797 
798 void
799 XrdThrottleManager::PrepLoadShed(const char * opaque, std::string &lsOpaque)
800 {
801  if (m_loadshed_port == 0)
802  {
803  return;
804  }
805  if (opaque && opaque[0])
806  {
807  XrdOucEnv env(opaque);
808  // Do not load shed client if it has already been done once.
809  if (env.Get("throttle.shed") != 0)
810  {
811  return;
812  }
813  lsOpaque = opaque;
814  lsOpaque += "&throttle.shed=1";
815  }
816  else
817  {
818  lsOpaque = "throttle.shed=1";
819  }
820 }
821 
822 void
823 XrdThrottleManager::PerformLoadShed(const std::string &opaque, std::string &host, unsigned &port)
824 {
825  host = m_loadshed_host;
826  host += "?";
827  host += opaque;
828  port = m_loadshed_port;
829 }
830 
831 bool
832 XrdThrottleManager::Waiter::Wait()
833 {
834  auto timeout = std::chrono::steady_clock::now() + m_manager->m_max_wait_time;
835  {
836  std::unique_lock<std::mutex> lock(m_mutex);
837  m_waiting++;
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; });
840  m_waiting--;
841  }
842  if (std::chrono::steady_clock::now() > timeout) {
843  return false;
844  }
845  return true;
846 }
#define DEBUG(x)
Definition: XrdBwmTrace.hh:54
#define AtomicFSub(w, x, y)
#define AtomicFAZ(x)
#define AtomicBeg(Mtx)
#define AtomicEnd(Mtx)
#define TRACE(act, x)
Definition: XrdTrace.hh:63
char * Get(const char *varname)
Definition: XrdOucEnv.hh:69
XrdSecAttr * Get(const void *sigkey)
char * vorg
Entity's virtual organization(s)
Definition: XrdSecEntity.hh:71
XrdSecEntityAttr * eaAPI
non-const API to attributes
Definition: XrdSecEntity.hh:92
char * name
Entity's name.
Definition: XrdSecEntity.hh:69
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
Definition: XrdSysError.cc:95
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)
Definition: XrdSysTimer.cc:227
T exchange(T v, std::memory_order mo=std::memory_order_relaxed) noexcept
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 GetThrottleConcurrency() const
const std::string & GetLoadshedHost() const
long long GetLoadshedFreq() const
long long GetThrottleIOPSRate() const
long long GetThrottleRecomputeIntervalMS() const
bool Insert(const char *data, int dlen)