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
This commit is contained in:
parent
7166f166c0
commit
d0fa98931b
1
.github/workflows/selfdrive_tests.yaml
vendored
1
.github/workflows/selfdrive_tests.yaml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
22
selfdrive/proclogd/main.cc
Normal file
22
selfdrive/proclogd/main.cc
Normal file
|
@ -0,0 +1,22 @@
|
|||
|
||||
#include <sys/resource.h>
|
||||
|
||||
#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;
|
||||
}
|
239
selfdrive/proclogd/proclog.cc
Normal file
239
selfdrive/proclogd/proclog.cc
Normal file
|
@ -0,0 +1,239 @@
|
|||
#include "selfdrive/proclogd/proclog.h"
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
|
||||
#include "selfdrive/common/swaglog.h"
|
||||
#include "selfdrive/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);
|
||||
// repace 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 = 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<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 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);
|
||||
}
|
40
selfdrive/proclogd/proclog.h
Normal file
40
selfdrive/proclogd/proclog.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
#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;
|
||||
unsigned long utime, stime, vms, rss;
|
||||
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,233 +0,0 @@
|
|||
#include <dirent.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#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<std::string> 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<pid_t, ProcCache> proc_cache;
|
||||
|
||||
while (!do_exit) {
|
||||
|
||||
MessageBuilder msg;
|
||||
auto procLog = msg.initEvent().initProcLog();
|
||||
auto orphanage = msg.getOrphanage();
|
||||
|
||||
// stat
|
||||
{
|
||||
std::vector<capnp::Orphan<cereal::ProcLog::CPUTimes>> 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<cereal::ProcLog::CPUTimes>();
|
||||
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<capnp::Orphan<cereal::ProcLog::Process>> 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<cereal::ProcLog::Process>();
|
||||
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;
|
||||
}
|
1
selfdrive/proclogd/tests/.gitignore
vendored
Normal file
1
selfdrive/proclogd/tests/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
test_proclog
|
145
selfdrive/proclogd/tests/test_proclog.cc
Normal file
145
selfdrive/proclogd/tests/test_proclog.cc
Normal file
|
@ -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<int> 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<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("buildProcLogerMessage") {
|
||||
std::vector<int> current_pids = Parser::pids();
|
||||
|
||||
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();
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue