From d0fa98931b22d60e0e70a54d683905d8b34ffb10 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 5 Aug 2021 17:27:02 +0800 Subject: [PATCH] proclogd: reduce the size of the procLog message by nearly half (#21800) * cp msg to remove the space * no orphans * cleanup * parse using istringstream * add test * split files * cleanup * add parser.cc to files_common * add test for build message * use > 0 * cleanup * test proc/self/stat * more test * dd * fix bug * update test * refactor pidStat * cleanup * test exe * check procs size in message * rename pidStat->ProcStat * don't use util::format_string * robust pids() * catch conversion exception * fix softirq * udpate test * use istringstream * use REQUIRE_THAT&cleanup * reserve vector of procStats * use istream to parse cmdline * cleanup --- .github/workflows/selfdrive_tests.yaml | 1 + release/files_common | 4 +- selfdrive/proclogd/SConscript | 6 +- selfdrive/proclogd/main.cc | 22 +++ selfdrive/proclogd/proclog.cc | 239 +++++++++++++++++++++++ selfdrive/proclogd/proclog.h | 40 ++++ selfdrive/proclogd/proclogd.cc | 233 ---------------------- selfdrive/proclogd/tests/.gitignore | 1 + selfdrive/proclogd/tests/test_proclog.cc | 145 ++++++++++++++ 9 files changed, 456 insertions(+), 235 deletions(-) create mode 100644 selfdrive/proclogd/main.cc create mode 100644 selfdrive/proclogd/proclog.cc create mode 100644 selfdrive/proclogd/proclog.h delete mode 100644 selfdrive/proclogd/proclogd.cc create mode 100644 selfdrive/proclogd/tests/.gitignore create mode 100644 selfdrive/proclogd/tests/test_proclog.cc diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index f915235c0..ebd1526cd 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -214,6 +214,7 @@ jobs: $UNIT_TEST selfdrive/thermald && \ $UNIT_TEST tools/lib/tests && \ ./selfdrive/common/tests/test_util && \ + ./selfdrive/proclogd/tests/test_proclog && \ ./selfdrive/camerad/test/ae_gray_test" - name: Upload coverage to Codecov run: bash <(curl -s https://codecov.io/bash) -v -F unit_tests diff --git a/release/files_common b/release/files_common index 4ca12b536..d5265280c 100644 --- a/release/files_common +++ b/release/files_common @@ -310,7 +310,9 @@ selfdrive/logcatd/logcatd_android.cc selfdrive/logcatd/logcatd_systemd.cc selfdrive/proclogd/SConscript -selfdrive/proclogd/proclogd.cc +selfdrive/proclogd/main.cc +selfdrive/proclogd/proclog.cc +selfdrive/proclogd/proclog.h selfdrive/loggerd/SConscript selfdrive/loggerd/encoder.h diff --git a/selfdrive/proclogd/SConscript b/selfdrive/proclogd/SConscript index f95f2597e..1b94a32f1 100644 --- a/selfdrive/proclogd/SConscript +++ b/selfdrive/proclogd/SConscript @@ -1,2 +1,6 @@ Import('env', 'cereal', 'messaging', 'common') -env.Program('proclogd.cc', LIBS=[cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj', 'common']) +libs = [cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj', 'common', 'zmq', 'json11'] +env.Program('proclogd', ['main.cc', 'proclog.cc'], LIBS=libs) + +if GetOption('test'): + env.Program('tests/test_proclog', ['tests/test_proclog.cc', 'proclog.cc'], LIBS=libs) diff --git a/selfdrive/proclogd/main.cc b/selfdrive/proclogd/main.cc new file mode 100644 index 000000000..4e31597c2 --- /dev/null +++ b/selfdrive/proclogd/main.cc @@ -0,0 +1,22 @@ + +#include + +#include "selfdrive/common/util.h" +#include "selfdrive/proclogd/proclog.h" + +ExitHandler do_exit; + +int main(int argc, char **argv) { + setpriority(PRIO_PROCESS, 0, -15); + + PubMaster publisher({"procLog"}); + while (!do_exit) { + MessageBuilder msg; + buildProcLogMessage(msg); + publisher.send("procLog", msg); + + util::sleep_for(2000); // 2 secs + } + + return 0; +} diff --git a/selfdrive/proclogd/proclog.cc b/selfdrive/proclogd/proclog.cc new file mode 100644 index 000000000..adc828cb5 --- /dev/null +++ b/selfdrive/proclogd/proclog.cc @@ -0,0 +1,239 @@ +#include "selfdrive/proclogd/proclog.h" + +#include + +#include +#include +#include +#include + +#include "selfdrive/common/swaglog.h" +#include "selfdrive/common/util.h" + +namespace Parser { + +// parse /proc/stat +std::vector cpuTimes(std::istream &stream) { + std::vector cpu_times; + std::string line; + // skip the first line for cpu total + std::getline(stream, line); + while (std::getline(stream, line)) { + if (line.compare(0, 3, "cpu") != 0) break; + + CPUTime t = {}; + std::istringstream iss(line); + if (iss.ignore(3) >> t.id >> t.utime >> t.ntime >> t.stime >> t.itime >> t.iowtime >> t.irqtime >> t.sirqtime) + cpu_times.push_back(t); + } + return cpu_times; +} + +// parse /proc/meminfo +std::unordered_map memInfo(std::istream &stream) { + std::unordered_map mem_info; + std::string line, key; + while (std::getline(stream, line)) { + uint64_t val = 0; + std::istringstream iss(line); + if (iss >> key >> val) { + mem_info[key] = val * 1024; + } + } + return mem_info; +} + +// field position (https://man7.org/linux/man-pages/man5/proc.5.html) +enum StatPos { + pid = 1, + state = 3, + ppid = 4, + utime = 14, + stime = 15, + cutime = 16, + cstime = 17, + priority = 18, + nice = 19, + num_threads = 20, + starttime = 22, + vsize = 23, + rss = 24, + processor = 39, + MAX_FIELD = 52, +}; + +// parse /proc/pid/stat +std::optional procStat(std::string stat) { + // To avoid being fooled by names containing a closing paren, scan backwards. + auto open_paren = stat.find('('); + auto close_paren = stat.rfind(')'); + if (open_paren == std::string::npos || close_paren == std::string::npos || open_paren > close_paren) { + return std::nullopt; + } + + std::string name = stat.substr(open_paren + 1, close_paren - open_paren - 1); + // repace space in name with _ + std::replace(&stat[open_paren], &stat[close_paren], ' ', '_'); + std::istringstream iss(stat); + std::vector v{std::istream_iterator(iss), + std::istream_iterator()}; + try { + if (v.size() != StatPos::MAX_FIELD) { + throw std::invalid_argument("stat"); + } + ProcStat p = { + .name = name, + .pid = stoi(v[StatPos::pid - 1]), + .state = v[StatPos::state - 1][0], + .ppid = stoi(v[StatPos::ppid - 1]), + .utime = stoul(v[StatPos::utime - 1]), + .stime = stoul(v[StatPos::stime - 1]), + .cutime = stol(v[StatPos::cutime - 1]), + .cstime = stol(v[StatPos::cstime - 1]), + .priority = stol(v[StatPos::priority - 1]), + .nice = stol(v[StatPos::nice - 1]), + .num_threads = stol(v[StatPos::num_threads - 1]), + .starttime = stoull(v[StatPos::starttime - 1]), + .vms = stoul(v[StatPos::vsize - 1]), + .rss = stoul(v[StatPos::rss - 1]), + .processor = stoi(v[StatPos::processor - 1]), + }; + return p; + } catch (const std::invalid_argument &e) { + LOGE("failed to parse procStat (%s) :%s", e.what(), stat.c_str()); + } catch (const std::out_of_range &e) { + LOGE("failed to parse procStat (%s) :%s", e.what(), stat.c_str()); + } + return std::nullopt; +} + +// return list of PIDs from /proc +std::vector pids() { + std::vector ids; + DIR *d = opendir("/proc"); + assert(d); + char *p_end; + struct dirent *de = NULL; + while ((de = readdir(d))) { + if (de->d_type == DT_DIR) { + int pid = strtol(de->d_name, &p_end, 10); + if (p_end == (de->d_name + strlen(de->d_name))) { + ids.push_back(pid); + } + } + } + closedir(d); + return ids; +} + +// null-delimited cmdline arguments to vector +std::vector cmdline(std::istream &stream) { + std::vector ret; + std::string line; + while (std::getline(stream, line, '\0')) { + if (!line.empty()) { + ret.push_back(line); + } + } + return ret; +} + +const ProcCache &getProcExtraInfo(int pid, const std::string &name) { + static std::unordered_map proc_cache; + ProcCache &cache = proc_cache[pid]; + if (cache.pid != pid || cache.name != name) { + cache.pid = pid; + cache.name = name; + std::string proc_path = "/proc/" + std::to_string(pid); + cache.exe = util::readlink(proc_path + "/exe"); + std::ifstream stream(proc_path + "/cmdline"); + cache.cmdline = cmdline(stream); + } + return cache; +} + +} // namespace Parser + +const double jiffy = sysconf(_SC_CLK_TCK); +const size_t page_size = sysconf(_SC_PAGE_SIZE); + +void buildCPUTimes(cereal::ProcLog::Builder &builder) { + std::ifstream stream("/proc/stat"); + std::vector stats = Parser::cpuTimes(stream); + + auto log_cpu_times = builder.initCpuTimes(stats.size()); + for (int i = 0; i < stats.size(); ++i) { + auto l = log_cpu_times[i]; + const CPUTime &r = stats[i]; + l.setCpuNum(r.id); + l.setUser(r.utime / jiffy); + l.setNice(r.ntime / jiffy); + l.setSystem(r.stime / jiffy); + l.setIdle(r.itime / jiffy); + l.setIowait(r.iowtime / jiffy); + l.setIrq(r.irqtime / jiffy); + l.setSoftirq(r.sirqtime / jiffy); + } +} + +void buildMemInfo(cereal::ProcLog::Builder &builder) { + std::ifstream stream("/proc/meminfo"); + auto mem_info = Parser::memInfo(stream); + + auto mem = builder.initMem(); + mem.setTotal(mem_info["MemTotal:"]); + mem.setFree(mem_info["MemFree:"]); + mem.setAvailable(mem_info["MemAvailable:"]); + mem.setBuffers(mem_info["Buffers:"]); + mem.setCached(mem_info["Cached:"]); + mem.setActive(mem_info["Active:"]); + mem.setInactive(mem_info["Inactive:"]); + mem.setShared(mem_info["Shmem:"]); +} + +void buildProcs(cereal::ProcLog::Builder &builder) { + auto pids = Parser::pids(); + std::vector proc_stats; + proc_stats.reserve(pids.size()); + for (int pid : pids) { + std::string path = "/proc/" + std::to_string(pid) + "/stat"; + if (auto stat = Parser::procStat(util::read_file(path))) { + proc_stats.push_back(*stat); + } + } + + auto procs = builder.initProcs(proc_stats.size()); + for (size_t i = 0; i < proc_stats.size(); i++) { + auto l = procs[i]; + const ProcStat &r = proc_stats[i]; + l.setPid(r.pid); + l.setState(r.state); + l.setPpid(r.ppid); + l.setCpuUser(r.utime / jiffy); + l.setCpuSystem(r.stime / jiffy); + l.setCpuChildrenUser(r.cutime / jiffy); + l.setCpuChildrenSystem(r.cstime / jiffy); + l.setPriority(r.priority); + l.setNice(r.nice); + l.setNumThreads(r.num_threads); + l.setStartTime(r.starttime / jiffy); + l.setMemVms(r.vms); + l.setMemRss((uint64_t)r.rss * page_size); + l.setProcessor(r.processor); + l.setName(r.name); + + const ProcCache &extra_info = Parser::getProcExtraInfo(r.pid, r.name); + l.setExe(extra_info.exe); + auto lcmdline = l.initCmdline(extra_info.cmdline.size()); + for (size_t i = 0; i < lcmdline.size(); i++) { + lcmdline.set(i, extra_info.cmdline[i]); + } + } +} + +void buildProcLogMessage(MessageBuilder &msg) { + auto procLog = msg.initEvent().initProcLog(); + buildProcs(procLog); + buildCPUTimes(procLog); + buildMemInfo(procLog); +} diff --git a/selfdrive/proclogd/proclog.h b/selfdrive/proclogd/proclog.h new file mode 100644 index 000000000..9ed53d1ba --- /dev/null +++ b/selfdrive/proclogd/proclog.h @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +#include "cereal/messaging/messaging.h" + +struct CPUTime { + int id; + unsigned long utime, ntime, stime, itime; + unsigned long iowtime, irqtime, sirqtime; +}; + +struct ProcCache { + int pid; + std::string name, exe; + std::vector cmdline; +}; + +struct ProcStat { + int pid, ppid, processor; + char state; + long cutime, cstime, priority, nice, num_threads; + unsigned long utime, stime, vms, rss; + unsigned long long starttime; + std::string name; +}; + +namespace Parser { + +std::vector pids(); +std::optional procStat(std::string stat); +std::vector cmdline(std::istream &stream); +std::vector cpuTimes(std::istream &stream); +std::unordered_map memInfo(std::istream &stream); +const ProcCache &getProcExtraInfo(int pid, const std::string &name); + +}; // namespace Parser + +void buildProcLogMessage(MessageBuilder &msg); diff --git a/selfdrive/proclogd/proclogd.cc b/selfdrive/proclogd/proclogd.cc deleted file mode 100644 index af7a6e3f2..000000000 --- a/selfdrive/proclogd/proclogd.cc +++ /dev/null @@ -1,233 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cereal/messaging/messaging.h" -#include "selfdrive/common/timing.h" -#include "selfdrive/common/util.h" - -ExitHandler do_exit; - -namespace { -struct ProcCache { - std::string name; - std::vector cmdline; - std::string exe; -}; - -} - -int main() { - setpriority(PRIO_PROCESS, 0, -15); - - PubMaster publisher({"procLog"}); - - double jiffy = sysconf(_SC_CLK_TCK); - size_t page_size = sysconf(_SC_PAGE_SIZE); - - std::unordered_map proc_cache; - - while (!do_exit) { - - MessageBuilder msg; - auto procLog = msg.initEvent().initProcLog(); - auto orphanage = msg.getOrphanage(); - - // stat - { - std::vector> otimes; - - std::ifstream sstat("/proc/stat"); - std::string stat_line; - while (std::getline(sstat, stat_line)) { - if (util::starts_with(stat_line, "cpu ")) { - // cpu total - } else if (util::starts_with(stat_line, "cpu")) { - // specific cpu - int id; - unsigned long utime, ntime, stime, itime; - unsigned long iowtime, irqtime, sirqtime; - - sscanf(stat_line.data(), "cpu%d %lu %lu %lu %lu %lu %lu %lu", - &id, &utime, &ntime, &stime, &itime, &iowtime, &irqtime, &sirqtime); - - auto ltimeo = orphanage.newOrphan(); - auto ltime = ltimeo.get(); - ltime.setCpuNum(id); - ltime.setUser(utime / jiffy); - ltime.setNice(ntime / jiffy); - ltime.setSystem(stime / jiffy); - ltime.setIdle(itime / jiffy); - ltime.setIowait(iowtime / jiffy); - ltime.setIrq(irqtime / jiffy); - ltime.setSoftirq(irqtime / jiffy); - - otimes.push_back(std::move(ltimeo)); - - } else { - break; - } - } - - auto ltimes = procLog.initCpuTimes(otimes.size()); - for (size_t i = 0; i < otimes.size(); i++) { - ltimes.adoptWithCaveats(i, std::move(otimes[i])); - } - } - - // meminfo - { - auto mem = procLog.initMem(); - - std::ifstream smem("/proc/meminfo"); - std::string mem_line; - - uint64_t mem_total = 0, mem_free = 0, mem_available = 0, mem_buffers = 0; - uint64_t mem_cached = 0, mem_active = 0, mem_inactive = 0, mem_shared = 0; - - while (std::getline(smem, mem_line)) { - if (util::starts_with(mem_line, "MemTotal:")) sscanf(mem_line.data(), "MemTotal: %" SCNu64 " kB", &mem_total); - else if (util::starts_with(mem_line, "MemFree:")) sscanf(mem_line.data(), "MemFree: %" SCNu64 " kB", &mem_free); - else if (util::starts_with(mem_line, "MemAvailable:")) sscanf(mem_line.data(), "MemAvailable: %" SCNu64 " kB", &mem_available); - else if (util::starts_with(mem_line, "Buffers:")) sscanf(mem_line.data(), "Buffers: %" SCNu64 " kB", &mem_buffers); - else if (util::starts_with(mem_line, "Cached:")) sscanf(mem_line.data(), "Cached: %" SCNu64 " kB", &mem_cached); - else if (util::starts_with(mem_line, "Active:")) sscanf(mem_line.data(), "Active: %" SCNu64 " kB", &mem_active); - else if (util::starts_with(mem_line, "Inactive:")) sscanf(mem_line.data(), "Inactive: %" SCNu64 " kB", &mem_inactive); - else if (util::starts_with(mem_line, "Shmem:")) sscanf(mem_line.data(), "Shmem: %" SCNu64 " kB", &mem_shared); - } - - mem.setTotal(mem_total * 1024); - mem.setFree(mem_free * 1024); - mem.setAvailable(mem_available * 1024); - mem.setBuffers(mem_buffers * 1024); - mem.setCached(mem_cached * 1024); - mem.setActive(mem_active * 1024); - mem.setInactive(mem_inactive * 1024); - mem.setShared(mem_shared * 1024); - } - - // processes - { - std::vector> oprocs; - struct dirent *de = NULL; - DIR *d = opendir("/proc"); - assert(d); - while ((de = readdir(d))) { - if (!isdigit(de->d_name[0])) continue; - pid_t pid = atoi(de->d_name); - - - auto lproco = orphanage.newOrphan(); - auto lproc = lproco.get(); - - lproc.setPid(pid); - - char tcomm[PATH_MAX] = {0}; - - { - std::string stat = util::read_file(util::string_format("/proc/%d/stat", pid)); - - char state; - - int ppid; - unsigned long utime, stime; - long cutime, cstime, priority, nice, num_threads; - unsigned long long starttime; - unsigned long vms, rss; - int processor; - - int count = sscanf(stat.data(), - "%*d (%1024[^)]) %c %d %*d %*d %*d %*d %*d %*d %*d %*d %*d " - "%lu %lu %ld %ld %ld %ld %ld %*d %lld " - "%lu %lu %*d %*d %*d %*d %*d %*d %*d " - "%*d %*d %*d %*d %*d %*d %*d %d", - tcomm, &state, &ppid, - &utime, &stime, &cutime, &cstime, &priority, &nice, &num_threads, &starttime, - &vms, &rss, &processor); - - if (count != 14) continue; - - lproc.setState(state); - lproc.setPpid(ppid); - lproc.setCpuUser(utime / jiffy); - lproc.setCpuSystem(stime / jiffy); - lproc.setCpuChildrenUser(cutime / jiffy); - lproc.setCpuChildrenSystem(cstime / jiffy); - lproc.setPriority(priority); - lproc.setNice(nice); - lproc.setNumThreads(num_threads); - lproc.setStartTime(starttime / jiffy); - lproc.setMemVms(vms); - lproc.setMemRss((uint64_t)rss * page_size); - lproc.setProcessor(processor); - } - - std::string name(tcomm); - lproc.setName(name); - - // populate other things from cache - auto cache_it = proc_cache.find(pid); - ProcCache cache; - if (cache_it != proc_cache.end()) { - cache = cache_it->second; - } - if (cache_it == proc_cache.end() || cache.name != name) { - cache = (ProcCache){ - .name = name, - .exe = util::readlink(util::string_format("/proc/%d/exe", pid)), - }; - - // null-delimited cmdline arguments to vector - std::string cmdline_s = util::read_file(util::string_format("/proc/%d/cmdline", pid)); - const char* cmdline_p = cmdline_s.c_str(); - const char* cmdline_ep = cmdline_p + cmdline_s.size(); - - // strip trailing null bytes - while ((cmdline_ep-1) > cmdline_p && *(cmdline_ep-1) == 0) { - cmdline_ep--; - } - - while (cmdline_p < cmdline_ep) { - std::string arg(cmdline_p); - cache.cmdline.push_back(arg); - cmdline_p += arg.size() + 1; - } - - proc_cache[pid] = cache; - } - - auto lcmdline = lproc.initCmdline(cache.cmdline.size()); - for (size_t i = 0; i < lcmdline.size(); i++) { - lcmdline.set(i, cache.cmdline[i]); - } - lproc.setExe(cache.exe); - - oprocs.push_back(std::move(lproco)); - } - closedir(d); - - auto lprocs = procLog.initProcs(oprocs.size()); - for (size_t i = 0; i < oprocs.size(); i++) { - lprocs.adoptWithCaveats(i, std::move(oprocs[i])); - } - } - - publisher.send("procLog", msg); - - util::sleep_for(2000); // 2 secs - } - - return 0; -} diff --git a/selfdrive/proclogd/tests/.gitignore b/selfdrive/proclogd/tests/.gitignore new file mode 100644 index 000000000..5230b1598 --- /dev/null +++ b/selfdrive/proclogd/tests/.gitignore @@ -0,0 +1 @@ +test_proclog diff --git a/selfdrive/proclogd/tests/test_proclog.cc b/selfdrive/proclogd/tests/test_proclog.cc new file mode 100644 index 000000000..88a09dd03 --- /dev/null +++ b/selfdrive/proclogd/tests/test_proclog.cc @@ -0,0 +1,145 @@ +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" +#include "selfdrive/common/util.h" +#include "selfdrive/proclogd/proclog.h" + +const std::string allowed_states = "RSDTZtWXxKWPI"; + +TEST_CASE("Parser::procStat") { + SECTION("from string") { + const std::string stat_str = + "33012 (code )) S 32978 6620 6620 0 -1 4194368 2042377 0 144 0 24510 11627 0 " + "0 20 0 39 0 53077 830029824 62214 18446744073709551615 94257242783744 94257366235808 " + "140735738643248 0 0 0 0 4098 1073808632 0 0 0 17 2 0 0 2 0 0 94257370858656 94257371248232 " + "94257404952576 140735738648768 140735738648823 140735738648823 140735738650595 0"; + auto stat = Parser::procStat(stat_str); + REQUIRE(stat); + REQUIRE(stat->pid == 33012); + REQUIRE(stat->name == "code )"); + REQUIRE(stat->state == 'S'); + REQUIRE(stat->ppid == 32978); + REQUIRE(stat->utime == 24510); + REQUIRE(stat->stime == 11627); + REQUIRE(stat->cutime == 0); + REQUIRE(stat->cstime == 0); + REQUIRE(stat->priority == 20); + REQUIRE(stat->nice == 0); + REQUIRE(stat->num_threads == 39); + REQUIRE(stat->starttime == 53077); + REQUIRE(stat->vms == 830029824); + REQUIRE(stat->rss == 62214); + REQUIRE(stat->processor == 2); + } + SECTION("all processes") { + std::vector pids = Parser::pids(); + REQUIRE(pids.size() > 1); + int parsed_cnt = 0; + for (int pid : pids) { + if (auto stat = Parser::procStat(util::read_file("/proc/" + std::to_string(pid) + "/stat"))) { + REQUIRE(stat->pid == pid); + REQUIRE(allowed_states.find(stat->state) != std::string::npos); + ++parsed_cnt; + } + } + REQUIRE(parsed_cnt == pids.size()); + } +} + +TEST_CASE("Parser::cpuTimes") { + SECTION("from string") { + std::string stat = + "cpu 0 0 0 0 0 0 0 0 0 0\n" + "cpu0 1 2 3 4 5 6 7 8 9 10\n" + "cpu1 1 2 3 4 5 6 7 8 9 10\n"; + std::istringstream stream(stat); + auto stats = Parser::cpuTimes(stream); + REQUIRE(stats.size() == 2); + for (int i = 0; i < stats.size(); ++i) { + REQUIRE(stats[i].id == i); + REQUIRE(stats[i].utime == 1); + REQUIRE(stats[i].ntime ==2); + REQUIRE(stats[i].stime == 3); + REQUIRE(stats[i].itime == 4); + REQUIRE(stats[i].iowtime == 5); + REQUIRE(stats[i].irqtime == 6); + REQUIRE(stats[i].sirqtime == 7); + } + } + SECTION("all cpus") { + std::istringstream stream(util::read_file("/proc/stat")); + auto stats = Parser::cpuTimes(stream); + REQUIRE(stats.size() == sysconf(_SC_NPROCESSORS_ONLN)); + for (int i = 0; i < stats.size(); ++i) { + REQUIRE(stats[i].id == i); + } + } +} + +TEST_CASE("Parser::memInfo") { + SECTION("from string") { + std::istringstream stream("MemTotal: 1024 kb\nMemFree: 2048 kb\n"); + auto meminfo = Parser::memInfo(stream); + REQUIRE(meminfo["MemTotal:"] == 1024 * 1024); + REQUIRE(meminfo["MemFree:"] == 2048 * 1024); + } + SECTION("from /proc/meminfo") { + std::string require_keys[] = {"MemTotal:", "MemFree:", "MemAvailable:", "Buffers:", "Cached:", "Active:", "Inactive:", "Shmem:"}; + std::istringstream stream(util::read_file("/proc/meminfo")); + auto meminfo = Parser::memInfo(stream); + for (auto &key : require_keys) { + REQUIRE(meminfo.find(key) != meminfo.end()); + REQUIRE(meminfo[key] > 0); + } + } +} + +void test_cmdline(std::string cmdline, const std::vector requires) { + std::stringstream ss; + ss.write(&cmdline[0], cmdline.size()); + auto cmds = Parser::cmdline(ss); + REQUIRE(cmds.size() == requires.size()); + for (int i = 0; i < requires.size(); ++i) { + REQUIRE(cmds[i] == requires[i]); + } +} +TEST_CASE("Parser::cmdline") { + test_cmdline(std::string("a\0b\0c\0", 7), {"a", "b", "c"}); + test_cmdline(std::string("a\0\0c\0", 6), {"a", "c"}); + test_cmdline(std::string("a\0b\0c\0\0\0", 9), {"a", "b", "c"}); +} + +TEST_CASE("buildProcLogerMessage") { + std::vector current_pids = Parser::pids(); + + MessageBuilder msg; + buildProcLogMessage(msg); + + kj::Array buf = capnp::messageToFlatArray(msg); + capnp::FlatArrayMessageReader reader(buf); + auto log = reader.getRoot().getProcLog(); + REQUIRE(log.totalSize().wordCount > 0); + + // test cereal::ProcLog::CPUTimes + auto cpu_times = log.getCpuTimes(); + REQUIRE(cpu_times.size() == sysconf(_SC_NPROCESSORS_ONLN)); + REQUIRE(cpu_times[cpu_times.size() - 1].getCpuNum() == cpu_times.size() - 1); + + // test cereal::ProcLog::Mem + auto mem = log.getMem(); + REQUIRE(mem.getTotal() > 0); + REQUIRE(mem.getShared() > 0); + + // test cereal::ProcLog::Process + auto procs = log.getProcs(); + REQUIRE(procs.size() == current_pids.size()); + + for (auto p : procs) { + REQUIRE_THAT(current_pids, Catch::Matchers::VectorContains(p.getPid())); + REQUIRE(allowed_states.find(p.getState()) != std::string::npos); + if (p.getPid() == ::getpid()) { + REQUIRE(p.getName() == "test_proclog"); + REQUIRE(p.getState() == 'R'); + REQUIRE_THAT(p.getExe().cStr(), Catch::Matchers::Contains("test_proclog")); + } + } +}