/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2014-2017 Phusion Holding B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Phusion Holding B.V. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef _PASSENGER_SYSTEM_METRICS_COLLECTOR_H_ #define _PASSENGER_SYSTEM_METRICS_COLLECTOR_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #include #include #include #endif #ifdef __APPLE__ #include #include #include #include #endif #ifdef __FreeBSD__ #include #include #include #include #endif #include #include #include #include #include #include #include #include #include /* * Useful resources * * OS X: * http://www.opensource.apple.com/source/system_cmds/system_cmds-496/iostat.tproj/iostat.c * https://github.com/max-horvath/htop-osx * https://github.com/malkia/busybox-osx/blob/master/procps/iostat.c * * Linux: * http://procps.cvs.sourceforge.net/viewvc/procps/procps/ * https://github.com/sysstat/sysstat/blob/master/mpstat.c * http://www.thomas-krenn.com/en/wiki/Linux_Performance_Measurements_using_vmstat * http://man7.org/linux/man-pages/man5/proc.5.html * * FreeBSD: * https://github.com/freebsd/freebsd/blob/master/usr.bin/vmstat/vmstat.c * https://github.com/freebsd/freebsd/blob/master/sbin/swapon/swapon.c * http://stuff.mit.edu/afs/sipb/project/freebsd/head/contrib/top/machine.h */ namespace Passenger { using namespace boost; using namespace std; class SystemMetricsCollector; /** All memory sizes are in KB. */ class SystemMetrics { public: struct DescriptionOptions { bool general; bool cpu; bool memory; bool colors; DescriptionOptions() : general(true), cpu(true), memory(true), colors(false) { } }; struct XmlOptions { bool general; bool cpu; bool memory; XmlOptions() : general(true), cpu(true), memory(true) { } }; private: friend class SystemMetricsCollector; #ifdef __linux__ SpeedMeter forkRateSpeedMeter; SpeedMeter swapInSpeedMeter, swapOutSpeedMeter; #endif double divideTotalCpuUsageByNCpus(double total) const { if (ncpus() == 0) { return -1; } else { total /= ncpus(); if (total > 100) { return 100; } else { return total; } } } void outputHeader(ostream &stream, const DescriptionOptions &options, const char *label) const { if (options.colors) { stream << ANSI_COLOR_BLUE_BG ANSI_COLOR_BOLD ANSI_COLOR_YELLOW; } stream << "------------- " << label << " -------------"; if (options.colors) { stream << ANSI_COLOR_RESET; } stream << endl; } string formatWidth(const string &str, int width) const { char buf[128]; snprintf(buf, sizeof(buf), "%*s", width, str.c_str()); return buf; } string maybeColorAfterThreshold(const DescriptionOptions &options, const string &str, double value, double threshold) const { if (value >= threshold && options.colors) { return string(ANSI_COLOR_BOLD) + str + ANSI_COLOR_RESET; } else { return str; } } string formatPercent(const DescriptionOptions &options, double percent, int precision, int width, double threshold) const { string result; if (percent == -2) { if (options.colors) { return string(ANSI_COLOR_DGRAY) + formatWidth("unsupported by OS", width) + ANSI_COLOR_RESET; } else { return formatWidth("unsupported by OS", width); } } else if (percent == -1) { if (options.colors) { return string(ANSI_COLOR_RED) + formatWidth("?", width) + ANSI_COLOR_RESET; } else { return formatWidth("?", width); } } else { char buf[64]; snprintf(buf, sizeof(buf), "%.*f%%", precision, percent); return maybeColorAfterThreshold(options, formatWidth(buf, width), percent, threshold); } } string formatPercent0(const DescriptionOptions &options, double percent, int width = -1, double threshold = numeric_limits::infinity()) const { return formatPercent(options, percent, 0, width, threshold); } string formatPercent2(const DescriptionOptions &options, double percent, int width = -1, double threshold = -10) const { return formatPercent(options, percent, 2, width, threshold); } string kbToMb(ssize_t size) const { if (size < 0) { return "?"; } else { char buf[32]; snprintf(buf, sizeof(buf), "%lld", (long long) size / 1024); return buf; } } public: class CpuUsage { private: friend class SystemMetricsCollector; unsigned long long lastUserTicks; unsigned long long lastNiceTicks; unsigned long long lastSystemTicks; unsigned long long lastIoWaitTicks; unsigned long long lastIdleTicks; unsigned long long lastStealTicks; /** Current usage statistics for this CPU. * * userUsage, niceUsage, systemUsage and idleUsage are fractions * of user + nice + system + idle. * * ioWaitUsage is a fraction of user + nice + system + idle + iowait. * * stealUsage is a fraction of user + nice + system + idle + steal. * * All fractions range from 0 (unutilized) to SHRT_MAX (fully utilized). * Use the *Pct() methods to convert them to percentages. * * Each statistic can individually be -1 if an error occurred while querying * it, or -2 if the OS doesn't support it. */ short userUsage, niceUsage, systemUsage; short ioWaitUsage, idleUsage, stealUsage; double fracToPercentage(short usage) const { if (usage < 0) { return usage; } else { return (double) usage / SHRT_MAX * 100.0; } } public: CpuUsage() : lastUserTicks(0), lastNiceTicks(0), lastSystemTicks(0), lastIoWaitTicks(0), lastIdleTicks(0), lastStealTicks(0), userUsage(-1), niceUsage(-1), systemUsage(-1), ioWaitUsage(-1), idleUsage(-1), stealUsage(-1) { } /** These methods return the usage statistics as percentages (0..100) */ double userPct() const { return fracToPercentage(userUsage); } double nicePct() const { return fracToPercentage(niceUsage); } double systemPct() const { return fracToPercentage(systemUsage); } double ioWaitPct() const { return fracToPercentage(ioWaitUsage); } double idlePct() const { return fracToPercentage(idleUsage); } double stealPct() const { return fracToPercentage(stealUsage); } /** Returns this CPU's usage as a percentage (0..100), * or -1 if it cannot be determined. */ double usage() const { if (userUsage < 0 || niceUsage < 0 || systemUsage < 0) { return -1; } else { return userPct() + nicePct() + systemPct(); } } }; /** Per-core CPU usage. This collection is empty if the number of cores * cannot be queried. */ vector cpuUsages; /** Total system physical RAM. -1 if this information cannot be queried. */ ssize_t ramTotal; /** Amount of RAM used. Does not include kernel caches and buffers. -1 * if this information cannot be queried. */ ssize_t ramUsed; /** Total system swap space, or -1 if this information cannot be queried. */ ssize_t swapTotal; /** Amount of swap space used, or -1 if this information cannot be queried. */ ssize_t swapUsed; /** Load averages for the past 1, 5 and 15 minutes. Can each individually be -1 * if that particular statistic cannot be queried. */ double loadAverage1; double loadAverage5; double loadAverage15; /** Time at which the system booted. -1 if this information cannot be queried. */ time_t boottime; /** Speed at which processes are created per second. * SpeedMeter::unknownSpeed() if it's not yet known (because too * few samples have been taken so far). * -1 if there was an error querying this information. * -2 if the OS does not support this metric. */ double forkRate; /** Speed at which the OS swaps in and swaps out data, in KB/sec. * SpeedMeter::unknownSpeed() if it's not yet known * (because too few samples have been taken so far). * -1 if there was an error querying this information. * -2 if the OS does not support this metric. */ double swapInRate, swapOutRate; /** Kernel version number, or the empty string if this information cannot be queried. */ string kernelVersion; SystemMetrics() : #ifdef __linux__ forkRateSpeedMeter(), swapInSpeedMeter(), swapOutSpeedMeter(), #endif ramTotal(-1), ramUsed(-1), swapTotal(-1), swapUsed(-1), loadAverage1(-1), loadAverage5(-1), loadAverage15(-1), boottime(-1), forkRate(-2), swapInRate(-2), swapOutRate(-2) { } unsigned int ncpus() const { return cpuUsages.size(); } /** The following methods calculate the current average system CPU usage * statistics. Ranges from 0 (no cores are being used) to 100 (all cores * at full utilization). Return -1 if the information cannot be queried. */ double avgUserCpuUsage() const { BOOST_AUTO(it, cpuUsages.begin()); BOOST_AUTO(end, cpuUsages.end()); double total = 0; for (; it != end; it++) { double val = it->userPct(); if (val < 0) { return val; } else { total += val; } } return divideTotalCpuUsageByNCpus(total); } double avgNiceCpuUsage() const { BOOST_AUTO(it, cpuUsages.begin()); BOOST_AUTO(end, cpuUsages.end()); double total = 0; for (; it != end; it++) { double val = it->nicePct(); if (val < 0) { return val; } else { total += val; } } return divideTotalCpuUsageByNCpus(total); } double avgSystemCpuUsage() const { BOOST_AUTO(it, cpuUsages.begin()); BOOST_AUTO(end, cpuUsages.end()); double total = 0; for (; it != end; it++) { double val = it->systemPct(); if (val < 0) { return val; } else { total += val; } } return divideTotalCpuUsageByNCpus(total); } double avgIoWaitCpuUsage() const { BOOST_AUTO(it, cpuUsages.begin()); BOOST_AUTO(end, cpuUsages.end()); double total = 0; for (; it != end; it++) { double val = it->ioWaitPct(); if (val < 0) { return val; } else { total += val; } } return divideTotalCpuUsageByNCpus(total); } double avgIdleCpuUsage() const { BOOST_AUTO(it, cpuUsages.begin()); BOOST_AUTO(end, cpuUsages.end()); double total = 0; for (; it != end; it++) { double val = it->idlePct(); if (val < 0) { return val; } else { total += val; } } return divideTotalCpuUsageByNCpus(total); } double avgStealCpuUsage() const { BOOST_AUTO(it, cpuUsages.begin()); BOOST_AUTO(end, cpuUsages.end()); double total = 0; for (; it != end; it++) { double val = it->stealPct(); if (val < 0) { return val; } else { total += val; } } return divideTotalCpuUsageByNCpus(total); } double avgCpuUsage() const { BOOST_AUTO(it, cpuUsages.begin()); BOOST_AUTO(end, cpuUsages.end()); double total = 0; for (; it != end; it++) { double val = it->usage(); if (val < 0) { return val; } else { total += val; } } return divideTotalCpuUsageByNCpus(total); } ssize_t ramFree() const { if (ramTotal == -1 || ramUsed == -1) { return -1; } else { return ramTotal - ramUsed; } } ssize_t swapFree() const { if (swapTotal == -1 || swapUsed == -1) { return -1; } else { return swapTotal - swapUsed; } } void toDescription(ostream &stream, const DescriptionOptions &options = DescriptionOptions()) const { char buf[1024]; stream << std::right << std::setfill(' '); if (options.general) { outputHeader(stream, options, "General"); stream << "Kernel version : " << kernelVersion << endl; stream << "Uptime : " << distanceOfTimeInWords(boottime) << endl; stream << "Load averages : "; stream << formatPercent2(options, loadAverage1, 5, 2); stream << ", "; stream << formatPercent2(options, loadAverage5, 5, 2); stream << ", "; stream << formatPercent2(options, loadAverage15, 5, 2); stream << endl; if (forkRate != -2) { stream << "Fork rate : "; if (forkRate == SpeedMeter::unknownSpeed() || forkRate < 0) { if (options.colors) { stream << ANSI_COLOR_DGRAY; } stream << "unknown"; if (options.colors) { stream << ANSI_COLOR_RESET; } } else { char buf[32]; snprintf(buf, sizeof(buf), "%.1f", forkRate); stream << buf << "/sec"; } stream << endl; } stream << endl; } if (options.cpu) { double tmp; outputHeader(stream, options, "CPU"); if (ncpus() == 0) { stream << "Number of CPUs : unknown" << endl; } else { snprintf(buf, sizeof(buf), "%4u", ncpus()); stream << "Number of CPUs : " << buf << endl; stream << "Average CPU usage : "; stream << formatPercent0(options, avgCpuUsage(), 4, 95); stream << " -- "; stream << formatPercent0(options, avgUserCpuUsage(), 4, 95); stream << " user, "; stream << formatPercent0(options, avgNiceCpuUsage(), 4, 95); stream << " nice, "; stream << formatPercent0(options, avgSystemCpuUsage(), 4, 95); stream << " system, "; stream << formatPercent0(options, avgIdleCpuUsage(), 4); stream << " idle" << endl; } for (unsigned i = 0; i < ncpus(); i++) { snprintf(buf, sizeof(buf), " CPU %-2u : ", i + 1); stream << buf; stream << formatPercent0(options, cpuUsages[i].usage(), 4, 95); stream << " -- "; stream << formatPercent0(options, cpuUsages[i].userPct(), 4, 95); stream << " user, "; stream << formatPercent0(options, cpuUsages[i].nicePct(), 4, 95); stream << " nice, "; stream << formatPercent0(options, cpuUsages[i].systemPct(), 4, 95); stream << " system, "; stream << formatPercent0(options, cpuUsages[i].idlePct(), 4); stream << " idle" << endl; } // For the two average metrics below, if a metric is unsupported by the OS (-2) // then that implies that it's unsupported for all individual CPUs, so we // don't bother printing CPU-specific metrics. // But if an average metric is merely errored (-1), then it's still // possible that we succeeded in querying the metric for a specific CPU. tmp = avgIoWaitCpuUsage(); if (tmp != -2) { stream << "I/O pressure : "; stream << formatPercent0(options, avgIoWaitCpuUsage(), 4, 95); stream << endl; for (unsigned i = 0; i < ncpus(); i++) { snprintf(buf, sizeof(buf), " CPU %-2u : %s", i + 1, formatPercent0(options, cpuUsages[i].ioWaitPct(), 4, 95).c_str()); stream << buf << endl; } } tmp = avgStealCpuUsage(); if (tmp != -2) { stream << "Interference from other VMs: "; stream << formatPercent0(options, tmp, 4, 20); stream << endl; for (unsigned i = 0; i < ncpus(); i++) { snprintf(buf, sizeof(buf), " CPU %-2u : %s", i + 1, formatPercent0(options, cpuUsages[i].stealPct(), 4, 35).c_str()); stream << buf << endl; } } stream << endl; } if (options.memory) { double ramUsedPct = ramUsed / (double) ramTotal * 100; double swapUsedPct = swapUsed / (double) swapTotal * 100; outputHeader(stream, options, "Memory"); stream << "RAM total : " << formatWidth(kbToMb(ramTotal), 6) << " MB" << endl; stream << "RAM used : " << formatWidth(kbToMb(ramUsed), 6) << " MB (" << formatPercent0(options, ramUsedPct, 1, 90) << ")" << endl; stream << "RAM free : " << formatWidth(kbToMb(ramFree()), 6) << " MB" << endl; stream << "Swap total : " << formatWidth(kbToMb(swapTotal), 6) << " MB" << endl; stream << "Swap used : " << formatWidth(kbToMb(swapUsed), 6) << " MB (" << formatPercent0(options, swapUsedPct, 1, 90) << ")" << endl; stream << "Swap free : " << formatWidth(kbToMb(swapFree()), 6) << " MB" << endl; if (swapInRate != -2) { stream << "Swap in : "; if (swapInRate == SpeedMeter::unknownSpeed() || swapInRate < 0) { if (options.colors) { stream << ANSI_COLOR_DGRAY; } stream << "unknown"; if (options.colors) { stream << ANSI_COLOR_RESET; } } else { char buf[32]; snprintf(buf, sizeof(buf), "%.1f", swapInRate / 1024); stream << maybeColorAfterThreshold(options, buf, swapInRate / 1024, 2); stream << " MB/sec"; } stream << endl; } if (swapOutRate != -2) { stream << "Swap out : "; if (swapOutRate == SpeedMeter::unknownSpeed() || swapOutRate < 0) { if (options.colors) { stream << ANSI_COLOR_DGRAY; } stream << "unknown"; if (options.colors) { stream << ANSI_COLOR_RESET; } } else { char buf[32]; snprintf(buf, sizeof(buf), "%.1f", swapOutRate / 1024); stream << maybeColorAfterThreshold(options, buf, swapOutRate / 1024, 2); stream << " MB/sec"; } stream << endl; } stream << endl; } } void toXml(ostream &stream, const XmlOptions &options = XmlOptions()) const { time_t timestamp = SystemTime::get(); stream << std::fixed << std::setprecision(2); stream << ""; if (options.general) { stream << ""; stream << ""; stream << "" << strip(std::ctime(×tamp)) << ""; stream << "" << timestamp << ""; stream << ""; stream << "" PASSENGER_VERSION ""; stream << "" << kernelVersion << ""; stream << ""; stream << "" << strip(std::ctime(&boottime)) << ""; stream << "" << boottime << ""; stream << ""; stream << ""; stream << "" << timestamp - boottime << ""; stream << "" << distanceOfTimeInWords(boottime) << ""; stream << ""; stream << ""; stream << "" << loadAverage1 << ""; stream << "" << loadAverage5 << ""; stream << "" << loadAverage15 << ""; stream << ""; stream << "" << forkRate << ""; stream << ""; } if (options.cpu) { stream << ""; stream << "" << (int) ncpus() << ""; if (ncpus() != 0) { stream << ""; stream << "" << avgCpuUsage() << ""; stream << "" << avgUserCpuUsage() << ""; stream << "" << avgNiceCpuUsage() << ""; stream << "" << avgSystemCpuUsage() << ""; stream << "" << avgIoWaitCpuUsage() << ""; stream << "" << avgIdleCpuUsage() << ""; stream << "" << avgStealCpuUsage() << ""; stream << ""; } stream << ""; for (unsigned i = 0; i < ncpus(); i++) { const CpuUsage &cpuUsage = cpuUsages[i]; stream << ""; stream << "" << i + 1 << ""; stream << "" << cpuUsage.usage() << ""; stream << "" << cpuUsage.userPct() << ""; stream << "" << cpuUsage.nicePct() << ""; stream << "" << cpuUsage.systemPct() << ""; stream << "" << cpuUsage.ioWaitPct() << ""; stream << "" << cpuUsage.idlePct() << ""; stream << "" << cpuUsage.stealPct() << ""; stream << ""; } stream << ""; stream << ""; } if (options.memory) { stream << ""; stream << "" << ramTotal << ""; stream << "" << ramUsed << ""; stream << "" << ramFree() << ""; stream << "" << swapTotal << ""; stream << "" << swapUsed << ""; stream << "" << swapFree() << ""; stream << "" << swapInRate << ""; stream << "" << swapOutRate << ""; stream << ""; } stream << ""; } }; /** * Utility class for collection system metrics, such as system CPU usage, * amount of memory available and free, etc. * * SystemMetrics metrics; * SystemMetricsCollector collector; * * collector.collect(metrics); // => metrics are now available * sleep(1); * collector.collect(metrics); // => metrics have been updated * * Note that to measure the CPU usage, you must collect metrics at least * twice, using the same metrics object, within a time interval that's * longer than 10 ms. That's because on most systems, the CPU usage is * measured by comparing the number of CPU ticks that have passed at the * beginning and end of a time interval. The metrics object remembers the * number of CPU ticks that was queried last time. */ class SystemMetricsCollector { private: #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) int pageSize; void queryLoadAvg(SystemMetrics &metrics) const { double avg[3]; int ret; ret = getloadavg(avg, 3); if (ret >= 1) { metrics.loadAverage1 = avg[0]; } if (ret >= 2) { metrics.loadAverage5 = avg[1]; } if (ret >= 3) { metrics.loadAverage15 = avg[2]; } } void failReadingCpuUsages(SystemMetrics &metrics) const { vector::iterator it, end = metrics.cpuUsages.end(); for (it = metrics.cpuUsages.begin(); it != end; it++) { it->userUsage = -1; it->niceUsage = -1; it->systemUsage = -1; it->idleUsage = -1; #if defined(__linux__) it->ioWaitUsage = -1; it->stealUsage = -1; #else it->ioWaitUsage = -2; it->stealUsage = -2; #endif } } short fracToShort(double x) const { return (short) (x * SHRT_MAX); } void updateCpuMetrics(SystemMetrics::CpuUsage &cpuUsage, long long user, long long nice, long long sys, long long iowait, long long idle, long long steal) const { unsigned long long userDiff, niceDiff, systemDiff, idleDiff, ioWaitDiff = 0, stealDiff = 0; double totalCalculationTicks, totalTicks; userDiff = (unsigned long long) user - cpuUsage.lastUserTicks; niceDiff = (unsigned long long) nice - cpuUsage.lastNiceTicks; systemDiff = (unsigned long long) sys - cpuUsage.lastSystemTicks; if (iowait >= 0) { ioWaitDiff = (unsigned long long) iowait - cpuUsage.lastIoWaitTicks; } idleDiff = (unsigned long long) idle - cpuUsage.lastIdleTicks; if (steal >= 0) { stealDiff = (unsigned long long) steal - cpuUsage.lastStealTicks; } totalCalculationTicks = userDiff; totalCalculationTicks += niceDiff; totalCalculationTicks += systemDiff; totalCalculationTicks += idleDiff; if (totalCalculationTicks == 0) { // If the CPU didn't tick, treat it as 100% idle. cpuUsage.userUsage = 0; cpuUsage.niceUsage = 0; cpuUsage.systemUsage = 0; cpuUsage.idleUsage = fracToShort(1); } else { cpuUsage.userUsage = fracToShort(userDiff / totalCalculationTicks); cpuUsage.niceUsage = fracToShort(niceDiff / totalCalculationTicks); cpuUsage.systemUsage = fracToShort(systemDiff / totalCalculationTicks); cpuUsage.idleUsage = fracToShort(idleDiff / totalCalculationTicks); } if (iowait >= 0) { totalTicks = totalCalculationTicks + ioWaitDiff; if (totalTicks == 0) { cpuUsage.ioWaitUsage = 0; } else { cpuUsage.ioWaitUsage = fracToShort(ioWaitDiff / totalTicks); } } else { cpuUsage.ioWaitUsage = iowait; // Assign error code. } if (steal >= 0) { totalTicks = totalCalculationTicks + stealDiff; if (totalTicks == 0) { cpuUsage.stealUsage = 0; } else { cpuUsage.stealUsage = fracToShort(stealDiff / totalTicks); } } else { cpuUsage.stealUsage = steal; // Assign error code. } cpuUsage.lastUserTicks = user; cpuUsage.lastNiceTicks = nice; cpuUsage.lastSystemTicks = sys; if (iowait >= 0) { cpuUsage.lastIoWaitTicks = iowait; } cpuUsage.lastIdleTicks = idle; if (steal >= 0) { cpuUsage.lastStealTicks = steal; } } #endif #ifdef __linux__ void readNextWordAndAssertEqual(const char **data, const StaticString &expected) const { if (readNextWord(data) != expected) { throw ParseException(); } } void queryMemInfo(SystemMetrics &metrics) const { string contents; bool hasContents = false; try { contents = unsafeReadFile("/proc/meminfo"); hasContents = true; } catch (const SystemException &) { } if (hasContents) { try { parseMemInfo(metrics, contents); } catch (const ParseException &) { throw RuntimeException("Cannot parse information in /proc/meminfo"); } } else { metrics.ramTotal = metrics.ramUsed = -1; metrics.swapTotal = metrics.swapUsed = -1; } } void parseMemInfo(SystemMetrics &metrics, const string &data) const { const char *start = data.c_str(); long long memTotal = -1, memFree = -1, buffers = -1, cached = -1; long long swapTotal = -1, swapFree = -1; while (start != NULL) { StaticString name = readNextWord(&start); long long value = readNextWordAsLongLong(&start); if (!skipToNextLine(&start) || *start == '\0') { start = NULL; } if (name == "MemTotal:") { memTotal = value; } else if (name == "MemFree:") { memFree = value; } else if (name == "Buffers:") { buffers = value; } else if (name == "Cached:") { cached = value; } else if (name == "SwapTotal:") { swapTotal = value; } else if (name == "SwapFree:") { swapFree = value; } } if (memTotal != -1) { metrics.ramTotal = memTotal; if (memFree != -1) { metrics.ramUsed = memTotal - memFree; if (buffers != -1) { metrics.ramUsed -= buffers; } if (cached != -1) { metrics.ramUsed -= cached; } } else { metrics.ramUsed = -1; } } else { metrics.ramTotal = metrics.ramUsed = -1; } if (swapTotal != -1) { metrics.swapTotal = swapTotal; if (swapFree != -1) { metrics.swapUsed = swapTotal - swapFree; } else { metrics.swapUsed = -1; } } else { metrics.swapTotal = metrics.swapUsed = -1; } } void queryProcStat(SystemMetrics &metrics) const { string contents; bool hasContents = false; try { contents = unsafeReadFile("/proc/stat"); hasContents = true; } catch (const SystemException &) { } if (hasContents) { try { parseProcStat(metrics, contents); } catch (const ParseException &) { throw RuntimeException("Cannot parse information in /proc/stat"); } } else { failReadingCpuUsages(metrics); } } void parseProcStat(SystemMetrics &metrics, const string &data) const { const char *start = data.c_str(); unsigned long long forkCount = 0; while (start != NULL) { if (*start == '\n') { // Empty line. Skip to next line. start++; continue; } StaticString name = readNextWord(&start); if (name.size() > 3 && startsWith(name, "cpu")) { const char *numStart = name.data() + 3; unsigned long num = strtoul(numStart, NULL, 10); long long user = readNextWordAsLongLong(&start); long long nice = readNextWordAsLongLong(&start); long long sys = readNextWordAsLongLong(&start); long long idle = readNextWordAsLongLong(&start); long long iowait = readNextWordAsLongLong(&start); readNextWordAsLongLong(&start); // irq readNextWordAsLongLong(&start); // softirq long long steal; try { steal = readNextWordAsLongLong(&start); } catch (const ParseException &) { // Not supported on Linux < 2.6.11 steal = -2; } if (num + 1 > metrics.cpuUsages.size()) { metrics.cpuUsages.resize(num + 1); } updateCpuMetrics( metrics.cpuUsages[num], user, nice, sys, iowait, idle, steal); } else if (name == "processes") { forkCount = (long long) readNextWordAsLongLong(&start); } if (!skipToNextLine(&start) || *start == '\0') { start = NULL; } } if (forkCount == 0) { metrics.forkRate = -1; } else { metrics.forkRateSpeedMeter.addSample(forkCount); metrics.forkRate = metrics.forkRateSpeedMeter.currentSpeed(); } } void queryProcVmstat(SystemMetrics &metrics) const { string contents; bool hasContents = false; try { contents = unsafeReadFile("/proc/vmstat"); hasContents = true; } catch (const SystemException &) { } if (hasContents) { try { parseProcVmstat(metrics, contents); } catch (const ParseException &) { throw RuntimeException("Cannot parse information in /proc/vmstat"); } } else { failReadingCpuUsages(metrics); } } void parseProcVmstat(SystemMetrics &metrics, const string &data) const { const char *start = data.c_str(); long long pswpin = -1, pswpout = -1; while (start != NULL) { StaticString name = readNextWord(&start); long long value = readNextWordAsLongLong(&start); if (!skipToNextLine(&start) || *start == '\0') { start = NULL; } if (name == "pswpin") { pswpin = value; } else if (name == "pswpout") { pswpout = value; } } if (pswpin == -1 || pswpout == -1) { metrics.swapInRate = -1; metrics.swapOutRate = -1; } else { metrics.swapInSpeedMeter.addSample(pswpin * pageSize / 1024); metrics.swapOutSpeedMeter.addSample(pswpout * pageSize / 1024); metrics.swapInRate = metrics.swapInSpeedMeter.currentSpeed(); metrics.swapOutRate = metrics.swapOutSpeedMeter.currentSpeed(); } } void queryBoottimeFromSysinfo(SystemMetrics &metrics) const { if (metrics.boottime == -1) { struct sysinfo info; if (sysinfo(&info) != -1) { metrics.boottime = (long long) SystemTime::get() - (long long) info.uptime; } } } #endif #ifdef __APPLE__ mach_port_t hostPort; void collectOSX(SystemMetrics &metrics) const { kern_return_t status; mach_msg_type_number_t count; host_basic_info_data_t hostInfo; vm_statistics64_data_t vmStat; struct xsw_usage swap; size_t bufSize = sizeof(swap); int mib[2] = { CTL_VM, VM_SWAPUSAGE }; unsigned int cpuCount; processor_cpu_load_info_t cpuLoads; // Query total RAM. count = HOST_BASIC_INFO_COUNT; status = host_info(hostPort, HOST_BASIC_INFO, (host_info_t) &hostInfo, &count); if (status == KERN_SUCCESS) { metrics.ramTotal = hostInfo.max_mem / 1024; } else { metrics.ramTotal = -1; } // Query system memory usage. // We regard memory usage as the sum of active, wired and compressed memory. // Active + wired is shown as "App memory" in Activity Monitor. count = HOST_VM_INFO64_COUNT; status = host_statistics64(hostPort, HOST_VM_INFO64, (host_info64_t) &vmStat, &count); if (status == KERN_SUCCESS) { metrics.ramUsed = ((ssize_t) vmStat.active_count + vmStat.wire_count); #if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9 metrics.ramUsed += vmStat.compressor_page_count; #endif metrics.ramUsed = metrics.ramUsed * (pageSize / 1024); } else { metrics.ramUsed = -1; } // Query swap. if (sysctl(mib, 2, &swap, &bufSize, NULL, 0) == 0) { metrics.swapTotal = swap.xsu_total / 1024; metrics.swapUsed = swap.xsu_used / 1024; } else { metrics.swapTotal = metrics.swapUsed = -1; } // Query CPU usages. status = host_processor_info(hostPort, PROCESSOR_CPU_LOAD_INFO, &cpuCount, (processor_info_array_t *) &cpuLoads, &count); if (status == KERN_SUCCESS) { if ((unsigned int) metrics.cpuUsages.size() != cpuCount) { metrics.cpuUsages.resize(cpuCount); } for (unsigned int i = 0; i < cpuCount; i++) { unsigned int *cpuTicks = cpuLoads[i].cpu_ticks; updateCpuMetrics( metrics.cpuUsages[i], cpuTicks[CPU_STATE_USER], cpuTicks[CPU_STATE_NICE], cpuTicks[CPU_STATE_SYSTEM], -2, /* OS X does not support iowait */ cpuTicks[CPU_STATE_IDLE], -2 /* OS X does not support steal */); } } else { failReadingCpuUsages(metrics); } } #endif #ifdef __FreeBSD__ int kern_smp_maxcpus[3]; int kern_cp_times[2]; int vm_active_count[4]; int vm_wire_count[4]; template bool querySysctl(int mib1, int mib2, IntegerType &result) const { int mib[2] = { mib1, mib2 }; IntegerType val; size_t len = sizeof(IntegerType); if (sysctl(mib, 2, &val, &len, NULL, 0) == 0) { if (len == sizeof(IntegerType)) { result = val; return true; } else { return false; } } else { return false; } } template bool querySysctlMib(int mib[], size_t mibsize, IntegerType &result) const { IntegerType val; size_t len = sizeof(IntegerType); if (mib[0] == -1) { return false; } else if (sysctl(mib, mibsize / sizeof(int), &val, &len, NULL, 0) == 0) { if (len == sizeof(IntegerType)) { result = val; return true; } else { return false; } } else { return false; } } void collectFreeBSD(SystemMetrics &metrics) const { size_t len; size_t val_size_t; unsigned int val1_uint, val2_uint; int vm_active_count[sizeof(this->vm_active_count) / sizeof(int)]; int vm_wire_count[sizeof(this->vm_wire_count) / sizeof(int)]; // Query active CPU count. if (!queryCpuUsage(metrics)) { failReadingCpuUsages(metrics); } // Query memory. memcpy(vm_active_count, this->vm_active_count, sizeof(vm_active_count)); memcpy(vm_wire_count, this->vm_wire_count, sizeof(vm_wire_count)); if (metrics.ramTotal < 0) { if (querySysctl(CTL_HW, HW_PHYSMEM, val_size_t)) { metrics.ramTotal = val_size_t / 1024; } else { metrics.ramTotal = -1; } } if (metrics.ramTotal >= 0 && querySysctlMib(vm_active_count, sizeof(vm_active_count), val1_uint) && querySysctlMib(vm_wire_count, sizeof(vm_wire_count), val2_uint)) { metrics.ramUsed = ((long long) val1_uint + val2_uint) * pageSize / 1024; } else { metrics.ramUsed = -1; } // Query swap. int mib[17]; size_t mibsize = 16; if (sysctlnametomib("vm.swap_info", mib, &mibsize) == 0) { long long total = 0, used = 0; for (int n = 0; ; n++) { struct xswdev xsw; mib[mibsize] = n; len = sizeof(xsw); if (sysctl(mib, mibsize + 1, &xsw, &len, NULL, 0) == -1) { break; } if (xsw.xsw_version != XSWDEV_VERSION) { metrics.swapTotal = -1; metrics.swapUsed = -1; break; } total += (long long) xsw.xsw_nblks * pageSize; used += (long long) xsw.xsw_used * pageSize; } metrics.swapTotal = total / 1024; metrics.swapUsed = used / 1024; } else { metrics.swapTotal = -1; metrics.swapUsed = -1; } } bool cpuStatesAreEmpty(const long *states) const { for (int i = 0; i < CPUSTATES; i++) { if (states[i] != 0) { return false; } } return true; } bool queryCpuUsage(SystemMetrics &metrics) const { int kern_smp_maxcpus[sizeof(this->kern_smp_maxcpus) / sizeof(int)]; int kern_cp_times[sizeof(this->kern_cp_times) / sizeof(int)]; long *times = NULL; int maxcpus; size_t size; unsigned int i, j; // Preparation. memcpy(kern_smp_maxcpus, this->kern_smp_maxcpus, sizeof(kern_smp_maxcpus)); memcpy(kern_cp_times, this->kern_cp_times, sizeof(kern_cp_times)); if (kern_smp_maxcpus[0] == -1 || kern_cp_times[0] == -1) { goto error; } // Query maximum number of supported CPUs. if (!querySysctlMib(kern_smp_maxcpus, sizeof(kern_smp_maxcpus), maxcpus)) { goto error; } // Query CPU times. size = sizeof(long) * maxcpus * CPUSTATES; times = (long *) malloc(size); if (times == NULL) { goto error; } if (sysctl(kern_cp_times, sizeof(kern_cp_times) / sizeof(int), times, &size, NULL, 0) == -1) { goto error; } i = j = 0; while (i < size / CPUSTATES / sizeof(long)) { if (!cpuStatesAreEmpty(×[i * CPUSTATES])) { if (metrics.cpuUsages.size() < j + 1) { metrics.cpuUsages.resize(j + 1); } updateCpuMetrics( metrics.cpuUsages[j], times[i * CPUSTATES + CP_USER], times[i * CPUSTATES + CP_NICE], times[i * CPUSTATES + CP_SYS], -2, /* FreeBSD does not support iowait */ times[i * CPUSTATES + CP_IDLE], -2 /* FreeBSD does not support steal */); j++; } i++; } if (metrics.cpuUsages.size() != j) { metrics.cpuUsages.resize(j); } return true; error: if (times != NULL) { free(times); } return false; } #endif #if defined(__APPLE__) || defined(__FreeBSD__) void queryBoottimeFromSysctl(SystemMetrics &metrics) const { if (metrics.boottime == -1) { struct timeval boottime; size_t len = sizeof(boottime); int mib[2] = { CTL_KERN, KERN_BOOTTIME }; if (sysctl(mib, 2, &boottime, &len, NULL, 0) == 0) { metrics.boottime = boottime.tv_sec; } else { metrics.boottime = -1; } } } #endif void queryOsRelease(SystemMetrics &metrics) const { struct utsname name; if (metrics.kernelVersion.empty() && uname(&name) == 0) { metrics.kernelVersion = name.release; } } public: SystemMetricsCollector() { #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) pageSize = getpagesize(); #endif #if defined(__APPLE__) hostPort = mach_host_self(); #endif #if defined(__FreeBSD__) size_t len; len = sizeof(kern_smp_maxcpus) / sizeof(int); if (sysctlnametomib("kern.smp.maxcpus", kern_smp_maxcpus, &len) == -1) { kern_smp_maxcpus[0] = -1; } len = sizeof(kern_cp_times) / sizeof(int); if (sysctlnametomib("kern.cp_times", kern_cp_times, &len) == -1) { kern_cp_times[0] = -1; } len = sizeof(vm_active_count) / sizeof(int); if (sysctlnametomib("vm.stats.vm.v_active_count", vm_active_count, &len) == -1) { vm_active_count[0] = -1; } len = sizeof(vm_wire_count) / sizeof(int); if (sysctlnametomib("vm.stats.vm.v_wire_count", vm_wire_count, &len) == -1) { vm_wire_count[0] = -1; } #endif } /** * If some information cannot be queried, then this method does not * throw an exception. Instead, that particular metric in the metrics * object is just not updated. However if something really unexpected * goes wrong (such as when a command did not return the output it's * supposed to return, so that we're unable to parse the output) then * a RuntimeException is thrown. * * @throws RuntimeException */ void collect(SystemMetrics &metrics) const { #if defined(__linux__) queryMemInfo(metrics); queryProcStat(metrics); queryProcVmstat(metrics); queryBoottimeFromSysinfo(metrics); queryLoadAvg(metrics); #elif defined(__APPLE__) collectOSX(metrics); queryBoottimeFromSysctl(metrics); queryLoadAvg(metrics); #elif defined(__FreeBSD__) collectFreeBSD(metrics); queryBoottimeFromSysctl(metrics); queryLoadAvg(metrics); #endif queryOsRelease(metrics); } }; } // namespace Passenger #endif /* _PASSENGER_SYSTEM_METRICS_COLLECTOR_H_ */