mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
195 lines
7.4 KiB
C
195 lines
7.4 KiB
C
// Device-side per-thread CPU sampler for the perf matrix (P14).
|
|
//
|
|
// Usage (as root, pinned to a little core):
|
|
// taskset 01 cpusampler <package> <interval_ms> <watch_file> <out.csv> [max_s]
|
|
//
|
|
// Every interval it reads /proc/<pid>/task/<tid>/schedstat (on-CPU ns, not the 10 ms tick
|
|
// counters of stat) for every thread of every process whose cmdline starts with <package>
|
|
// (the replay process) and of every libMobileGLServer.so process (spawn/tcp), and keeps the last
|
|
// kRingSeconds of samples in memory. When <watch_file> (benchmark.json) appears it dumps the
|
|
// ring to <out.csv> and exits; the host cuts the benchmark's tail window out of it with the
|
|
// file's mtime as the window end (make_report.py / cpu_window.py). Timestamps are
|
|
// CLOCK_REALTIME so they compare with st_mtim.
|
|
#define _GNU_SOURCE
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#define MAXP 16
|
|
#define MAXT 512
|
|
#define RING 4096
|
|
|
|
struct Thread {
|
|
int pid, tid;
|
|
char comm[24];
|
|
};
|
|
struct Sample {
|
|
long long t; // CLOCK_REALTIME ns
|
|
int index; // into threads[]
|
|
long long cpu; // schedstat on-CPU ns
|
|
};
|
|
|
|
static struct Thread threads[MAXT];
|
|
static int threadCount;
|
|
static struct Sample* ring;
|
|
static long long ringCap, ringHead, ringSize;
|
|
|
|
static long long NowNs(void) {
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
return (long long)ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
|
}
|
|
|
|
static int ReadSmall(const char* path, char* buf, int n) {
|
|
int fd = open(path, O_RDONLY);
|
|
if (fd < 0) return -1;
|
|
int r = (int)read(fd, buf, n - 1);
|
|
close(fd);
|
|
if (r < 0) return -1;
|
|
buf[r] = 0;
|
|
return r;
|
|
}
|
|
|
|
// Pseudo-threads: pid -1 carries the Adreno kgsl cumulative busy time and the GPU / CPU clocks,
|
|
// so the host can say whether a window was GPU-bound and that the pin held.
|
|
static int Pseudo(const char* name) {
|
|
for (int i = 0; i < threadCount; ++i)
|
|
if (threads[i].pid == -1 && strcmp(threads[i].comm, name) == 0) return i;
|
|
if (threadCount >= MAXT) return -1;
|
|
threads[threadCount].pid = -1;
|
|
threads[threadCount].tid = -1;
|
|
snprintf(threads[threadCount].comm, sizeof threads[threadCount].comm, "%s", name);
|
|
return threadCount++;
|
|
}
|
|
|
|
static int FindThread(int pid, int tid) {
|
|
for (int i = 0; i < threadCount; ++i)
|
|
if (threads[i].tid == tid && threads[i].pid == pid) return i;
|
|
if (threadCount >= MAXT) return -1;
|
|
char path[96], buf[64];
|
|
snprintf(path, sizeof path, "/proc/%d/task/%d/comm", pid, tid);
|
|
threads[threadCount].pid = pid;
|
|
threads[threadCount].tid = tid;
|
|
threads[threadCount].comm[0] = 0;
|
|
if (ReadSmall(path, buf, sizeof buf) > 0) {
|
|
buf[strcspn(buf, "\n,")] = 0;
|
|
snprintf(threads[threadCount].comm, sizeof threads[threadCount].comm, "%s", buf);
|
|
}
|
|
return threadCount++;
|
|
}
|
|
|
|
static int ScanPids(const char* pkg, int* pids) {
|
|
int n = 0;
|
|
DIR* d = opendir("/proc");
|
|
if (!d) return 0;
|
|
struct dirent* e;
|
|
size_t len = strlen(pkg);
|
|
char path[64], buf[256];
|
|
while ((e = readdir(d)) && n < MAXP) {
|
|
if (e->d_name[0] < '0' || e->d_name[0] > '9') continue;
|
|
snprintf(path, sizeof path, "/proc/%s/cmdline", e->d_name);
|
|
if (ReadSmall(path, buf, sizeof buf) <= 0) continue;
|
|
// The replay process (and its :services), plus the render-server processes the spawn and
|
|
// tcp arms exec (their cmdline is the server library's path in the package's install dir).
|
|
if ((strncmp(buf, pkg, len) == 0 && (buf[len] == 0 || buf[len] == ':')) ||
|
|
(strstr(buf, "libMobileGLServer.so") != NULL && strstr(buf, pkg) != NULL))
|
|
pids[n++] = atoi(e->d_name);
|
|
}
|
|
closedir(d);
|
|
return n;
|
|
}
|
|
|
|
static void Push(long long t, int index, long long cpu) {
|
|
struct Sample* s = &ring[(ringHead + ringSize) % ringCap];
|
|
if (ringSize == ringCap) ringHead = (ringHead + 1) % ringCap;
|
|
else ++ringSize;
|
|
s->t = t;
|
|
s->index = index;
|
|
s->cpu = cpu;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
if (argc < 5) {
|
|
fprintf(stderr, "usage: cpusampler <package> <interval_ms> <watch_file> <out.csv> [max_s]\n");
|
|
return 2;
|
|
}
|
|
const char* pkg = argv[1];
|
|
const int intervalMs = atoi(argv[2]) > 0 ? atoi(argv[2]) : 10;
|
|
const char* watch = argv[3];
|
|
const char* out = argv[4];
|
|
const long long maxNs = (argc > 5 ? atoll(argv[5]) : 1800) * 1000000000LL;
|
|
const long long ringSeconds = 30;
|
|
ringCap = (ringSeconds * 1000 / intervalMs + 1) * 128;
|
|
ring = calloc((size_t)ringCap, sizeof *ring);
|
|
if (!ring) return 1;
|
|
const long long start = NowNs();
|
|
int pids[MAXP], pidCount = 0;
|
|
long long iteration = 0;
|
|
char path[96], buf[512];
|
|
struct stat st;
|
|
long long mtime = -1;
|
|
for (;;) {
|
|
const long long now = NowNs();
|
|
if (now - start > maxNs) break;
|
|
if (iteration % 25 == 0 || pidCount == 0) pidCount = ScanPids(pkg, pids);
|
|
for (int p = 0; p < pidCount; ++p) {
|
|
snprintf(path, sizeof path, "/proc/%d/task", pids[p]);
|
|
DIR* d = opendir(path);
|
|
if (!d) continue;
|
|
struct dirent* e;
|
|
while ((e = readdir(d))) {
|
|
if (e->d_name[0] == '.') continue;
|
|
const int tid = atoi(e->d_name);
|
|
snprintf(path, sizeof path, "/proc/%d/task/%d/schedstat", pids[p], tid);
|
|
if (ReadSmall(path, buf, sizeof buf) <= 0) continue;
|
|
const int index = FindThread(pids[p], tid);
|
|
if (index >= 0) Push(now, index, atoll(buf));
|
|
}
|
|
closedir(d);
|
|
}
|
|
// gpu_clock_stats: cumulative busy time per power level; its sum is busy-since-boot.
|
|
// (gpubusy is a sliding ~1 s window, not a counter.)
|
|
if (ReadSmall("/sys/class/kgsl/kgsl-3d0/gpu_clock_stats", buf, sizeof buf) > 0) {
|
|
long long sum = 0;
|
|
for (char* q = buf; *q;) {
|
|
char* end = q;
|
|
const long long v = strtoll(q, &end, 10);
|
|
if (end == q) break;
|
|
sum += v;
|
|
q = end;
|
|
}
|
|
Push(now, Pseudo("gpubusy_us"), sum);
|
|
}
|
|
if (iteration % 10 == 0) {
|
|
if (ReadSmall("/sys/class/kgsl/kgsl-3d0/devfreq/cur_freq", buf, sizeof buf) > 0)
|
|
Push(now, Pseudo("gpufreq"), atoll(buf));
|
|
if (ReadSmall("/sys/devices/system/cpu/cpufreq/policy7/scaling_cur_freq", buf, sizeof buf) > 0)
|
|
Push(now, Pseudo("cpu7freq"), atoll(buf));
|
|
if (ReadSmall("/sys/devices/system/cpu/cpufreq/policy2/scaling_cur_freq", buf, sizeof buf) > 0)
|
|
Push(now, Pseudo("cpu2freq"), atoll(buf));
|
|
}
|
|
++iteration;
|
|
if (stat(watch, &st) == 0 && (long long)st.st_mtim.tv_sec * 1000000000LL + st.st_mtim.tv_nsec > start) {
|
|
mtime = (long long)st.st_mtim.tv_sec * 1000000000LL + st.st_mtim.tv_nsec;
|
|
break;
|
|
}
|
|
usleep((useconds_t)intervalMs * 1000);
|
|
}
|
|
FILE* f = fopen(out, "w");
|
|
if (!f) return 1;
|
|
fprintf(f, "# watch_mtime_ns=%lld interval_ms=%d\n", mtime, intervalMs);
|
|
fprintf(f, "t_ns,pid,tid,comm,cpu_ns\n");
|
|
for (long long i = 0; i < ringSize; ++i) {
|
|
const struct Sample* s = &ring[(ringHead + i) % ringCap];
|
|
const struct Thread* th = &threads[s->index];
|
|
fprintf(f, "%lld,%d,%d,%s,%lld\n", s->t, th->pid, th->tid, th->comm, s->cpu);
|
|
}
|
|
fclose(f);
|
|
return mtime >= 0 ? 0 : 3;
|
|
}
|