ohmu
·
2026-10-15
1#include "top.h"
2
3#include "common/debug.h"
4#include "common/io.h"
5#include "common/memrchr.h"
6#include "common/strutil.h"
7
8#include <inttypes.h>
9
10#define PF_KTHREAD 0x00200000 // defined in kernel include/linux/sched.h
11
12static bool parseStat(const char* buffer, size_t length, FFTopProcessSnapshot* result) {
13 const char* open = strchr(buffer, '(');
14 const char* close = (const char*) memrchr(buffer, ')', length);
15 if (!open || !close || close <= open || close + 2 >= buffer + length) {
16 FF_DEBUG("parseStat: malformed stat content");
17 return false;
18 }
19
20 ffStrbufSetNS(&result->name, (uint32_t) (close - open - 1), open + 1);
21
22 const char* cursor = close + 2; // skip ") "
23 if (__builtin_expect(cursor >= buffer + length, false)) {
24 return false;
25 }
26
27 char* p;
28 for (uint32_t field = 3; field <= 22 /*start time*/; ++field) {
29 switch (field) {
30 case 9: // flags
31 if ((strtoul(cursor, &p, 10) & PF_KTHREAD)) {
32 FF_DEBUG("parseStat: skipping kernel task %s", result->name.chars);
33 return false;
34 }
35 cursor = p;
36 break;
37
38 case 14: // user
39 case 15: // system
40 result->cpuTime += (uint64_t) strtoull(cursor, &p, 10);
41 cursor = p;
42 break;
43
44 case 20: // number of threads
45 result->threads = (uint32_t) strtoul(cursor, &p, 10);
46 cursor = p;
47 break;
48
49 case 22: // start time
50 result->startTime = (uint64_t) strtoull(cursor, &p, 10);
51 cursor = p;
52 break;
53
54 default:
55 while (*cursor != ' ' && __builtin_expect(*cursor != '\0', true)) {
56 ++cursor;
57 }
58 break;
59 }
60
61 ++cursor;
62 if (__builtin_expect(cursor >= buffer + length, false)) {
63 return false;
64 }
65 }
66
67 return true;
68}
69
70const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
71 const long ticks = sysconf(_SC_CLK_TCK); // sysconf returns -1 ONLY if `_SC_CLK_TCK` is an invalid name
72 const uint32_t pageSizeShift = instance.state.platform.sysinfo.pageSizeShift;
73 FF_DEBUG("Scanning /proc: clk_tck=%ld, pageSizeShift=%u", ticks, (unsigned) pageSizeShift);
74
75 FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc");
76 if (!dir) {
77 FF_DEBUG("opendir(\"/proc\") failed: %s", strerror(errno));
78 return "opendir(\"/proc\") failed";
79 }
80
81 int procfd = dirfd(dir);
82
83 struct dirent* entry;
84 char statBuffer[1024];
85 char statmBuffer[256];
86 while ((entry = readdir(dir))) {
87 if (!entry->d_name[0] || !ffCharIsDigit(entry->d_name[0])) {
88 continue;
89 }
90
91 unsigned long pid = strtoul(entry->d_name, nullptr, 10);
92 if (pid == 0 || pid > UINT32_MAX) {
93 FF_DEBUG("Skip invalid pid directory: %s", entry->d_name);
94 continue;
95 }
96
97 FF_AUTO_CLOSE_FD int subfd = openat(procfd, entry->d_name, O_RDONLY | O_DIRECTORY);
98 if (subfd < 0) {
99 FF_DEBUG("openat(/proc/%s) failed: %s", entry->d_name, strerror(errno));
100 continue;
101 }
102
103 ssize_t statLength;
104 if ((statLength = ffReadFileDataRelative(subfd, "stat", sizeof(statBuffer) - 1, statBuffer)) < 0) {
105 FF_DEBUG("Failed to read /proc/%s/stat: %s", entry->d_name, strerror(errno));
106 continue;
107 }
108 statBuffer[statLength] = '\0';
109
110 ssize_t statmLength = 0;
111 if (showTypes & FF_TOP_TYPE_MEMORY) {
112 if ((statmLength = ffReadFileDataRelative(subfd, "statm", sizeof(statmBuffer) - 1, statmBuffer)) < 0) {
113 FF_DEBUG("Failed to read /proc/%s/statm: %s", entry->d_name, strerror(errno));
114 statmLength = 0;
115 }
116 statmBuffer[statmLength] = '\0';
117 }
118
119 char ioBuffer[512];
120 ssize_t ioLength = 0;
121 if (showTypes & FF_TOP_TYPE_DISK) {
122 if ((ioLength = ffReadFileDataRelative(subfd, "io", sizeof(ioBuffer) - 1, ioBuffer)) < 0) {
123 FF_DEBUG("Failed to read /proc/%s/io: %s", entry->d_name, strerror(errno));
124 ioLength = 0;
125 }
126 ioBuffer[ioLength] = '\0';
127 }
128
129 auto snapshot = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
130 ffStrbufInit(&snapshot->name);
131 snapshot->threads = 0;
132 snapshot->cpuTime = 0;
133 snapshot->startTime = 0;
134
135 if (!parseStat(statBuffer, (size_t) statLength, snapshot)) {
136 ffStrbufDestroy(&snapshot->name);
137 --snapshots->length;
138 continue;
139 }
140
141 uint64_t rssPages = 0;
142 if (__builtin_expect((showTypes & FF_TOP_TYPE_MEMORY) && sscanf(statmBuffer, "%*u %" SCNu64, &rssPages) != 1, false)) {
143 FF_DEBUG("Failed to parse statm of /proc/%lu", pid);
144 ffStrbufDestroy(&snapshot->name);
145 --snapshots->length;
146 continue;
147 }
148
149 snapshot->pid = (uint32_t) pid;
150 snapshot->memBytes = (uint64_t) rssPages << pageSizeShift;
151 snapshot->bytesRead = 0;
152 snapshot->bytesWritten = 0;
153 if (ioLength > 0) {
154 sscanf(ioBuffer, "rchar: %*u\nwchar: %*u\nsyscr: %*u\nsyscw: %*u\nread_bytes: %" SCNu64 "\nwrite_bytes: %" SCNu64, &snapshot->bytesRead, &snapshot->bytesWritten);
155 }
156 snapshot->cpuTime = snapshot->cpuTime * 1000u / (uint64_t) ticks;
157 FF_DEBUG(
158 "Captured process %u (%s): cpuTime=%" PRIu64 "ms, mem=%" PRIu64 "B, diskRead=%" PRIu64 "B, diskWrite=%" PRIu64 "B",
159 snapshot->pid, snapshot->name.chars, snapshot->cpuTime, snapshot->memBytes, snapshot->bytesRead, snapshot->bytesWritten);
160 }
161 FF_DEBUG("Captured %u process snapshots in total", snapshots->length);
162 return nullptr;
163}