Files

207 lines
6.9 KiB
C

// MobileGL - tools/device_bench/disagg/sampler.c
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
// Device-side /proc sampler: per-tid syscall occupancy + CPU + switch counters.
// strace attach is SELinux-blocked on this device; /proc/<tid>/syscall is
// readable same-uid via run-as, which gives the blocked-syscall histogram.
// Usage: sampler <pid> <duration_s> <interval_ms> <out.csv>
#define _GNU_SOURCE
#include <dirent.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
static volatile sig_atomic_t g_stop = 0;
static void onsig(int s) {
(void)s;
g_stop = 1;
}
#define MAXT 128
#define MAXSY 400
struct T {
int tid;
char name[32];
long long cpu0; // utime+stime at start (ticks)
long long vcs0; // nr_voluntary_switches at start
long long run; // samples with syscall "running"
long long sys[MAXSY]; // samples while in syscall nr
long long openerr;
};
static long long readfile(const char* p, char* buf, int n) {
int fd = open(p, O_RDONLY);
if (fd < 0) return -1;
long long r = read(fd, buf, n - 1);
close(fd);
if (r > 0) buf[r] = 0;
return r;
}
int main(int argc, char** argv) {
if (argc < 4) {
fprintf(stderr, "usage: sampler pid dur_s interval_ms out\n");
return 2;
}
int pid = atoi(argv[1]);
int durMs = atoi(argv[2]) * 1000;
int intMs = argc > 4 ? atoi(argv[3]) : 2;
const char* out = argc > 4 ? argv[4] : "sampler.csv";
struct T ts[MAXT];
int n = 0;
char path[256], buf[8192];
// initial scan
for (int pass = 0; pass < 2; pass++) {
snprintf(path, sizeof path, "/proc/%d/task", pid);
DIR* d = opendir(path);
if (!d) {
perror("opendir task");
return 1;
}
struct dirent* e;
n = 0;
while ((e = readdir(d)) && n < MAXT) {
if (e->d_name[0] == '.') continue;
int tid = atoi(e->d_name);
if (tid <= 0) continue;
struct T* t = &ts[n++];
memset(t, 0, sizeof *t);
t->tid = tid;
snprintf(path, sizeof path, "/proc/%d/task/%d/comm", pid, tid);
if (readfile(path, buf, sizeof buf) > 0) {
strncpy(t->name, buf, sizeof t->name - 1);
char* nl = strchr(t->name, '\n');
if (nl) *nl = 0;
}
snprintf(path, sizeof path, "/proc/%d/task/%d/stat", pid, tid);
long long ut = 0, st = 0;
if (readfile(path, buf, sizeof buf) > 0) {
// fields: 1 pid, 2 comm, 3 state ... 14 utime, 15 stime.
// tokens after ')' start at field 3, so utime is f==11, stime f==12.
char* r = strrchr(buf, ')');
if (r) {
int f = 0;
char* sp = r + 1;
while (*sp) {
if (f == 11) ut = atoll(sp);
if (f == 12) {
st = atoll(sp);
break;
}
while (*sp && *sp != ' ')
sp++;
while (*sp == ' ')
sp++;
f++;
}
}
}
t->cpu0 = ut + st;
snprintf(path, sizeof path, "/proc/%d/task/%d/sched", pid, tid);
t->vcs0 = -1;
if (readfile(path, buf, sizeof buf) > 0) {
char* v = strstr(buf, "nr_voluntary_switches");
if (v) {
char* col = strchr(v, ':');
if (col) t->vcs0 = atoll(col + 1);
}
}
}
closedir(d);
if (n > 1) break; // first pass may catch a dying thread set
usleep(50000);
}
struct timespec t0, now;
clock_gettime(CLOCK_MONOTONIC, &t0);
long long elapsed = 0;
signal(SIGTERM, onsig);
signal(SIGINT, onsig);
struct timespec slp = {.tv_sec = 0, .tv_nsec = (long)intMs * 1000000L};
while (elapsed < durMs && !g_stop) {
for (int i = 0; i < n; i++) {
snprintf(path, sizeof path, "/proc/%d/task/%d/syscall", pid, ts[i].tid);
long long r = readfile(path, buf, sizeof buf);
if (r <= 0) {
ts[i].openerr++;
continue;
}
if (!strncmp(buf, "running", 7)) {
ts[i].run++;
continue;
}
long nr = -2;
if (buf[0] != '-') nr = strtol(buf, NULL, 10);
if (nr < 0) {
ts[i].run++;
continue;
} // -1 means running
if (nr >= MAXSY) nr = MAXSY - 1;
ts[i].sys[nr]++;
}
nanosleep(&slp, NULL);
clock_gettime(CLOCK_MONOTONIC, &now);
elapsed = (now.tv_sec - t0.tv_sec) * 1000LL + (now.tv_nsec - t0.tv_nsec) / 1000000LL;
}
FILE* f = fopen(out, "w");
if (!f) {
perror("fopen out");
return 1;
}
fprintf(f, "# pid=%d dur_ms=%lld interval_ms=%d samples_per_thread~=%lld\n", pid, elapsed, intMs, elapsed / intMs);
fprintf(f, "tid,name,cpu_ticks_delta,vcs_delta,run_samples,open_err,sys_hist\n");
for (int i = 0; i < n; i++) {
struct T* t = &ts[i];
long long ut = 0, st = 0;
snprintf(path, sizeof path, "/proc/%d/task/%d/stat", pid, t->tid);
if (readfile(path, buf, sizeof buf) > 0) {
char* r = strrchr(buf, ')');
if (r) {
int f2 = 0;
char* sp = r + 1;
while (*sp) {
if (f2 == 11) ut = atoll(sp);
if (f2 == 12) {
st = atoll(sp);
break;
}
while (*sp && *sp != ' ')
sp++;
while (*sp == ' ')
sp++;
f2++;
}
}
}
long long vcs = -1;
snprintf(path, sizeof path, "/proc/%d/task/%d/sched", pid, t->tid);
if (readfile(path, buf, sizeof buf) > 0) {
char* v = strstr(buf, "nr_voluntary_switches");
if (v) {
char* col = strchr(v, ':');
if (col) vcs = atoll(col + 1);
}
}
fprintf(f, "%d,%s,%lld,%lld,%lld,%lld,\"", t->tid, t->name, (ut + st) - t->cpu0,
(vcs >= 0 && t->vcs0 >= 0) ? vcs - t->vcs0 : -1, t->run, t->openerr);
for (int s = 0; s < MAXSY; s++)
if (t->sys[s]) fprintf(f, "%d:%lld;", s, t->sys[s]);
fprintf(f, "\"\n");
}
fclose(f);
return 0;
}