Rewrite proclogd in Python (#36110)

* Rewrite proclogd in Python

* lil more

* lil more

* Update system/proclogd.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update system/proclogd.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update system/proclogd.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
pull/36093/merge
Adeeb Shihadeh 1 week ago committed by GitHub
parent ff34b8af76
commit 275abc1eb5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      .gitignore
  2. 1
      SConstruct
  3. 1
      pyproject.toml
  4. 4
      selfdrive/test/test_onroad.py
  5. 2
      system/manager/process_config.py
  6. 227
      system/proclogd.py
  7. 6
      system/proclogd/SConscript
  8. 25
      system/proclogd/main.cc
  9. 239
      system/proclogd/proclog.cc
  10. 40
      system/proclogd/proclog.h
  11. 1
      system/proclogd/tests/.gitignore
  12. 142
      system/proclogd/tests/test_proclog.cc

1
.gitignore vendored

@ -50,7 +50,6 @@ cereal/services.h
cereal/gen
cereal/messaging/bridge
selfdrive/mapd/default_speeds_by_region.json
system/proclogd/proclogd
selfdrive/ui/translations/tmp
selfdrive/test/longitudinal_maneuvers/out
selfdrive/car/tests/cars_dump

@ -341,7 +341,6 @@ SConscript([
if arch != "Darwin":
SConscript([
'system/logcatd/SConscript',
'system/proclogd/SConscript',
])
if arch == "larch64":

@ -158,7 +158,6 @@ testpaths = [
"system/camerad",
"system/hardware",
"system/loggerd",
"system/proclogd",
"system/tests",
"system/ubloxd",
"system/webrtc",

@ -32,7 +32,7 @@ CPU usage budget
TEST_DURATION = 25
LOG_OFFSET = 8
MAX_TOTAL_CPU = 287. # total for all 8 cores
MAX_TOTAL_CPU = 300. # total for all 8 cores
PROCS = {
# Baseline CPU usage by process
"selfdrive.controls.controlsd": 16.0,
@ -56,7 +56,7 @@ PROCS = {
"selfdrive.ui.soundd": 3.0,
"selfdrive.ui.feedback.feedbackd": 1.0,
"selfdrive.monitoring.dmonitoringd": 4.0,
"./proclogd": 2.0,
"system.proclogd": 3.0,
"system.logmessaged": 1.0,
"system.tombstoned": 0,
"./logcatd": 1.0,

@ -72,7 +72,7 @@ procs = [
NativeProcess("camerad", "system/camerad", ["./camerad"], driverview, enabled=not WEBCAM),
PythonProcess("webcamerad", "tools.webcam.camerad", driverview, enabled=WEBCAM),
NativeProcess("logcatd", "system/logcatd", ["./logcatd"], only_onroad, platform.system() != "Darwin"),
NativeProcess("proclogd", "system/proclogd", ["./proclogd"], only_onroad, platform.system() != "Darwin"),
PythonProcess("proclogd", "system.proclogd", only_onroad, enabled=platform.system() != "Darwin"),
PythonProcess("micd", "system.micd", iscar),
PythonProcess("timed", "system.timed", always_run, enabled=not PC),

@ -0,0 +1,227 @@
#!/usr/bin/env python3
import os
from typing import NoReturn, TypedDict
from cereal import messaging
from openpilot.common.realtime import Ratekeeper
from openpilot.common.swaglog import cloudlog
JIFFY = os.sysconf(os.sysconf_names['SC_CLK_TCK'])
PAGE_SIZE = os.sysconf(os.sysconf_names['SC_PAGE_SIZE'])
def _cpu_times() -> list[dict[str, float]]:
cpu_times: list[dict[str, float]] = []
try:
with open('/proc/stat') as f:
lines = f.readlines()[1:]
for line in lines:
if not line.startswith('cpu') or len(line) < 4 or not line[3].isdigit():
break
parts = line.split()
cpu_times.append({
'cpuNum': int(parts[0][3:]),
'user': float(parts[1]) / JIFFY,
'nice': float(parts[2]) / JIFFY,
'system': float(parts[3]) / JIFFY,
'idle': float(parts[4]) / JIFFY,
'iowait': float(parts[5]) / JIFFY,
'irq': float(parts[6]) / JIFFY,
'softirq': float(parts[7]) / JIFFY,
})
except Exception:
cloudlog.exception("failed to read /proc/stat")
return cpu_times
def _mem_info() -> dict[str, int]:
keys = ["MemTotal:", "MemFree:", "MemAvailable:", "Buffers:", "Cached:", "Active:", "Inactive:", "Shmem:"]
info: dict[str, int] = dict.fromkeys(keys, 0)
try:
with open('/proc/meminfo') as f:
for line in f:
parts = line.split()
if parts and parts[0] in info:
info[parts[0]] = int(parts[1]) * 1024
except Exception:
cloudlog.exception("failed to read /proc/meminfo")
return info
_STAT_POS = {
'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,
}
class ProcStat(TypedDict):
name: str
pid: int
state: str
ppid: int
utime: int
stime: int
cutime: int
cstime: int
priority: int
nice: int
num_threads: int
starttime: int
vms: int
rss: int
processor: int
def _parse_proc_stat(stat: str) -> ProcStat | None:
open_paren = stat.find('(')
close_paren = stat.rfind(')')
if open_paren == -1 or close_paren == -1 or open_paren > close_paren:
return None
name = stat[open_paren + 1:close_paren]
stat = stat[:open_paren] + stat[open_paren:close_paren].replace(' ', '_') + stat[close_paren:]
parts = stat.split()
if len(parts) < 52:
return None
try:
return {
'name': name,
'pid': int(parts[_STAT_POS['pid'] - 1]),
'state': parts[_STAT_POS['state'] - 1][0],
'ppid': int(parts[_STAT_POS['ppid'] - 1]),
'utime': int(parts[_STAT_POS['utime'] - 1]),
'stime': int(parts[_STAT_POS['stime'] - 1]),
'cutime': int(parts[_STAT_POS['cutime'] - 1]),
'cstime': int(parts[_STAT_POS['cstime'] - 1]),
'priority': int(parts[_STAT_POS['priority'] - 1]),
'nice': int(parts[_STAT_POS['nice'] - 1]),
'num_threads': int(parts[_STAT_POS['num_threads'] - 1]),
'starttime': int(parts[_STAT_POS['starttime'] - 1]),
'vms': int(parts[_STAT_POS['vsize'] - 1]),
'rss': int(parts[_STAT_POS['rss'] - 1]),
'processor': int(parts[_STAT_POS['processor'] - 1]),
}
except Exception:
cloudlog.exception("failed to parse /proc/<pid>/stat")
return None
class ProcExtra(TypedDict):
pid: int
name: str
exe: str
cmdline: list[str]
_proc_cache: dict[int, ProcExtra] = {}
def _get_proc_extra(pid: int, name: str) -> ProcExtra:
cache: ProcExtra | None = _proc_cache.get(pid)
if cache is None or cache.get('name') != name:
exe = ''
cmdline: list[str] = []
try:
exe = os.readlink(f'/proc/{pid}/exe')
except OSError:
pass
try:
with open(f'/proc/{pid}/cmdline', 'rb') as f:
cmdline = [c.decode('utf-8', errors='replace') for c in f.read().split(b'\0') if c]
except OSError:
pass
cache = {'pid': pid, 'name': name, 'exe': exe, 'cmdline': cmdline}
_proc_cache[pid] = cache
return cache
def _procs() -> list[ProcStat]:
stats: list[ProcStat] = []
for pid_str in os.listdir('/proc'):
if not pid_str.isdigit():
continue
try:
with open(f'/proc/{pid_str}/stat') as f:
stat = f.read()
parsed = _parse_proc_stat(stat)
if parsed is not None:
stats.append(parsed)
except OSError:
continue
return stats
def build_proc_log_message(msg) -> None:
pl = msg.procLog
procs = _procs()
l = pl.init('procs', len(procs))
for i, r in enumerate(procs):
proc = l[i]
proc.pid = r['pid']
proc.state = ord(r['state'][0])
proc.ppid = r['ppid']
proc.cpuUser = r['utime'] / JIFFY
proc.cpuSystem = r['stime'] / JIFFY
proc.cpuChildrenUser = r['cutime'] / JIFFY
proc.cpuChildrenSystem = r['cstime'] / JIFFY
proc.priority = r['priority']
proc.nice = r['nice']
proc.numThreads = r['num_threads']
proc.startTime = r['starttime'] / JIFFY
proc.memVms = r['vms']
proc.memRss = r['rss'] * PAGE_SIZE
proc.processor = r['processor']
proc.name = r['name']
extra = _get_proc_extra(r['pid'], r['name'])
proc.exe = extra['exe']
cmdline = proc.init('cmdline', len(extra['cmdline']))
for j, arg in enumerate(extra['cmdline']):
cmdline[j] = arg
cpu_times = _cpu_times()
cpu_list = pl.init('cpuTimes', len(cpu_times))
for i, ct in enumerate(cpu_times):
cpu = cpu_list[i]
cpu.cpuNum = ct['cpuNum']
cpu.user = ct['user']
cpu.nice = ct['nice']
cpu.system = ct['system']
cpu.idle = ct['idle']
cpu.iowait = ct['iowait']
cpu.irq = ct['irq']
cpu.softirq = ct['softirq']
mem_info = _mem_info()
pl.mem.total = mem_info["MemTotal:"]
pl.mem.free = mem_info["MemFree:"]
pl.mem.available = mem_info["MemAvailable:"]
pl.mem.buffers = mem_info["Buffers:"]
pl.mem.cached = mem_info["Cached:"]
pl.mem.active = mem_info["Active:"]
pl.mem.inactive = mem_info["Inactive:"]
pl.mem.shared = mem_info["Shmem:"]
def main() -> NoReturn:
pm = messaging.PubMaster(['procLog'])
rk = Ratekeeper(0.5)
while True:
msg = messaging.new_message('procLog', valid=True)
build_proc_log_message(msg)
pm.send('procLog', msg)
rk.keep_time()
if __name__ == '__main__':
main()

@ -1,6 +0,0 @@
Import('env', 'messaging', 'common')
libs = [messaging, 'pthread', common]
env.Program('proclogd', ['main.cc', 'proclog.cc'], LIBS=libs)
if GetOption('extras'):
env.Program('tests/test_proclog', ['tests/test_proclog.cc', 'proclog.cc'], LIBS=libs)

@ -1,25 +0,0 @@
#include <sys/resource.h>
#include "common/ratekeeper.h"
#include "common/util.h"
#include "system/proclogd/proclog.h"
ExitHandler do_exit;
int main(int argc, char **argv) {
setpriority(PRIO_PROCESS, 0, -15);
RateKeeper rk("proclogd", 0.5);
PubMaster publisher({"procLog"});
while (!do_exit) {
MessageBuilder msg;
buildProcLogMessage(msg);
publisher.send("procLog", msg);
rk.keepTime();
}
return 0;
}

@ -1,239 +0,0 @@
#include "system/proclogd/proclog.h"
#include <dirent.h>
#include <cassert>
#include <fstream>
#include <iterator>
#include <sstream>
#include "common/swaglog.h"
#include "common/util.h"
namespace Parser {
// parse /proc/stat
std::vector<CPUTime> cpuTimes(std::istream &stream) {
std::vector<CPUTime> 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<std::string, uint64_t> memInfo(std::istream &stream) {
std::unordered_map<std::string, uint64_t> 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> 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);
// replace space in name with _
std::replace(&stat[open_paren], &stat[close_paren], ' ', '_');
std::istringstream iss(stat);
std::vector<std::string> v{std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>()};
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 = stol(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<int> pids() {
std::vector<int> 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<std::string> cmdline(std::istream &stream) {
std::vector<std::string> 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<pid_t, ProcCache> 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<CPUTime> 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<ProcStat> 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 j = 0; j < lcmdline.size(); j++) {
lcmdline.set(j, extra_info.cmdline[j]);
}
}
}
void buildProcLogMessage(MessageBuilder &msg) {
auto procLog = msg.initEvent().initProcLog();
buildProcs(procLog);
buildCPUTimes(procLog);
buildMemInfo(procLog);
}

@ -1,40 +0,0 @@
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#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<std::string> cmdline;
};
struct ProcStat {
int pid, ppid, processor;
char state;
long cutime, cstime, priority, nice, num_threads, rss;
unsigned long utime, stime, vms;
unsigned long long starttime;
std::string name;
};
namespace Parser {
std::vector<int> pids();
std::optional<ProcStat> procStat(std::string stat);
std::vector<std::string> cmdline(std::istream &stream);
std::vector<CPUTime> cpuTimes(std::istream &stream);
std::unordered_map<std::string, uint64_t> memInfo(std::istream &stream);
const ProcCache &getProcExtraInfo(int pid, const std::string &name);
}; // namespace Parser
void buildProcLogMessage(MessageBuilder &msg);

@ -1 +0,0 @@
test_proclog

@ -1,142 +0,0 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include "common/util.h"
#include "system/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<int> pids = Parser::pids();
REQUIRE(pids.size() > 1);
for (int pid : pids) {
std::string stat_path = "/proc/" + std::to_string(pid) + "/stat";
INFO(stat_path);
if (auto stat = Parser::procStat(util::read_file(stat_path))) {
REQUIRE(stat->pid == pid);
REQUIRE(allowed_states.find(stat->state) != std::string::npos);
} else {
REQUIRE(util::file_exists(stat_path) == false);
}
}
}
}
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<std::string> 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("buildProcLoggerMessage") {
MessageBuilder msg;
buildProcLogMessage(msg);
kj::Array<capnp::word> buf = capnp::messageToFlatArray(msg);
capnp::FlatArrayMessageReader reader(buf);
auto log = reader.getRoot<cereal::Event>().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();
for (auto p : procs) {
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"));
REQUIRE_THAT(p.getCmdline()[0], Catch::Matchers::Contains("test_proclog"));
}
}
}
Loading…
Cancel
Save