13#define XRD_TRACE m_trace->
27unsigned XrdThrottleManager::GetTimerListHash() {
28 int cpu = sched_getcpu();
32 return cpu % m_timer_list_size;
37unsigned XrdThrottleManager::GetTimerListHash() {
44XrdThrottleManager::TraceID =
"ThrottleManager";
49 m_interval_length_seconds(1.0),
50 m_bytes_per_second(-1),
52 m_concurrency_limit(-1),
53 m_last_round_allocation(100*1024),
56 m_loadshed_frequency(0)
81 if (!loadshed_host.empty() && loadshed_port > 0 && loadshed_freq > 0)
84 SetLoadShed(loadshed_host, loadshed_port, loadshed_freq);
89 if (!user_config_file.empty())
91 m_user_config_file = user_config_file;
94 m_log->Emsg(
"ThrottleManager",
"Failed to load per-user configuration file", user_config_file.c_str());
102 TRACE(
DEBUG,
"Initializing the throttle manager.");
104 m_primary_bytes_shares.resize(m_max_users);
105 m_secondary_bytes_shares.resize(m_max_users);
106 m_primary_ops_shares.resize(m_max_users);
107 m_secondary_ops_shares.resize(m_max_users);
108 for (
auto & waiter : m_waiter_info) {
109 waiter.m_manager =
this;
113 for (
int i=0; i<m_max_users; i++)
115 m_primary_bytes_shares[i] = m_last_round_allocation;
116 m_secondary_bytes_shares[i] = 0;
117 m_primary_ops_shares[i] = 10;
118 m_secondary_ops_shares[i] = 0;
123 if ((rc =
XrdSysThread::Run(&tid, XrdThrottleManager::RecomputeBootstrap,
static_cast<void *
>(
this), 0,
"Buffer Manager throttle")))
124 m_log->Emsg(
"ThrottleManager", rc,
"create throttle thread");
128std::tuple<std::string, uint16_t>
132 return std::make_tuple(
"nobody", GetUid(
"nobody"));
139 if (client->
eaAPI && client->
eaAPI->
Get(
"token.subject", user)) {
140 if (client->
vorg) user = std::string(client->
vorg) +
":" + user;
141 }
else if (client->
eaAPI) {
142 std::string request_name;
143 if (client->
eaAPI->
Get(
"request.name", request_name) && !request_name.empty()) user = request_name;
145 if (user.empty()) {user = client->
name ? client->
name :
"nobody";}
146 uint16_t uid = GetUid(user.c_str());
147 return std::make_tuple(user, uid);
155XrdThrottleManager::GetShares(
int &shares,
int &request)
161 request -= (remaining < request) ? remaining : request;
170XrdThrottleManager::StealShares(
int uid,
int &reqsize,
int &reqops)
172 if (!reqsize && !reqops)
return;
173 TRACE(BANDWIDTH,
"Stealing shares to fill request of " << reqsize <<
" bytes");
174 TRACE(IOPS,
"Stealing shares to fill request of " << reqops <<
" ops.");
176 for (
int i=uid+1; i % m_max_users == uid; i++)
178 if (reqsize) GetShares(m_secondary_bytes_shares[i % m_max_users], reqsize);
179 if (reqops) GetShares(m_secondary_ops_shares[ i % m_max_users], reqops);
182 TRACE(BANDWIDTH,
"After stealing shares, " << reqsize <<
" of request bytes remain.");
183 TRACE(IOPS,
"After stealing shares, " << reqops <<
" of request ops remain.");
196 unsigned long effective_max_conn = user_max_conn < m_max_conns ? user_max_conn : m_max_conns;
198 if (m_max_open == 0 && effective_max_conn == 0)
return true;
200 const std::lock_guard<std::mutex> lock(m_file_mutex);
201 auto iter = m_file_counters.find(entity);
202 unsigned long cur_open_files = 0, cur_open_conns;
204 if (iter == m_file_counters.end()) {
205 m_file_counters[entity] = 1;
206 TRACE(FILES,
"User " << entity <<
" has opened their first file");
208 }
else if (iter->second < m_max_open) {
210 cur_open_files = iter->second;
212 std::stringstream ss;
213 ss <<
"User " << entity <<
" has hit the limit of " << m_max_open <<
" open files";
214 TRACE(FILES, ss.str());
215 error_message = ss.str();
220 if (effective_max_conn > 0) {
222 auto conn_iter = m_active_conns.find(entity);
223 auto conn_count_iter = m_conn_counters.find(entity);
224 if ((conn_count_iter != m_conn_counters.end()) && (conn_count_iter->second == effective_max_conn) &&
225 (conn_iter == m_active_conns.end() || ((*(conn_iter->second))[pid] == 0)))
228 if (m_max_open) iter->second--;
229 std::stringstream ss;
230 ss <<
"User " << entity <<
" has hit the limit of " << effective_max_conn <<
232 if (user_max_conn > 0) {
233 ss <<
" (per-user limit)";
235 TRACE(CONNS, ss.str());
236 error_message = ss.str();
239 if (conn_iter == m_active_conns.end()) {
240 std::unique_ptr<std::unordered_map<pid_t, unsigned long>> conn_map(
241 new std::unordered_map<pid_t, unsigned long>());
242 (*conn_map)[pid] = 1;
243 m_active_conns[entity] = std::move(conn_map);
244 if (conn_count_iter == m_conn_counters.end()) {
245 m_conn_counters[entity] = 1;
248 m_conn_counters[entity] ++;
249 cur_open_conns = m_conn_counters[entity];
252 auto pid_iter = conn_iter->second->find(pid);
253 if (pid_iter == conn_iter->second->end() || pid_iter->second == 0) {
254 (*(conn_iter->second))[pid] = 1;
255 conn_count_iter->second++;
256 cur_open_conns = conn_count_iter->second;
258 (*(conn_iter->second))[pid] ++;
259 cur_open_conns = conn_count_iter->second;
262 TRACE(CONNS,
"User " << entity <<
" has " << cur_open_conns <<
" open connections (limit: " << effective_max_conn <<
")");
264 if (m_max_open)
TRACE(FILES,
"User " << entity <<
" has " << cur_open_files <<
" open files");
278 if (m_max_open == 0 && m_max_conns == 0)
return true;
281 const std::lock_guard<std::mutex> lock(m_file_mutex);
283 auto iter = m_file_counters.find(entity);
284 if (iter == m_file_counters.end()) {
285 TRACE(FILES,
"WARNING: User " << entity <<
" closed a file but throttle plugin never saw an open file");
287 }
else if (iter->second == 0) {
288 TRACE(FILES,
"WARNING: User " << entity <<
" closed a file but throttle plugin thinks all files were already closed");
293 if (result)
TRACE(FILES,
"User " << entity <<
" closed a file; " << iter->second <<
299 auto conn_iter = m_active_conns.find(entity);
300 auto conn_count_iter = m_conn_counters.find(entity);
301 if (conn_iter == m_active_conns.end() || !(conn_iter->second)) {
302 TRACE(CONNS,
"WARNING: User " << entity <<
" closed a file on a connection we are not"
306 auto pid_iter = conn_iter->second->find(pid);
307 if (pid_iter == conn_iter->second->end()) {
308 TRACE(CONNS,
"WARNING: User " << entity <<
" closed a file on a connection we are not"
312 if (pid_iter->second == 0) {
313 TRACE(CONNS,
"WARNING: User " << entity <<
" closed a file on connection the throttle"
314 " plugin thinks was idle");
318 if (conn_count_iter == m_conn_counters.end()) {
319 TRACE(CONNS,
"WARNING: User " << entity <<
" closed a file but the throttle plugin never"
320 " observed an open file");
321 }
else if (pid_iter->second == 0) {
322 if (conn_count_iter->second == 0) {
323 TRACE(CONNS,
"WARNING: User " << entity <<
" had a connection go idle but the "
324 " throttle plugin already thought all connections were idle");
326 conn_count_iter->second--;
327 TRACE(CONNS,
"User " << entity <<
" had connection on thread " << pid <<
" go idle; "
328 << conn_count_iter->second <<
" active connections remain");
344 if (m_bytes_per_second < 0)
346 if (m_ops_per_second < 0)
348 while (reqsize || reqops)
352 GetShares(m_primary_bytes_shares[uid], reqsize);
355 TRACE(BANDWIDTH,
"Using secondary shares; request has " << reqsize <<
" bytes left.");
356 GetShares(m_secondary_bytes_shares[uid], reqsize);
357 TRACE(BANDWIDTH,
"Finished with secondary shares; request has " << reqsize <<
" bytes left.");
361 TRACE(BANDWIDTH,
"Filled byte shares out of primary; " << m_primary_bytes_shares[uid] <<
" left.");
363 GetShares(m_primary_ops_shares[uid], reqops);
366 GetShares(m_secondary_ops_shares[uid], reqops);
368 StealShares(uid, reqsize, reqops);
371 if (reqsize || reqops)
373 if (reqsize)
TRACE(BANDWIDTH,
"Sleeping to wait for throttle fairshare.");
374 if (reqops)
TRACE(IOPS,
"Sleeping to wait for throttle fairshare.");
375 m_compute_var.Wait();
376 m_loadshed_limit_hit++;
383XrdThrottleManager::UserIOAccounting()
385 std::chrono::steady_clock::duration::rep total_active_time = 0;
386 for (
size_t idx = 0; idx < m_timer_list.size(); idx++) {
387 auto &timerList = m_timer_list[idx];
388 std::unique_lock<std::mutex> lock(timerList.m_mutex);
389 auto timer = timerList.m_first;
391 auto next = timer->m_next;
392 auto uid = timer->m_owner;
393 auto &waiter = m_waiter_info[uid];
394 auto recent_duration = timer->Reset();
395 waiter.m_io_time += recent_duration.count();
397 total_active_time += recent_duration.count();
401 m_io_active_time += total_active_time;
405XrdThrottleManager::ComputeWaiterOrder()
412 auto now = std::chrono::steady_clock::now();
413 auto elapsed = now - m_last_waiter_recompute_time;
414 m_last_waiter_recompute_time = now;
415 std::chrono::duration<double> elapsed_secs = elapsed;
422 auto alpha = 1 - std::exp(-1 * elapsed_secs.count() / 10.0);
424 std::vector<double> share;
425 share.resize(m_max_users);
426 size_t users_with_waiters = 0;
429 for (
int i = 0; i < m_max_users; i++)
431 auto &waiter = m_waiter_info[i];
432 auto io_duration_rep = waiter.m_io_time.exchange(std::chrono::steady_clock::duration(0).count());
433 std::chrono::steady_clock::duration io_duration = std::chrono::steady_clock::duration(io_duration_rep);
434 std::chrono::duration<double> io_duration_secs = io_duration;
435 auto prev_concurrency = io_duration_secs.count() / elapsed_secs.count();
436 float new_concurrency = waiter.m_concurrency;
438 new_concurrency = (1 - alpha) * new_concurrency + alpha * prev_concurrency;
439 waiter.m_concurrency = new_concurrency;
440 if (new_concurrency > 0) {
441 TRACE(
DEBUG,
"User " << i <<
" has concurrency of " << new_concurrency);
445 std::lock_guard<std::mutex> lock(waiter.m_mutex);
446 waiting = waiter.m_waiting;
450 share[i] = new_concurrency;
451 TRACE(
DEBUG,
"User " << i <<
" has concurrency of " << share[i] <<
" and is waiting for " << waiting);
457 users_with_waiters++;
464 auto fair_share =
static_cast<double>(m_concurrency_limit) /
static_cast<double>(users_with_waiters);
465 std::vector<uint16_t> waiter_order;
466 waiter_order.resize(m_max_users);
471 double shares_sum = 0;
472 for (
int idx = 0; idx < m_max_users; idx++)
475 shares_sum += fair_share / share[idx];
483 auto scale_factor = (
static_cast<double>(m_max_users) -
static_cast<double>(users_with_waiters)) / shares_sum;
485 for (
int uid = 0; uid < m_max_users; uid++) {
486 if (share[uid] > 0) {
487 auto shares =
static_cast<unsigned>(scale_factor * fair_share / share[uid]) + 1;
488 TRACE(
DEBUG,
"User " << uid <<
" has " << shares <<
" shares");
489 for (
unsigned idx = 0; idx < shares; idx++)
491 waiter_order[offset % m_max_users] = uid;
496 if (offset < m_max_users) {
497 for (
size_t idx = offset; idx < m_max_users; idx++) {
498 waiter_order[idx] = -1;
502 std::shuffle(waiter_order.begin(), waiter_order.end(), std::default_random_engine());
506 auto &waiter_order_to_modify = (m_wake_order_active == 0) ? m_wake_order_1 : m_wake_order_0;
507 std::copy(waiter_order.begin(), waiter_order.end(), waiter_order_to_modify.begin());
511 m_wake_order_active = (m_wake_order_active + 1) % 2;
518 if (users_with_waiters) {
519 m_waiting_users = users_with_waiters;
520 auto io_active = m_io_active.load(std::memory_order_acquire);
521 for (
size_t idx = io_active; idx < static_cast<size_t>(m_concurrency_limit); idx++) {
528XrdThrottleManager::RecomputeBootstrap(
void *instance)
531 manager->Recompute();
536XrdThrottleManager::Recompute()
543 if (m_max_open || m_max_conns) {
544 const std::lock_guard<std::mutex> lock(m_file_mutex);
545 for (
auto iter = m_active_conns.begin(); iter != m_active_conns.end();)
547 auto & conn_count = *iter;
548 if (!conn_count.second) {
549 iter = m_active_conns.erase(iter);
552 for (
auto iter2 = conn_count.second->begin(); iter2 != conn_count.second->end();) {
553 if (iter2->second == 0) {
554 iter2 = conn_count.second->erase(iter2);
559 if (!conn_count.second->size()) {
560 iter = m_active_conns.erase(iter);
565 for (
auto iter = m_conn_counters.begin(); iter != m_conn_counters.end();) {
567 iter = m_conn_counters.erase(iter);
572 for (
auto iter = m_file_counters.begin(); iter != m_file_counters.end();) {
574 iter = m_file_counters.erase(iter);
581 TRACE(
DEBUG,
"Recomputing fairshares for throttle.");
583 ComputeWaiterOrder();
584 TRACE(
DEBUG,
"Finished recomputing fairshares for throttle; sleeping for " << m_interval_length_seconds <<
" seconds.");
607XrdThrottleManager::RecomputeInternal()
610 float intervals_per_second = 1.0/m_interval_length_seconds;
611 float total_bytes_shares = m_bytes_per_second / intervals_per_second;
612 float total_ops_shares = m_ops_per_second / intervals_per_second;
617 float active_users = 0;
619 for (
int i=0; i<m_max_users; i++)
621 int primary =
AtomicFAZ(m_primary_bytes_shares[i]);
622 if (primary != m_last_round_allocation)
626 m_secondary_bytes_shares[i] = primary;
627 primary =
AtomicFAZ(m_primary_ops_shares[i]);
629 m_secondary_ops_shares[i] = primary;
630 bytes_used += (primary < 0) ? m_last_round_allocation : (m_last_round_allocation-primary);
634 if (active_users == 0)
642 m_last_round_allocation =
static_cast<int>(total_bytes_shares / active_users);
643 int ops_shares =
static_cast<int>(total_ops_shares / active_users);
644 TRACE(BANDWIDTH,
"Round byte allocation " << m_last_round_allocation <<
" ; last round used " << bytes_used <<
".");
645 TRACE(IOPS,
"Round ops allocation " << ops_shares);
646 for (
int i=0; i<m_max_users; i++)
648 m_primary_bytes_shares[i] = m_last_round_allocation;
649 m_primary_ops_shares[i] = ops_shares;
655 int limit_hit = m_loadshed_limit_hit.exchange(0);
656 TRACE(
DEBUG,
"Throttle limit hit " << limit_hit <<
" times during last interval.");
659 m_compute_var.Lock();
660 m_stable_io_active = m_io_active.load(std::memory_order_acquire);
661 auto io_active = m_stable_io_active;
662 m_stable_io_total = m_io_total;
663 auto io_total = m_stable_io_total;
664 auto io_wait_rep = m_io_active_time.exchange(std::chrono::steady_clock::duration(0).count());
665 m_stable_io_wait += std::chrono::steady_clock::duration(io_wait_rep);
667 m_compute_var.UnLock();
669 auto io_wait_ms = std::chrono::duration_cast<std::chrono::milliseconds>(m_stable_io_wait).count();
670 TRACE(IOLOAD,
"Current IO counter is " << io_active <<
"; total IO active time is " << io_wait_ms <<
"ms.");
674 auto len = snprintf(buf, 128,
675 R
"({"event":"throttle_update","io_wait":%.4f,"io_active":%d,"io_total":%llu})",
676 static_cast<double>(io_wait_ms) / 1000.0, io_active,
static_cast<long long unsigned>(io_total));
677 auto suc = (len < 128) ? m_gstream->Insert(buf, len + 1) :
false;
680 TRACE(IOLOAD,
"Failed g-stream insertion of throttle_update record (len=" << len <<
"): " << buf);
683 m_compute_var.Broadcast();
690XrdThrottleManager::GetUid(
const std::string &username)
692 std::hash<std::string> hash_fn;
693 auto hash = hash_fn(username);
694 auto uid =
static_cast<uint16_t
>(hash % m_max_users);
695 TRACE(
DEBUG,
"Mapping user " << username <<
" to UID " << uid);
703XrdThrottleManager::NotifyOne()
705 auto &wake_order = (m_wake_order_active == 0) ? m_wake_order_0 : m_wake_order_1;
707 for (
size_t idx = 0; idx < m_max_users; ++idx)
709 auto offset = m_waiter_offset.fetch_add(1, std::memory_order_acq_rel);
710 int16_t uid = wake_order[offset % m_max_users];
715 auto &waiter_info = m_waiter_info[uid];
716 std::unique_lock<std::mutex> lock(waiter_info.m_mutex);
717 if (waiter_info.m_waiting) {
718 waiter_info.NotifyOne(std::move(lock));
730 int cur_counter = m_io_active.fetch_add(1, std::memory_order_acq_rel);
733 while (m_concurrency_limit >= 0 && cur_counter >= m_concurrency_limit)
738 if (m_waiter_info[uid].m_concurrency < 1)
742 m_loadshed_limit_hit++;
743 m_io_active.fetch_sub(1, std::memory_order_acq_rel);
744 TRACE(
DEBUG,
"ThrottleManager (user=" << uid <<
"): IO concurrency limit hit; waiting for other IOs to finish.");
745 ok = m_waiter_info[uid].Wait();
747 TRACE(
DEBUG,
"ThrottleManager (user=" << uid <<
"): timed out waiting for other IOs to finish.");
750 cur_counter = m_io_active.fetch_add(1, std::memory_order_acq_rel);
763 m_io_active_time += event_duration.count();
764 auto old_active = m_io_active.fetch_sub(1, std::memory_order_acq_rel);
765 m_waiter_info[uid].m_io_time += event_duration.count();
766 if (old_active ==
static_cast<unsigned>(m_concurrency_limit))
771 unsigned waiting_users = m_waiting_users;
772 if (waiting_users == 0) waiting_users = 1;
773 if (m_waiter_info[uid].m_concurrency < m_concurrency_limit / waiting_users)
775 std::unique_lock<std::mutex> lock(m_waiter_info[uid].m_mutex);
776 if (m_waiter_info[uid].m_waiting > 0)
778 m_waiter_info[uid].NotifyOne(std::move(lock));
796 if (m_loadshed_port == 0)
800 if (m_loadshed_limit_hit == 0)
804 if (
static_cast<unsigned>(rand()) % 100 > m_loadshed_frequency)
818 if (m_loadshed_port == 0)
822 if (opaque && opaque[0])
826 if (env.
Get(
"throttle.shed") != 0)
831 lsOpaque +=
"&throttle.shed=1";
835 lsOpaque =
"throttle.shed=1";
842 host = m_loadshed_host;
845 port = m_loadshed_port;
849XrdThrottleManager::Waiter::Wait()
851 auto timeout = std::chrono::steady_clock::now() + m_manager->m_max_wait_time;
853 std::unique_lock<std::mutex> lock(m_mutex);
855 m_cv.wait_until(lock, timeout,
856 [&] {
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; });
859 if (std::chrono::steady_clock::now() > timeout) {
883 INIReader reader(config_file);
884 if (reader.ParseError() < 0)
886 m_log->Emsg(
"ThrottleManager", errno,
"Unable to open per-user configuration file", config_file.c_str());
889 else if (reader.ParseError() > 0)
891 std::stringstream ss;
892 ss <<
"Parse error on line " << reader.ParseError() <<
" of file " << config_file;
893 m_log->Emsg(
"ThrottleManager", ss.str().c_str());
897 std::unordered_map<std::string, UserLimit> new_limits;
900 for (
const auto §ion : reader.Sections())
903 std::string name = reader.Get(section,
"name",
"");
906 m_log->Say(
"ThrottleManager",
"Section", section.c_str(),
"missing 'name' parameter; skipping");
910 long max_conn = reader.GetInteger(section,
"maxconn", 0);
913 m_log->Say(
"ThrottleManager",
"Section", section.c_str(),
"has invalid or missing 'maxconn' parameter; skipping");
918 limit.max_conn =
static_cast<unsigned long>(max_conn);
920 limit.is_wildcard = (name.find(
'*') != std::string::npos);
921 new_limits[name] = limit;
925 size_t num_entries = new_limits.size();
927 std::unique_lock<std::shared_mutex> lock(m_user_limits_mutex);
928 m_user_limits = std::move(new_limits);
931 m_log->Say(
"ThrottleManager",
"Loaded", std::to_string(num_entries).c_str(),
"per-user limit entries from", config_file.c_str());
941 if (m_user_config_file.empty())
943 m_log->Emsg(
"ThrottleManager",
"No per-user configuration file specified");
959 std::shared_lock lock(m_user_limits_mutex);
962 auto exact_iter = m_user_limits.find(username);
963 if (exact_iter != m_user_limits.end() && !exact_iter->second.is_wildcard)
965 return exact_iter->second.max_conn;
969 unsigned long best_match = 0;
970 size_t best_prefix_len = 0;
971 unsigned long catch_all_match = 0;
973 for (
const auto &entry : m_user_limits)
975 if (!entry.second.is_wildcard)
continue;
977 const std::string &pattern = entry.first;
982 catch_all_match = entry.second.max_conn;
986 size_t wildcard_pos = pattern.find(
'*');
987 if (wildcard_pos == std::string::npos)
continue;
990 std::string prefix = pattern.substr(0, wildcard_pos);
991 if (username.length() >= prefix.length() &&
992 username.substr(0, prefix.length()) == prefix)
995 if (prefix.length() > best_prefix_len)
997 best_prefix_len = prefix.length();
998 best_match = entry.second.max_conn;
1004 if (best_match > 0)
return best_match;
1008 return catch_all_match;
#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)
int LoadUserLimits(const std::string &config_file)
void SetMaxWait(unsigned long max_wait)
unsigned long GetUserMaxConn(const std::string &username)
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
const std::string & GetUserConfigFile() 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