17#define dprintf(...) printf(__VA_ARGS__)
19#define dprintf(...) (void(0))
27 const char *m_traceID =
"ResourceMonitor";
37 m_protected_top_dirs.insert(
"pfc-stats");
51 m_dir_scan_mutex.Lock();
52 if (m_dir_scan_in_progress) {
53 bool dir_checked =
false;
54 m_dir_scan_open_requests.push_back({lfn, cond, dir_checked});
56 m_dir_scan_mutex.UnLock();
57 while ( ! dir_checked)
61 m_dir_scan_mutex.UnLock();
65void ResourceMonitor::process_inter_dir_scan_open_requests(
FsTraversal &fst)
67 m_dir_scan_mutex.
Lock();
68 while ( ! m_dir_scan_open_requests.empty())
70 LfnCondRecord &lcr = m_dir_scan_open_requests.front();
73 cross_check_or_process_oob_lfn(lcr.f_lfn, fst);
79 m_dir_scan_mutex.
Lock();
80 m_dir_scan_open_requests.pop_front();
85void ResourceMonitor::cross_check_or_process_oob_lfn(
const std::string &lfn,
FsTraversal &fst)
89 static const char *trc_pfx =
"cross_check_or_process_oob_lfn() ";
94 DirState *last_existing_ds =
nullptr;
95 DirState *ds = m_fs_state.find_dirstate_for_lfn(lfn, &last_existing_ds);
99 size_t pos = lfn.find_last_of(
"/");
100 std::string dir = (pos == std::string::npos) ?
"" : lfn.substr(0, pos);
102 XrdOssDF *dhp = m_oss.newDir(trc_pfx);
112 if (it->second.has_data && it->second.has_cinfo) {
113 here.
m_StBlocks += it->second.stat_data.st_blocks;
115 seed_last_times_from_cinfo(here, it->second.stat_cinfo);
127 dprintf(
"In scan_dir_and_recurse for '%s', size of dir_vec = %d, file_stat_map = %d\n",
138 dprintf(
"would be doing something with %s ... has_data=%d, has_cinfo=%d\n",
139 it->first.c_str(), it->second.has_data, it->second.has_cinfo);
147 if (it->second.has_data && it->second.has_cinfo) {
148 here.
m_StBlocks += it->second.stat_data.st_blocks;
150 seed_last_times_from_cinfo(here, it->second.stat_cinfo);
157 std::vector<std::string> dirs;
160 if (++m_dir_scan_check_counter >= 100)
162 process_inter_dir_scan_open_requests(fst);
163 m_dir_scan_check_counter = 0;
168 for (
auto &dname : dirs)
186 DirState *root_ds = m_fs_state.get_root();
205 m_dir_scan_in_progress =
false;
206 m_dir_scan_check_counter = 0;
209 while ( ! m_dir_scan_open_requests.empty())
211 LfnCondRecord &lcr = m_dir_scan_open_requests.front();
213 lcr.f_checked =
true;
217 m_dir_scan_open_requests.pop_front();
235 static const char *trc_pfx =
"process_queues() ";
246 n_records += m_file_open_q.swap_queues();
247 n_records += m_file_update_stats_q.swap_queues();
248 n_records += m_file_close_q.swap_queues();
249 n_records += m_file_purge_q1.swap_queues();
250 n_records += m_file_purge_q2.swap_queues();
251 n_records += m_file_purge_q3.swap_queues();
255 for (
auto &i : m_file_open_q.read_queue())
258 AccessToken &at =
token(i.id);
259 dprintf(
"process file open for token %d, time %ld -- %s\n",
260 i.id, i.record.m_open_time, at.m_filename.c_str());
265 DirState *last_existing_ds =
nullptr;
266 DirState *ds = m_fs_state.find_dirstate_for_lfn(at.m_filename, &last_existing_ds);
273 for (
auto &i : m_file_update_stats_q.read_queue())
276 AccessToken &at =
token(i.id);
279 dprintf(
"process file update for token %d, %p -- %s\n",
280 i.id, ds, at.m_filename.c_str());
283 m_current_usage_in_st_blocks += i.record.m_StBlocksAdded;
286 for (
auto &i : m_file_close_q.read_queue())
289 AccessToken &at =
token(i.id);
290 dprintf(
"process file close for token %d, time %ld -- %s\n",
291 i.id, i.record.m_close_time, at.m_filename.c_str());
300 for (
auto &i : m_file_close_q.read_queue())
301 m_access_tokens.release(i.id);
304 for (
auto &i : m_file_purge_q1.read_queue())
310 m_current_usage_in_st_blocks -= i.record.m_size_in_st_blocks;
312 for (
auto &i : m_file_purge_q2.read_queue())
317 if (is_in_protected_top_dir(i.id)) {
318 TRACE(
Debug, trc_pfx <<
"no DirState for '" << i.id
319 <<
"', as expected under a protected top directory.");
321 TRACE(
Error, trc_pfx <<
"DirState not found for directory path '" << i.id <<
"'.");
328 m_current_usage_in_st_blocks -= i.record.m_size_in_st_blocks;
330 for (
auto &i : m_file_purge_q3.read_queue())
335 if (is_in_protected_top_dir(i.id)) {
336 TRACE(
Debug, trc_pfx <<
"no DirState for '" << i.id
337 <<
"', as expected under a protected top directory.");
339 TRACE(
Error, trc_pfx <<
"DirState not found for LFN path '" << i.id <<
"'.");
345 m_current_usage_in_st_blocks -= i.record;
360 static const char *tpfx =
"heart_beat() ";
364 const int s_queue_proc_interval = 10;
367 const int s_purge_check_interval = 60;
373 time_t now = time(0);
374 time_t next_queue_proc_time = now + s_queue_proc_interval;
375 time_t next_sshot_report_time = (now / 60) * 60 + 60;
376 time_t next_purge_check_time = now + s_purge_check_interval;
377 time_t next_purge_report_time = now + s_purge_report_interval;
378 time_t next_purge_cold_files_time = now + s_purge_cold_files_interval;
380 long long n_reported_token_failures = 0;
384 time_t start = time(0);
385 time_t next_event = std::min({ next_queue_proc_time, next_sshot_report_time,
386 next_purge_check_time, next_purge_report_time, next_purge_cold_files_time });
388 if (next_event > start)
390 unsigned int t_sleep = next_event - start;
391 TRACE(Dump, tpfx <<
"sleeping for " << t_sleep <<
" seconds until the next beat.");
405 time_t queue_swap_time = time(0);
409 next_queue_proc_time = queue_swap_time + s_queue_proc_interval;
410 TRACE(Dump, tpfx <<
"process_queues -- n_records=" << n_processed);
415 if (taf > n_reported_token_failures) {
416 TRACE(
Error, tpfx <<
"access-token space exhausted "
417 << taf - n_reported_token_failures <<
" time(s) (total " << taf
418 <<
"); those files are open but not accounted for. Raise MaxChunks "
419 "on ResourceMonitor::m_access_tokens.");
420 n_reported_token_failures = taf;
431 bool do_sshot_report = next_sshot_report_time <= now;
432 bool do_purge_check = next_purge_check_time <= now;
433 bool do_purge_report = next_purge_report_time <= now;
434 bool do_purge_cold_files = next_purge_cold_files_time <= now;
437 if (do_sshot_report || do_purge_check || do_purge_report || do_purge_cold_files)
439 m_fs_state.update_stats_and_usages(queue_swap_time, do_sshot_report);
447 const DirState &root_ds = *m_fs_state.get_root();
451 m_fs_state.dump_recursively(store_depth);
459 next_sshot_report_time = ((now + 1) / s_sshot_report_interval) * s_sshot_report_interval + s_sshot_report_interval;
464 DataFsSnapshot ss(m_fs_state, m_fs_state.m_sshot_stats_reset_time);
466 const DirState &root_ds = *m_fs_state.get_root();
476 const char* dumpfile =
"/pfc-stats/DirStat.json";
478 m_fs_state.reset_sshot_stats(queue_swap_time);
481 if (do_purge_check || do_purge_report || do_purge_cold_files)
485 next_purge_check_time = now + s_purge_check_interval;
486 if (do_purge_report) next_purge_report_time = now + s_purge_report_interval;
487 if (do_purge_cold_files) next_purge_cold_files_time = now + s_purge_cold_files_interval;
495 unlink_func unlink_foo = [&](
const std::string &dp)->
int {
496 int ret = m_oss.Unlink(dp.c_str());
498 TRACE(
Info, tpfx <<
"Empty dir unlink error: " << ret <<
" at " << dp);
500 TRACE(
Debug, tpfx <<
"Empty dir unlink success: " << dp);
505 m_current_usage_in_st_blocks -= m_fs_state.reap_empty_leaf_dirs(unlink_foo);
517 std::vector<DirStateElement> &vec,
520 const int pos0 = (int) vec.size();
521 const int n_children = (int) parent_ds.
m_subdirs.size();
524 if (parent_ds.
m_depth >= max_depth)
527 if (n_children == 0) {
540 if (n_children == 0)
return;
557 std::vector<DirPurgeElement> &vec,
560 const int pos0 = (int) vec.size();
561 const int n_children = (int) parent_ds.
m_subdirs.size();
564 if (parent_ds.
m_depth >= max_depth)
567 if (n_children == 0) {
580 if (n_children == 0)
return;
601 static const char *trc_pfx =
"update_vs_and_file_usage_info() ";
607 if (m_oss.StatVS(&vsi, conf.m_data_space.c_str(), 1) < 0) {
608 TRACE(
Error, trc_pfx <<
"can't get StatVS for oss space '" << conf.m_data_space <<
"'. This is a fatal error.");
611 m_fs_state.m_disk_total = vsi.
Total;
612 m_fs_state.m_disk_used = vsi.
Total - vsi.
Free;
613 m_fs_state.m_file_usage = 512ll * m_current_usage_in_st_blocks;
614 if (m_oss.StatVS(&vsi, conf.m_meta_space.c_str(), 1) < 0) {
615 TRACE(
Error, trc_pfx <<
"can't get StatVS for oss space '" << conf.m_meta_space <<
"'. This is a fatal error.");
618 m_fs_state.m_meta_total = vsi.
Total;
619 m_fs_state.m_meta_used = vsi.
Total - vsi.
Free;
622long long ResourceMonitor::get_file_usage_bytes_to_remove(
const DataFsPurgeshot &ps,
long long write_estimate,
int tl)
638 long long delta = write_estimate;
639 TRACE_INT(tl,
"file usage increased since the previous purge interval in bytes: " << delta );
641 long long bytes_to_remove = 0;
644 auto clamp = [&x, &bytes_to_remove](
long long lowval,
long long highval)
647 long long newval = val - bytes_to_remove;
656 if (newval > highval)
658 return val - highval;
661 return bytes_to_remove;
672 float frac_u =
static_cast<float>(u - w2) / (T - w2);
673 float frac_x =
static_cast<float>(x - f0) / (f1 - f0);
677 bytes_to_remove = u -w1;
684 bytes_to_remove = (frac_x - frac_u) * (f1 - f0);
685 bytes_to_remove += delta;
686 bytes_to_remove = clamp(f0, f1);
691 bytes_to_remove = clamp(f0, f2);
693 return bytes_to_remove;
699 if (u > w1 && x > f1)
701 float frac_u =
static_cast<float>(u - w1) / (w2 - w1);
702 float frac_x =
static_cast<float>(x - f1) / (f2 - f1);
705 TRACE_INT(tl,
"Disproportional file quota usage comapared to disc usage (frac_x/frac_u) = " << frac_x <<
"/"<< frac_u);
706 bytes_to_remove = (frac_x - frac_u) * (f2 - f1);
707 bytes_to_remove += delta;
711 bytes_to_remove = clamp(f0, f2);
712 return bytes_to_remove;
722 TRACE_INT(tl,
"File usage exceeds maxim file usage. Total disk usage is under lowWatermark. Clearing to low file usage.");
723 long long f2delta = std::max(f2 - delta, f0);
724 bytes_to_remove = clamp(f0, f2delta);
725 return bytes_to_remove;
728 return bytes_to_remove;
733 static const char *trc_pfx =
"perform_purge_check() ";
736 std::unique_ptr<DataFsPurgeshot> psp(
new DataFsPurgeshot(m_fs_state) );
744 TRACE_INT(tl, trc_pfx <<
"Purge check:");
778 TRACE(
Info, trc_pfx <<
"purge not required.");
783 TRACE(Warning, trc_pfx <<
"purge required but previous purge task is still active!");
787 TRACE(
Info, trc_pfx <<
"scheduling purge task.");
799 const DirState &root_ds = *m_fs_state.get_root();
807 dprintf(
"purge dir count recursive=%d vs from_usage=%d\n", n_pshot_dirs, n_calc_dirs);
808 const int discrepancy = n_calc_dirs - n_pshot_dirs;
809 if (discrepancy != m_dir_count_discrepancy)
811 if (discrepancy != 0) {
812 TRACE(
Error, trc_pfx <<
"directory count mismatch: usage counters " << n_calc_dirs
813 <<
", tree " << n_pshot_dirs <<
", previous difference " << m_dir_count_discrepancy);
815 TRACE(
Info, trc_pfx <<
"directory count consistent again: " << n_pshot_dirs);
817 m_dir_count_discrepancy = discrepancy;
826 struct PurgeDriverJob :
public XrdJob
831 XrdJob(
"XrdPfc::ResourceMonitor::PurgeDriver"),
832 m_purge_shot_ptr(psp)
840 delete m_purge_shot_ptr;
845 Cache::schedP->Schedule(
new PurgeDriverJob(psp.release()) );
894 m_dir_scan_in_progress =
true;
899 const char *tpfx =
"main_thread_function ";
901 time_t is_start = time(0);
902 m_fs_state.init_stat_reset_times(is_start);
903 TRACE(
Info, tpfx <<
"Stating initial directory scan.");
906 TRACE(
Error, tpfx <<
"Initial directory scan has failed. This is a terminal error, aborting.")
911 time_t is_duration = time(0) - is_start;
912 TRACE(
Info, tpfx <<
"Initial directory scan complete, duration=" << is_duration <<
"s");
916 TRACE(
Info, tpfx <<
"First process_queues finished, n_records=" << n_proc_is);
919 if (is_duration > 30 || n_proc_is > 3000)
921 m_file_open_q.shrink_read_queue();
922 m_file_update_stats_q.shrink_read_queue();
923 m_file_close_q.shrink_read_queue();
924 m_file_purge_q1.shrink_read_queue();
925 m_file_purge_q2.shrink_read_queue();
926 m_file_purge_q3.shrink_read_queue();
972 time_t heartbeat_start = time(0);
1016 int heartbeat_duration = time(0) - heartbeat_start;
1021 int sleep_time = 60 - heartbeat_duration;
int DoIt(int argpnt, int argc, char **argv, bool singleshot)
void Proto_ResourceMonitorHeartBeat()
#define TRACE_INT(act, x)
virtual int Opendir(const char *path, XrdOucEnv &env)
struct XrdOucCacheStats::CacheStats X
XrdOucCacheStats Statistics
static const Configuration & Conf()
static ResourceMonitor & ResMon()
void ClearPurgeProtectedSet()
static Cache & GetInstance()
Singleton access.
XrdSysTrace * GetTrace() const
static XrdScheduler * schedP
long long WritesSinceLastCall()
PurgePin * GetPurgePin() const
void AddUp(const DirStats &s)
long long m_StBlocksRemoved
std::vector< std::string > m_current_dirs
std::string m_current_path
void slurp_dir_ll(XrdOssDF &dh, int dir_level, const char *path, const char *trc_pfx)
bool begin_traversal(DirState *root, const char *root_path)
std::set< std::string > m_protected_top_dirs
bool cd_down(const std::string &dir_name)
XrdOucEnv & default_env()
std::map< std::string, FilePairStat > m_current_files
Status of cached file. Can be read from and written into a binary file.
virtual bool CallPeriodically()
bool m_purge_task_complete
AccessToken & token(int i)
void CrossCheckIfScanIsInProgress(const std::string &lfn, XrdSysCondVar &cond)
void perform_purge_check(bool purge_cold_files, int tl)
void fill_sshot_vec_children(const DirState &parent_ds, int parent_idx, std::vector< DirStateElement > &vec, int max_depth)
ResourceMonitor(XrdOss &oss)
void perform_purge_task(DataFsPurgeshot &ps)
void scan_dir_and_recurse(FsTraversal &fst)
void fill_pshot_vec_children(const DirState &parent_ds, int parent_idx, std::vector< DirPurgeElement > &vec, int max_depth)
XrdSysCondVar m_purge_task_cond
void perform_purge_task_cleanup()
void update_vs_and_file_usage_info()
bool perform_initial_scan()
time_t m_purge_task_start
void main_thread_function()
std::function< int(const std::string &)> unlink_func
void OldStylePurgeDriver(DataFsPurgeshot &ps)
void note_file_open(DirState *node, DirState *last_existing, bool existing_file)
Contains parameters configurable from the xrootd config file.
long long m_RamAbsAvailable
available from configuration
long long m_diskTotalSpace
total disk space on configured partition or oss space
long long m_fileUsageMax
cache purge - files usage maximum
long long m_fileUsageBaseline
cache purge - files usage baseline
int m_dirStatsStoreDepth
maximum depth for statistics write out
long long m_diskUsageHWM
cache purge - disk usage high water mark
bool are_file_usage_limits_set() const
long long m_fileUsageNominal
cache purge - files usage nominal
int m_purgeAgeBasedPeriod
peform cold file / uvkeep purge every this many purge cycles
long long m_diskUsageLWM
cache purge - disk usage low water mark
int m_dirStatsInterval
time between resource monitor statistics dump in seconds
bool is_age_based_purge_in_effect() const
int m_purgeInterval
sleep interval between cache purges
bool is_dir_stat_reporting_on() const
std::vector< DirPurgeElement > m_dir_vec
long long m_bytes_to_remove
long long m_estimated_writes_from_writeq
void write_json_file(const std::string &fname, XrdOss &oss, bool include_preamble)
std::vector< DirStateElement > m_dir_states
long long m_st_blocks_fuzz
DataFsState::m_st_blocks_fuzz.
DirUsage m_recursive_subdir_usage
int count_dirs_to_level(int max_depth) const
DirState * find_path(const std::string &path, int max_depth, bool parse_as_lfn, bool create_subdirs, DirState **last_existing_dir=nullptr)
void upward_propagate_initial_scan_usages()