#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" namespace fs = std::filesystem; #define VA_STR(x) dynamic_cast(std::ostringstream().flush() << x).str() auto constexpr MB_i = 1000000LL; auto constexpr MB_f = 1000000.0; auto constexpr GB_i = 1000000000LL; auto constexpr GB_f = 1000000000.0; auto constexpr TB_i = 1000000000000LL; auto constexpr TB_f = 1000000000000.0; // only C++14 has a readable alternative static std::string stateFilePath; void abortApp(const char* reason) { std::cout << "ERROR " << errno << ":\n" << (reason? reason : "") << ":" << strerror(errno) << std::endl; exit(2); } std::string ENV(const char *name) { if (char *val = std::getenv(name)) return val; return ""; } bool readFile(const char* inputFile, std::vector& buffer, bool mustExist = true) { int fd = open(inputFile, O_RDONLY); if (fd < 0) { if (mustExist) abortApp(inputFile); return false; } else { buffer.resize(4000); for (std::size_t offset = 0;;) { int bytes = ::read(fd, &buffer[offset], buffer.size() - offset - 1); if (bytes < 0) abortApp(inputFile); offset += bytes; if (offset + 1 == buffer.size()) buffer.resize(buffer.size() * 2); else if (bytes == 0) { close(fd); buffer[offset] = 0; buffer.resize(offset); return true; } } } } bool writeFile(const char* outputFile, const std::ostringstream& data, int flag = O_CREAT | O_WRONLY | O_TRUNC) { bool success = false; int fd = open(outputFile, flag, 0640); if (fd >= 0) { const std::string& buffer = data.str(); if (write(fd, buffer.c_str(), buffer.length()) == (ssize_t) buffer.length()) success = true; close(fd); } return success; } template std::ostream& operator<<(std::ostream& out, const std::map& container) { for (const auto& item : container) out << item.first << '|' << item.second << '\t'; // write into storage return out << '\n'; } template bool fromString(const std::string& from, T& to) { return bool(std::istringstream(from) >> to); } template <> bool fromString(const std::string& from, std::string& to) { to = from; return true; } template std::istream& operator>>(std::istream& in, std::map& container) { while (!in.eof() && !in.fail() && (in.peek() != '\n')) { // read from storage std::string skey; if (std::getline(in, skey, '|')) { K key; V value; if (fromString(skey, key) && (in >> value)) { container.insert( { key, value } ); in.ignore(); // tab } } } return in.ignore(); } struct CPU { typedef int16_t Number; // -1 for all cores summary and 0,1,2,... for each core struct Core { int64_t user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice; uint64_t freq_hz; int64_t cpuUsed() const { return user + nice + system + irq + softirq + guest + guestnice; } int64_t cpuTotal() const { return cpuUsed() + idle + iowait + steal; } Core operator-(const Core& that) const { int64_t roundUserFix = user == that.user-1? 1 : 0; // compensate for physical host & virtual int64_t roundNiceFix = nice == that.nice-1? 1 : 0; // guests counters evil rounding return { user - that.user + roundUserFix, nice - that.nice + roundNiceFix, system - that.system, idle - that.idle, iowait - that.iowait, irq - that.irq, softirq - that.softirq, steal - that.steal, guest - that.guest + roundUserFix, guestnice - that.guestnice + roundNiceFix, (freq_hz + that.freq_hz) / 2 // <-- a more useful abstraction }; } }; std::map cores; void readProc() { std::vector buffer; readFile("/proc/stat", buffer); std::istringstream cpustat(&buffer[0]); std::string name; Number number; while (cpustat >> name) { if (name.find("cpu") == 0) { name.erase(0,3); if (name.empty()) number = -1; else if (!fromString(name, number)) continue; Core& core = cores[number]; cpustat >> core.user >> core.nice >> core.system >> core.idle >> core.iowait >> core.irq >> core.softirq; if (cpustat.peek() != '\n') cpustat >> core.steal; else core.steal = 0; // caution with old kernels if (cpustat.peek() != '\n') cpustat >> core.guest; else core.guest = 0; if (cpustat.peek() != '\n') cpustat >> core.guestnice; else core.guestnice = 0; core.freq_hz = 0; core.user -= core.guest; // adjust for the already accounted values (may cause evil rounding core.nice -= core.guestnice; // effects, though) } cpustat.ignore(buffer.size(), '\n'); } readFile("/proc/cpuinfo", buffer); std::istringstream cpuinfo(&buffer[0]); std::string key; std::string skip; uint64_t sum_freq = 0; number = -1; while (cpuinfo >> key) { if (key == "processor") cpuinfo >> skip >> number; else if ((key == "cpu") && (number >= 0)) { if ((cpuinfo >> skip) && (skip == "MHz")) { double mhz; cpuinfo >> skip >> mhz; cores[number].freq_hz = uint64_t(mhz * MB_i); sum_freq += cores[number].freq_hz; number = -1; } } cpuinfo.ignore(buffer.size(), '\n'); } auto allcpu = cores.find(-1); if (allcpu != cores.end()) allcpu->second.freq_hz = sum_freq; } }; std::ostream& operator<<(std::ostream& out, const CPU::Core& core) // write into storage { return out << core.user << ' ' << core.nice << ' ' << core.system << ' ' << core.idle << ' ' << core.iowait << ' ' << core.irq << ' ' << core.softirq << ' ' << core.steal << ' ' << core.guest << ' ' << core.guestnice << ' ' << core.freq_hz; } std::istream& operator>>(std::istream& in, CPU::Core& core) // read from storage { return in >> core.user >> core.nice >> core.system >> core.idle >> core.iowait >> core.irq >> core.softirq >> core.steal >> core.guest >> core.guestnice >> core.freq_hz; } struct Memory { struct RAM { uint64_t total; uint64_t available; uint64_t free; uint64_t shared; uint64_t buffers; uint64_t cached; uint64_t swapTotal; uint64_t swapFree; }; RAM ram; void readProc() { std::vector buffer; readFile("/proc/meminfo", buffer); std::istringstream meminfo(&buffer[0]); bool hasAvailable = false; std::string key; for (int count = 0; meminfo >> key;) { if (key == "MemTotal:") { count++; meminfo >> ram.total; } else if (key == "MemFree:") { count++; meminfo >> ram.free; } else if (key == "MemAvailable:") { count++; meminfo >> ram.available; hasAvailable = true; } else if (key == "Buffers:") { count++; meminfo >> ram.buffers; } else if (key == "Cached:") { count++; meminfo >> ram.cached; } else if (key == "SwapTotal:") { count++; meminfo >> ram.swapTotal; } else if (key == "SwapFree:") { count++; meminfo >> ram.swapFree; } else if (key == "Shmem:") { count++; meminfo >> ram.shared; } if (count == 7 + (hasAvailable? 1:0)) break; meminfo.ignore(buffer.size(), '\n'); } if (!hasAvailable) ram.available = ram.free + ram.buffers + ram.cached; // pre-2014 kernels } }; std::ostream& operator<<(std::ostream& out, const Memory::RAM& ram) { return out << ram.total << ' ' << ram.available << ' ' << ram.free << ' ' << ram.shared << ' ' << ram.buffers << ' ' << ram.cached << ' ' << ram.swapTotal << ' ' << ram.swapFree << '\n'; } std::istream& operator>>(std::istream& in, Memory::RAM& ram) { return in >> ram.total >> ram.available >> ram.free >> ram.shared >> ram.buffers >> ram.cached >> ram.swapTotal >> ram.swapFree; } struct Health { typedef std::string Name; struct Thermometer { int32_t tempMilliCelsius; }; std::map thermometers; void readProc() { std::string coretemp; for (int hwmon = 0; hwmon < 256; hwmon++) { std::string base = VA_STR("/sys/class/hwmon/hwmon" << hwmon); std::vector buffer; if (!readFile(VA_STR(base << "/name").c_str(), buffer, false)) { // pre-3.15 kernel? base.append("/device"); if (!readFile(VA_STR(base << "/name").c_str(), buffer, false)) break; } std::istringstream sname(&buffer[0]); std::string name; if ((sname >> name) && (name == "coretemp")) { coretemp = base; break; } } if (!coretemp.empty()) for (int ic = 1; ic < 64; ic++) { std::vector buffer; if (!readFile(VA_STR(coretemp << "/temp" << ic << "_label").c_str(), buffer, false)) { if (thermometers.empty()) continue; else break; // Atom CPU may start at 2 (!?) } std::istringstream sname(&buffer[0]); std::string name; if (!std::getline(sname, name) || name.empty()) break; if (!readFile(VA_STR(coretemp << "/temp" << ic << "_input").c_str(), buffer, false)) break; std::istringstream temp(&buffer[0]); int32_t tempMilliCelsius; if (!(temp >> tempMilliCelsius)) break; thermometers[name].tempMilliCelsius = tempMilliCelsius; } } }; std::ostream& operator<<(std::ostream& out, const Health::Thermometer& thm) { return out << thm.tempMilliCelsius; } std::istream& operator>>(std::istream& in, Health::Thermometer& thm) { return in >> thm.tempMilliCelsius; } template struct Padded { uint64_t max; T value; }; template std::ostream& operator<<(std::ostream& out, const Padded& data) { if (!std::isnan(data.value)) for (T div = data.max;; div /= 10) { // avoid infinite loop with NaN numbers if ((data.value >= div) && (data.value >= 1)) break; if ((data.value < 1) && (div <= 1)) break; out << " "; // two spaces in place of each missing digit (same width in XFCE Generic Monitor applet) } return out << data.value; } std::string dumpReport() { std::shared_ptr new_CPU, old_CPU; std::shared_ptr new_Memory; std::shared_ptr new_Health; new_CPU.reset(new CPU()); new_Memory.reset(new Memory()); new_Health.reset(new Health()); timespec tp; if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) abortApp("clock_gettime"); uint64_t nowIs = tp.tv_sec * GB_i + tp.tv_nsec; std::ostringstream newState; newState << APP_VERSION << " " << nowIs << "\n"; if (new_CPU) { new_CPU->readProc(); newState << "CPU|" << new_CPU->cores; } if (new_Memory) { new_Memory->readProc(); } if (new_Health) { new_Health->readProc(); } for (int locTry = 0;; locTry++) { // read the previous state from disk and store the new state std::vector oldStateData; std::ostringstream stateFileName; if (stateFilePath.length()) stateFileName << stateFilePath; else if (locTry == 0) stateFileName << "/run/" APP_NAME "/" APP_NAME << ".dat"; else if (locTry == 1) stateFileName << "/tmp/" APP_NAME "-" << getuid() << "/" APP_NAME << ".dat"; else abortApp("can't write tmpfile"); auto stateDir = fs::path(stateFileName.str()).parent_path(); if(not fs::exists(stateDir)) { try { fs::create_directories(stateDir); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } } if (readFile(stateFileName.str().c_str(), oldStateData, false)) { std::istringstream oldState(&oldStateData[0]); std::string version; uint64_t previouslyWas; oldState >> version >> previouslyWas; oldState.ignore(oldStateData.size(), '\n'); if (version == APP_VERSION) { std::string category; while (std::getline(oldState, category, '|')) { if (category == "CPU") { old_CPU.reset(new CPU()); oldState >> old_CPU->cores; } else oldState.ignore(oldStateData.size(), '\n'); } } } if (writeFile(stateFileName.str().c_str(), newState)) { if(not stateFilePath.length()) stateFilePath = stateFileName.str(); break; } } std::ostringstream report; if (new_CPU && old_CPU) { // CPU report struct CpuStat { CPU::Number number; double percent; double ghz; }; std::multimap rankByGhzUsage; double cum_weighted_ghz = 0; for (auto itn = new_CPU->cores.cbegin(); itn != new_CPU->cores.cend(); ++itn) { if (itn->first < 0) continue; auto ito = old_CPU->cores.find(itn->first); if (ito == old_CPU->cores.end()) continue; CPU::Core diff = itn->second - ito->second; if (diff.cpuTotal() == 0) continue; double unityUsage = 1.0 * diff.cpuUsed() / diff.cpuTotal(); double ghz = diff.freq_hz / GB_f; double ghzUsage = ghz * unityUsage; cum_weighted_ghz += ghzUsage; rankByGhzUsage.insert({ ghzUsage, CpuStat { itn->first, 100.0 * unityUsage, ghz } }); } auto allnew = new_CPU->cores.find(-1); auto allold = old_CPU->cores.find(-1); if ((allnew != new_CPU->cores.end()) && (allold != old_CPU->cores.end())) { const CPU::Core& ncpu = allnew->second; const CPU::Core& ocpu = allold->second; CPU::Core diff = ncpu - ocpu; auto cpuTotal = diff.cpuTotal(); if (cpuTotal > 0) { auto cpuTotalSinceBoot = ncpu.cpuTotal(); double usagePercent = 100.0 * diff.cpuUsed() / cpuTotal; auto dumpPercent = [&](const char* title, int64_t user_hz, int64_t user_hz__sinceBoot) { report << "cpu\t" << title << "%\t" << 100.0 * user_hz / cpuTotal << "\t" << 100.0 * user_hz__sinceBoot / cpuTotalSinceBoot << std::endl; }; report << "cpu\tbase\t" << std::fixed << std::setprecision(2) << usagePercent << "\t" << uint64_t(cum_weighted_ghz * 1000) << std::setprecision(2) << std::endl; dumpPercent("user", diff.user, ncpu.user); dumpPercent("nice", diff.nice, ncpu.nice); dumpPercent("system", diff.system, ncpu.system); dumpPercent("idle", diff.idle, ncpu.idle); dumpPercent("iowait", diff.iowait, ncpu.iowait); if (ncpu.irq) dumpPercent("irq", diff.irq, ncpu.irq); if (ncpu.softirq) dumpPercent("swirq", diff.softirq, ncpu.softirq); if (ncpu.steal) dumpPercent("steal", diff.steal, ncpu.steal); if (ncpu.guest) dumpPercent("guest", diff.guest, ncpu.guest); if (ncpu.guestnice) dumpPercent("guestnice", diff.guestnice, ncpu.guestnice); int maxCpu = 8; for (auto itc = rankByGhzUsage.crbegin(); maxCpu-- && (itc != rankByGhzUsage.crend()); ++itc) { report << "cpu\tcore:" << itc->second.number << "\t" << std::fixed << std::setprecision(2) << itc->second.percent << "\t" << std::setprecision(3) << uint64_t(itc->second.ghz * 1000) << std::endl; } } } } auto dumpRam = [&] (const char * const title, uint64_t size) { report << "mem" << "\t" << title << "\t" << size << std::endl; }; if (new_Memory) { // RAM report if (new_CPU) { dumpRam("total", new_Memory->ram.total); dumpRam("avail", new_Memory->ram.available); dumpRam("cache", new_Memory->ram.cached + new_Memory->ram.buffers); } if (new_Memory->ram.shared) dumpRam("share", new_Memory->ram.shared); if (new_Memory->ram.swapTotal) { dumpRam("tswap", new_Memory->ram.swapTotal); dumpRam("uswap", new_Memory->ram.swapTotal - new_Memory->ram.swapFree); } } if (new_Health) { // TEMP report struct ThermalStat { ThermalStat() : min(std::numeric_limits::max()), max(std::numeric_limits::min()), avg(0), count(0) {} int32_t min; int32_t max; int32_t avg; std::size_t count; Health::Name firstName; }; std::map statByCategory; int32_t maxAbsTemp = std::numeric_limits::min(); for (const auto& itt : new_Health->thermometers) { std::string key = itt.first; auto catEnd = key.find(" "); if (catEnd != std::string::npos) key.erase(catEnd); auto its = statByCategory.find(key); if (its == statByCategory.end()) { its = statByCategory.insert( { key, ThermalStat() } ).first; its->second.firstName = itt.first; } if (itt.second.tempMilliCelsius < its->second.min) its->second.min = itt.second.tempMilliCelsius; if (itt.second.tempMilliCelsius > its->second.max) its->second.max = itt.second.tempMilliCelsius; if (itt.second.tempMilliCelsius > maxAbsTemp) maxAbsTemp = itt.second.tempMilliCelsius; auto prevTotal = its->second.avg * its->second.count; its->second.count++; its->second.avg = (prevTotal + itt.second.tempMilliCelsius) / its->second.count; } auto dumpTemp = [&](const char * const title, int32_t temp) { report << "tmp" << "\t" << title << "\t" << temp / 1000 << std::endl; }; for (auto its = statByCategory.crbegin(); its != statByCategory.crend(); ++its) { if (its->second.count == 1) dumpTemp("cur", its->second.max); else { dumpTemp("max", its->second.max); dumpTemp("min", its->second.min); dumpTemp("avg", its->second.avg); } } } return report.str(); }