/* Copyright (c) 2013 IOnU Security Inc. All rights reserved Created October 2013 by Kendrick Webster K2Daemon/LogMemstats.cpp - implementation for LogMemstats.h */ #ifdef __MACH__ #include #include #include #include #else #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" #include "ClientMap.h" #include "OfficeSubscriptions.h" #include "Version.h" #include "LogMemstats.h" using namespace boost::property_tree; // For debugging, un-comment (temporarilly) as needed: //#define SHOW_RAW_MALLOC_INFO //#define SHOW_PROC_SELF_STATUS // Log severity for allocated memory and CPU time summary statistics static const Log::severity_t stat_loglev = Log::informational; // Format strings (to make columns line up in log text) #define LABEL_FMT "%-40s" // Label field format #define F1_WIDTH "%12" // Data field 1 width static unsigned int getUptimeSeconds(void); static Memstats::uint_t getHeapAllocated(void); namespace Memstats // snapshot values and accessors { uint_t n_cpus; uint_t CpuCount(void) {return n_cpus;} uint_t total_virtual_memory; uint_t TotalVirtualMemory(void) {return total_virtual_memory;} uint_t total_memory; uint_t TotalMemory(void) {return total_memory;} uint_t system_virtual_memory_used; uint_t SystemWideVirtualMemoryUsed(void) {return system_virtual_memory_used;} uint_t system_memory_used; uint_t SystemWideMemoryUsed(void) {return system_memory_used;} double system_cpu_load_percent; double SystemWideCpuLoadPercent(void) {return system_cpu_load_percent;} uint_t UptimeSeconds(void) {return getUptimeSeconds();} uint_t ConnectedClients(void) {return ClientMap::Count();} uint_t SubscribedOffices(void) {return OfficeSubscriptions::OfficeCount();} uint_t OfficeSubscriptions(void) {return OfficeSubscriptions::SubscriptionCount();} uint_t virtual_memory_used; uint_t VirtualMemoryUsed(void) {return virtual_memory_used;} uint_t memory_used; uint_t MemoryUsed(void) {return memory_used;} uint_t HeapAllocated(void) {return getHeapAllocated();} double cpu_load_percent; double CpuLoadPercent(void) {return cpu_load_percent;} double cpu_user_percent; double CpuUserPercent(void) {return cpu_user_percent;} double cpu_system_percent; double CpuSystemPercent(void) {return cpu_system_percent;} double cpu_child_user_percent; double CpuChildUserPercent(void) {return cpu_child_user_percent;} double cpu_child_system_percent; double CpuChildSystemPercent(void) {return cpu_child_system_percent;} } struct CUptimeInitializer { std::chrono::steady_clock::time_point t0; CUptimeInitializer() { t0 = std::chrono::steady_clock::now(); } }; static CUptimeInitializer uptimeInit; static uint64_t getUptimeMicroseconds(void) { using std::chrono::duration_cast; using std::chrono::microseconds; using std::chrono::steady_clock; return duration_cast(steady_clock::now() - uptimeInit.t0).count(); } static unsigned int getUptimeSeconds(void) { return static_cast(getUptimeMicroseconds() / 1000000); } static Memstats::uint_t getHeapAllocated(void) { #ifndef __MACH__ struct mallinfo mi = mallinfo(); return static_cast(mi.uordblks); #else // Don't know how to determine heap usage on OS X, but it isn't critical since the // daemon is intended to run on Linux (with OS X only supported for developer use) return 0; #endif } /* --------------------------------------------------------------------------- Utility functions */ static const char* kibi(uint64_t x) { constexpr unsigned int k = 1024; constexpr const char* u = "KMGTPE"; static char v[16]; if (x < k) { snprintf(v, sizeof(v), "%d B", static_cast(x)); } else { int e = static_cast(log(x) / log(k)); if ((e < 1) || (e > 6)) { return ""; } snprintf(v, sizeof(v), "%.1f %ciB", x / pow(k, e), u[e - 1]); } return v; } static const char* kibi(uint64_t x, char& c) { constexpr unsigned int k = 1024; constexpr const char* u = "KMGTPE"; static char v[16]; if (x < k) { snprintf(v, sizeof(v), "%d", static_cast(x)); c = 0; } else { int e = static_cast(log(x) / log(k)); if ((e < 1) || (e > 6)) { c = 0; return ""; } snprintf(v, sizeof(v), "%.1f", x / pow(k, e)); c = u[e - 1]; } return v; } static double percent(long long n, long long d) { return 100.0 * (static_cast(n) / static_cast(d)); } /* --------------------------------------------------------------------------- Implementation */ static unsigned int get_num_cpus(void) { Memstats::uint_t &n = Memstats::n_cpus; if (0 == n) { #ifndef __MACH__ int d; char b[128]; FILE* f = fopen("/proc/cpuinfo", "rt"); while (NULL != fgets(b, sizeof(b), f)) { if (1 == sscanf(b, "processor : %d", &d)) { ++n; } } fclose(f); #else int mib[2], n1; size_t len; mib[0] = CTL_HW; mib[1] = HW_NCPU; len = sizeof(n1); sysctl(mib, 2, &n1, &len, NULL, 0); n = n1; #endif } return static_cast(n); } // ---------------------- // malloc_info() parsing #ifndef __MACH__ // everything related to malloc_info() is Linux-only, but is currently not used except for log output #if 0 // Summing the sizes produces erratic data, can't get the same stats as what mallinfo() returns. // // Since mallinfo() is deprecated (not 64-bit compatible, will biff if 32 bits overflow), it would // be preferable to get the total in-use stat from malloc_info() XML, but there doesn't appear to // be any way to do so currently. // // Keeping this code around so it can be resurrected if a later version of malloc_info() gives us // the stats we need. static void logPtree(const ptree& t) { uint64_t freed = 0, current = 0; const ptree& c = t.get_child("malloc"); const ptree& d = c.get_child("heap"); for (const ptree::value_type& e: d) { if (0 == e.first.compare("sizes")) { for (const ptree::value_type& f: e.second) { if ((0 == f.first.compare("size")) || (0 == f.first.compare("unsorted"))) { const ptree& a = f.second.get_child(""); std::string total = a.get("total"); freed += strtoul(total.c_str(), NULL, 0); } } } else if (0 == e.first.compare("system")) { const ptree& a = e.second.get_child(""); std::string type = a.get("type"); if (0 == type.compare("current")) { std::string size = a.get("size"); current = strtoul(size.c_str(), NULL, 0); } } } logprintf(Log::debug, LABEL_FMT F1_WIDTH "lu (%s)", "Current heap size:", current, kibi(current)); logprintf(Log::debug, LABEL_FMT F1_WIDTH "lu (%s)", "Heap freed:", freed, kibi(freed)); } #else static void logPtree(const ptree& t) { const ptree& c = t.get_child("malloc"); const ptree& d = c.get_child("heap"); for (const ptree::value_type& e: d) { if (0 == e.first.compare("system")) { const ptree& a = e.second.get_child(""); std::string type = a.get("type"); if (0 == type.compare("current")) { std::string size = a.get("size"); uint64_t current = strtoul(size.c_str(), NULL, 0); logprintf(Log::debug, LABEL_FMT F1_WIDTH "lu (%s)", "Current heap size:", current, kibi(current)); } } } } #endif static void logMallocInfoSummary(const char* p) { using boost::property_tree::ptree; ptree pt; std::stringstream ss; std::string str; ss.str(p); read_xml(ss, pt); try { logPtree(pt); } catch (...) { logprintf(Log::error, "unable to parse malloc_info() xml"); } } #ifdef SHOW_RAW_MALLOC_INFO static const char* indent(int d) { switch (d) { case 0: return ""; case 1: return " "; case 2: return " "; case 3: return " "; case 4: return " "; default: return " "; } } static void logMallocInfoRaw(const char* p) { enum { START, TAG1, TAG2, TAG3, TAG4 } e = START; const char* q; std::string s; int d = 0, i = 0; logprintf(Log::debug3, "malloc_info:"); while (char c = *p++) { switch (c) { case '\n': q = strstr(s.c_str(), "size=\""); if (q) { uint64_t x = strtoul (q + 6, NULL, 0); logprintf(Log::debug3, "%s%-64s%s", indent(i), s.c_str(), kibi(x)); } else { logprintf(Log::debug3, "%s%s", indent(i), s.c_str()); } i = d; s.clear(); break; case '<': e = TAG1; s += c; break; case '/': switch (e) { case TAG1: e = TAG3; break; case TAG2: e = TAG4; break; default: break; } s += c; break; case '>': switch (e) { case TAG1: case TAG2: ++d; break; case TAG3: i = --d; break; default: break; } e = START; s += c; break; default: switch (e) { case TAG1: e = TAG2; break; default: break; } s += c; break; } } } #endif // SHOW_RAW_MALLOC_INFO static void logMallocInfo(bool details) { if (details) { char *p; size_t size; FILE *f = open_memstream(&p, &size); if (NULL == f) { logprintf(Log::error, "open_memstream: %s", strerror(errno)); return; } if (0 != malloc_info(0, f)) { logprintf(Log::error, "malloc_info: %s", strerror(errno)); fclose(f); return; } fclose(f); #ifdef SHOW_RAW_MALLOC_INFO logMallocInfoRaw(p); #endif logMallocInfoSummary(p); free(p); } } #else // __MACH__ static void logMallocInfo(bool details) {} #endif // END malloc_info() parsing // -------------------------- #ifndef __MACH__ static void logMallinfo(bool details) { struct mallinfo mi = mallinfo(); if (details) { logprintf(Log::debug3, LABEL_FMT F1_WIDTH "d (%s)", "Total non-mmapped bytes (arena):", mi.arena, kibi(mi.arena)); logprintf(Log::debug3, LABEL_FMT F1_WIDTH "d", "# of free chunks (ordblks):", mi.ordblks); logprintf(Log::debug3, LABEL_FMT F1_WIDTH "d", "# of free fastbin blocks (smblks):", mi.smblks); logprintf(Log::debug3, LABEL_FMT F1_WIDTH "d", "# of mapped regions (hblks):", mi.hblks); logprintf(Log::debug3, LABEL_FMT F1_WIDTH "d (%s)", "Bytes in mapped regions (hblkhd):", mi.hblkhd, kibi(mi.hblkhd)); logprintf(Log::debug3, LABEL_FMT F1_WIDTH "d (%s)", "Max. total allocated space (usmblks):", mi.usmblks, kibi(mi.usmblks)); logprintf(Log::debug3, LABEL_FMT F1_WIDTH "d (%s)", "Free bytes held in fastbins (fsmblks):", mi.fsmblks, kibi(mi.fsmblks)); logprintf(stat_loglev, LABEL_FMT F1_WIDTH "d (%s)", "Total allocated space (uordblks):", mi.uordblks, kibi(mi.uordblks)); logprintf(Log::debug3, LABEL_FMT F1_WIDTH "d (%s)", "Total free space (fordblks):", mi.fordblks, kibi(mi.fordblks)); logprintf(Log::debug3, LABEL_FMT F1_WIDTH "d (%s)", "Topmost releasable block (keepcost):", mi.keepcost, kibi(mi.keepcost)); } char c; const char* s = kibi(mi.uordblks, c); char p[4]; snprintf(p, sizeof(p), c ? "%ciB" : "B", c); logprintf(stat_loglev, LABEL_FMT F1_WIDTH "s %s (%d bytes)", "Heap allocated:", s, p, mi.uordblks); } #else static void logMallinfo(bool details) { // no equivalent on OS X, skip } #endif // ---------------------------- // Virtual and physical memory #ifndef __MACH__ typedef struct { int VmSize; int VmRSS; } proc_self_status_t; static void getProcSelfVmSize(proc_self_status_t& out #ifdef SHOW_PROC_SELF_STATUS , bool details #endif ) { memset(&out, 0, sizeof(out)); static const char* fname = "/proc/self/status"; FILE* f = fopen(fname, "rt"); if (NULL == f) { logprintf(Log::error, "fopen(\"%s\"): %s", fname, strerror(errno)); return; } char b[128]; #ifdef SHOW_PROC_SELF_STATUS if (details) { logprintf(Log::debug3, "%s :", fname); } #endif while (NULL != fgets(b, sizeof(b), f)) { #ifdef SHOW_PROC_SELF_STATUS if (details) { std::string s = b; boost::algorithm::trim(s); logprintf(Log::debug3, " %s", s.c_str()); } #endif if (0 == strncmp(b, "VmSize:", 7)) { out.VmSize = atoi(b + 7); } else if (0 == strncmp(b, "VmRSS:", 6)) { out.VmRSS = atoi(b + 6); } } fclose(f); } static long long totalVirtualMem; // calculated by logMemoryInfo(), shared with logLocalMemoryInfo() static long long totalRAM; // calculated by logMemoryInfo(), shared with logLocalMemoryInfo() static void logMemoryInfo(bool details) { struct sysinfo memInfo; if (0 != sysinfo(&memInfo)) { logprintf(Log::error, "sysinfo(): %s", strerror(errno)); return; } // Virtual memory totalVirtualMem = memInfo.totalram; totalVirtualMem += memInfo.totalswap; totalVirtualMem *= memInfo.mem_unit; Memstats::total_virtual_memory = static_cast(totalVirtualMem); long long virtualMemUsed = memInfo.totalram - memInfo.freeram; virtualMemUsed += memInfo.totalswap - memInfo.freeswap; virtualMemUsed *= memInfo.mem_unit; Memstats::system_virtual_memory_used = static_cast(virtualMemUsed); if (details) { logprintf(stat_loglev, LABEL_FMT F1_WIDTH "lld (%s)", "Total virtual memory:", totalVirtualMem, kibi(totalVirtualMem)); logprintf(stat_loglev, LABEL_FMT F1_WIDTH "lld (%s)", "Virtual memory used (system wide):", virtualMemUsed, kibi(virtualMemUsed)); } logprintf(stat_loglev, LABEL_FMT F1_WIDTH ".1lf%% of %s", "Virtual memory used (system wide):", percent(virtualMemUsed, totalVirtualMem), kibi(totalVirtualMem)); // RAM totalRAM = memInfo.totalram; totalRAM *= memInfo.mem_unit; Memstats::total_memory = static_cast(totalRAM); long long ramUsed = memInfo.totalram - memInfo.freeram; ramUsed *= memInfo.mem_unit; Memstats::system_memory_used = static_cast(ramUsed); if (details) { logprintf(stat_loglev, LABEL_FMT F1_WIDTH "lld (%s)", "Total RAM:", totalRAM, kibi(totalRAM)); logprintf(stat_loglev, LABEL_FMT F1_WIDTH "lld (%s)", "RAM used (system wide):", ramUsed, kibi(ramUsed)); } logprintf(stat_loglev, LABEL_FMT F1_WIDTH ".1lf%% of %s", "RAM used (system wide):", percent(ramUsed, totalRAM), kibi(totalRAM)); } static void logLocalMemoryInfo(bool details) { proc_self_status_t pss; #ifdef SHOW_PROC_SELF_STATUS getProcSelfVmSize(pss, details); #else getProcSelfVmSize(pss); #endif long long virtualMemThisProcess = 1024 * pss.VmSize; Memstats::virtual_memory_used = static_cast(virtualMemThisProcess); if (details) { logprintf(stat_loglev, LABEL_FMT F1_WIDTH "i (%s)", "Virtual memory used:", virtualMemThisProcess, kibi(virtualMemThisProcess)); logprintf(stat_loglev, LABEL_FMT F1_WIDTH ".1lf%%", "Virtual memory used:", percent(virtualMemThisProcess, totalVirtualMem)); } long long ramThisProcess = 1024 * pss.VmRSS; Memstats::memory_used = static_cast(ramThisProcess); if (details) { logprintf(stat_loglev, LABEL_FMT F1_WIDTH "i (%s)", "RAM used:", ramThisProcess, kibi(ramThisProcess)); } logprintf(stat_loglev, LABEL_FMT F1_WIDTH ".1lf%% (%s)", "RAM used:", percent(ramThisProcess, totalRAM), kibi(ramThisProcess)); } #else // __MACH__ static long long totalRAM; // calculated by logMemoryInfo(), shared with logLocalMemoryInfo() static void logMemoryInfo(bool details) { int mib[2]; uint64_t memsize; size_t len; mib[0] = CTL_HW; mib[1] = HW_MEMSIZE; len = sizeof(memsize); sysctl(mib, 2, &memsize, &len, NULL, 0); totalRAM = memsize; Memstats::total_memory = static_cast(totalRAM); logprintf(stat_loglev, LABEL_FMT F1_WIDTH "llu (%s)", "Total RAM:", memsize, kibi(memsize)); } static void logLocalMemoryInfo(bool details) { struct task_basic_info s; mach_msg_type_number_t n = TASK_BASIC_INFO_COUNT; task_info(current_task(), TASK_BASIC_INFO, (task_info_t)&s, &n); Memstats::virtual_memory_used = s.virtual_size; Memstats::memory_used = s.resident_size; if (details) { logprintf(stat_loglev, LABEL_FMT F1_WIDTH "u (%s)", "RAM used:", s.resident_size, kibi(s.resident_size)); } logprintf(stat_loglev, LABEL_FMT F1_WIDTH ".1lf%% (%s)", "RAM used:", percent(s.resident_size, totalRAM), kibi(s.resident_size)); } #endif // __MACH__ // END Virtual and physical memory // -------------------------------- // --------- // CPU load #ifndef __MACH__ typedef unsigned long long cpu_stat_element_t; typedef struct { cpu_stat_element_t user; cpu_stat_element_t nice; cpu_stat_element_t system; cpu_stat_element_t idle; cpu_stat_element_t iowait; cpu_stat_element_t irq; cpu_stat_element_t softirq; cpu_stat_element_t steal; cpu_stat_element_t guest; cpu_stat_element_t guest_nice; } cpu_stats_t; static void logCPU(bool details) // system-wide CPU stats { if (details) { logprintf(Log::informational, LABEL_FMT F1_WIDTH "d", "CPU count:", get_num_cpus()); } FILE *f = fopen("/proc/stat", "rt"); if (NULL == f) { logprintf(Log::error, "fopen(\"/proc/stats\"): %s", strerror(errno)); return; } cpu_stats_t s = {0,0,0,0,0,0,0,0,0,0}; int n = fscanf(f, "cpu %Ld %Ld %Ld %Ld %Ld %Ld %Ld %Ld %Ld %Ld", &s.user, &s.nice, &s.system, &s.idle, &s.iowait, &s.irq, &s.softirq, &s.steal, &s.guest, &s.guest_nice); fclose(f); if (n < 10) { if (n < 4) { logprintf(Log::error, "Unable to scan CPU usage statistics from /proc/stat, fscanf returned %d", n); return; } else { logprintf(Log::informational, "scanned only %d (of 10) feilds from /proc/stat, the OS may be old", n); } } static cpu_stats_t r; if ((s.user < r.user) || (s.nice < r.nice) || (s.system < r.system) || (s.idle < r.idle) || (s.iowait < r.iowait) || (s.irq < r.irq) || (s.softirq < r.softirq) || (s.steal < r.steal) || (s.guest < r.guest) || (s.guest_nice < r.guest_nice)) { logprintf(Log::debug, "skipping current CPU stats calculation due to count overflow"); } else { cpu_stat_element_t d; d = (r.user - s.user) + (r.nice - s.nice) + (r.system - s.system) + (r.iowait - s.iowait) + (r.irq < s.irq) + (r.softirq - s.softirq) + (r.steal - s.steal) + (r.guest - s.guest) + (r.guest_nice - s.guest_nice); unsigned int nc = get_num_cpus(); Memstats::system_cpu_load_percent = percent(d, d + (r.idle - s.idle)); logprintf(Log::informational, LABEL_FMT F1_WIDTH ".1lf%% of %d core%s", "CPU used (system wide):", Memstats::system_cpu_load_percent, nc, (1 == nc) ? "" : "s"); } r = s; } struct CTimesInitializer { clock_t t0; struct tms s0; CTimesInitializer() { t0 = times(&s0); } }; static CTimesInitializer timesInit; static void logLocalCPU(bool details) // CPU stats for this process { static clock_t t0 = timesInit.t0; static struct tms s0 = timesInit.s0; struct tms s; clock_t t = times(&s); if ((t <= t0) || (s.tms_stime < s0.tms_stime) || (s.tms_utime < s0.tms_utime)) { logprintf(Log::debug, "skipping current local CPU stats calculation due to count overflow"); } else { long long n; long long d = (t - t0); #define LLCPU(field, label, stat) \ n = s.tms_##field - s0.tms_##field; \ Memstats::stat = percent(n, d); \ if (details) {logprintf(Log::debug, LABEL_FMT F1_WIDTH ".1lf%% (%d jiffies)", label ":", Memstats::stat, n);} LLCPU(utime, "CPU (user)", cpu_user_percent) LLCPU(stime, "CPU (system)", cpu_system_percent) LLCPU(cutime, "CPU (children, user)", cpu_child_user_percent) LLCPU(cstime, "CPU (children, system)", cpu_child_system_percent) n = (s.tms_utime - s0.tms_utime) + (s.tms_stime - s0.tms_stime) + (s.tms_cutime - s0.tms_cutime) + (s.tms_cstime - s0.tms_cstime); Memstats::cpu_load_percent = percent(n, d); logprintf(Log::informational, LABEL_FMT F1_WIDTH ".1lf%% of 1 core (%d, %d, %d, %d jiffies u,s,cu,cs)", "CPU used:", Memstats::cpu_load_percent, (s.tms_utime - s0.tms_utime), (s.tms_stime - s0.tms_stime), (s.tms_cutime - s0.tms_cutime), (s.tms_cstime - s0.tms_cstime)); } t0 = t; s0 = s; } #else // __MACH__ static void logCPU(bool details) // system-wide CPU stats { if (details) { logprintf(Log::informational, LABEL_FMT F1_WIDTH "d", "CPU count:", get_num_cpus()); } // skipping system-wide CPU load on OS X (requires research) } static void logLocalCPU(bool details) // CPU stats for this process { struct rusage r_usage; if (getrusage(RUSAGE_SELF, &r_usage)) { logprintf(Log::error, "getrusage: %s", strerror(errno)); return; } double user_cpu_seconds = r_usage.ru_utime.tv_sec + (1E-6 * r_usage.ru_utime.tv_usec); double system_cpu_seconds = r_usage.ru_stime.tv_sec + (1E-6 * r_usage.ru_stime.tv_usec); if (details) { logprintf(Log::debug, LABEL_FMT F1_WIDTH ".6lf seconds", "User CPU", user_cpu_seconds); logprintf(Log::debug, LABEL_FMT F1_WIDTH ".6lf seconds", "System CPU", system_cpu_seconds); } static uint64_t t0 = 0; uint64_t t = getUptimeMicroseconds(); uint64_t elapsed = t - t0; t0 = t; Memstats::cpu_load_percent = 100.0 * ((user_cpu_seconds + system_cpu_seconds) / elapsed); Memstats::cpu_user_percent = 100.0 * (user_cpu_seconds / elapsed); Memstats::cpu_system_percent = 100.0 * (system_cpu_seconds / elapsed); logprintf(Log::informational, LABEL_FMT F1_WIDTH ".1lf%% of 1 core (%.6lf, %.6lf seconds user,system)", "CPU used:", Memstats::cpu_load_percent, user_cpu_seconds, system_cpu_seconds); } #endif // __MACH__ // END CPU load // ------------- static void logUptime(void) { constexpr const char* label = "Uptime:"; constexpr const char* fmt = LABEL_FMT F1_WIDTH ".1lf %s"; unsigned int s = getUptimeSeconds(); double d = s / 86400.0; if (d >= 0.5) { logprintf(Log::informational, fmt, label, "days", d); } else { double h = s / 3600.0; if (h >= 0.5) { logprintf(Log::informational, fmt, label, "hours", h); } else { double m = s / 60.0; logprintf(Log::informational, fmt, label, "minutes", m); } } } static void logStats(bool details) { logprintf(Log::informational, "------ Server load ------"); logMemoryInfo(details); logCPU(details); { namespace vd = Version::Data; logprintf(Log::informational, LABEL_FMT "%d.%d.%d %s", "------ This daemon ------", vd::major, vd::minor, vd::step, vd::timestamp); } logUptime(); logprintf(Log::informational, LABEL_FMT F1_WIDTH "u", "Connected clients:", ClientMap::Count()); logprintf(Log::informational, LABEL_FMT F1_WIDTH "u", "Subscribed offices:", OfficeSubscriptions::OfficeCount()); logprintf(Log::informational, LABEL_FMT F1_WIDTH "u", "Office subscriptions:", OfficeSubscriptions::SubscriptionCount()); logLocalMemoryInfo(details); logMallocInfo(details); logMallinfo(details); logLocalCPU(details); logprintf(Log::informational, "-------------------------"); } /* --------------------------------------------------------------------------- LogMemstats interface functions */ void LogMemstats::Timer(void) { time_t t = time(NULL); static time_t t0s = t - (log_summary_interval_seconds - first_log_delay_seconds); static time_t t0d = t - (log_details_interval_seconds - first_log_delay_seconds); if ((t - t0s) >= log_summary_interval_seconds) { t0s = t; bool details = false; if ((t - t0d) >= log_details_interval_seconds) { t0d = t; details = true; } logStats(details); } }