845 lines
27 KiB
C++
845 lines
27 KiB
C++
/*
|
|
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 <mach/task.h>
|
|
#include <mach/mach_init.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/resource.h>
|
|
#else
|
|
#include <sys/sysinfo.h>
|
|
#include <malloc.h>
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/times.h>
|
|
#include <chrono>
|
|
#include <string>
|
|
#include <cmath>
|
|
#include <boost/property_tree/ptree.hpp>
|
|
#include <boost/property_tree/xml_parser.hpp>
|
|
#include <boost/algorithm/string.hpp>
|
|
#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<microseconds>(steady_clock::now() - uptimeInit.t0).count();
|
|
}
|
|
static unsigned int getUptimeSeconds(void)
|
|
{
|
|
return static_cast<unsigned int>(getUptimeMicroseconds() / 1000000);
|
|
}
|
|
static Memstats::uint_t getHeapAllocated(void)
|
|
{
|
|
#ifndef __MACH__
|
|
struct mallinfo mi = mallinfo();
|
|
return static_cast<Memstats::uint_t>(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<int>(x));
|
|
}
|
|
else
|
|
{
|
|
int e = static_cast<int>(log(x) / log(k));
|
|
if ((e < 1) || (e > 6))
|
|
{
|
|
return "<ERROR>";
|
|
}
|
|
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<int>(x));
|
|
c = 0;
|
|
}
|
|
else
|
|
{
|
|
int e = static_cast<int>(log(x) / log(k));
|
|
if ((e < 1) || (e > 6))
|
|
{
|
|
c = 0;
|
|
return "<ERROR>";
|
|
}
|
|
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<double>(n) / static_cast<double>(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<unsigned int>(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("<xmlattr>");
|
|
std::string total = a.get<std::string>("total");
|
|
freed += strtoul(total.c_str(), NULL, 0);
|
|
}
|
|
}
|
|
}
|
|
else if (0 == e.first.compare("system"))
|
|
{
|
|
const ptree& a = e.second.get_child("<xmlattr>");
|
|
std::string type = a.get<std::string>("type");
|
|
if (0 == type.compare("current"))
|
|
{
|
|
std::string size = a.get<std::string>("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("<xmlattr>");
|
|
std::string type = a.get<std::string>("type");
|
|
if (0 == type.compare("current"))
|
|
{
|
|
std::string size = a.get<std::string>("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<Memstats::uint_t>(totalVirtualMem);
|
|
|
|
long long virtualMemUsed = memInfo.totalram - memInfo.freeram;
|
|
virtualMemUsed += memInfo.totalswap - memInfo.freeswap;
|
|
virtualMemUsed *= memInfo.mem_unit;
|
|
Memstats::system_virtual_memory_used = static_cast<Memstats::uint_t>(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<Memstats::uint_t>(totalRAM);
|
|
|
|
long long ramUsed = memInfo.totalram - memInfo.freeram;
|
|
ramUsed *= memInfo.mem_unit;
|
|
Memstats::system_memory_used = static_cast<Memstats::uint_t>(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<Memstats::uint_t>(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<Memstats::uint_t>(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<Memstats::uint_t>(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);
|
|
}
|
|
}
|
|
|