ohmu
·
2026-10-15
1#include "top.h"
2
3#include "common/time.h"
4
5static FFlist first;
6static double startTick;
7static FFTopTypes preparedTypes;
8
9void ffPrepareTopProcesses(FFTopTypes showTypes) {
10 if (startTick != 0) {
11 return; // Already prepared
12 }
13
14 if ((showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) == 0) {
15 return; // Memory usage is instantaneous; no baseline snapshot is needed
16 }
17
18 // Within one module `showTypes` cannot change between this call and `ffDetectTopProcesses`:
19 // `ffPrepareCommandOption` and `parseStructureCommand` both build the options through
20 // `initStructureModuleOptions`, which merges the module object from the JSON config, so the
21 // baseline always matches what the second snapshot collects.
22 preparedTypes = showTypes;
23 ffListInit(&first);
24 startTick = ffTimeGetTick();
25 ffTopGetProcessSnapshot(&first, showTypes);
26}
27
28// clang-format off
29static int compareCpuResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
30 if (a->cpuPercent < b->cpuPercent) return 1;
31 if (a->cpuPercent > b->cpuPercent) return -1;
32 return (int) (a->pid - b->pid);
33}
34
35static int compareMemoryResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
36 if (a->memBytes < b->memBytes) return 1;
37 if (a->memBytes > b->memBytes) return -1;
38 return (int) (a->pid - b->pid);
39}
40
41static int compareDiskReadResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
42 if (a->bytesRead < b->bytesRead) return 1;
43 if (a->bytesRead > b->bytesRead) return -1;
44 return (int) (a->pid - b->pid);
45}
46
47static int compareDiskWriteResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
48 if (a->bytesWritten < b->bytesWritten) return 1;
49 if (a->bytesWritten > b->bytesWritten) return -1;
50 return (int) (a->pid - b->pid);
51}
52
53static int compareStartTimeResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
54 if (a->startTime < b->startTime) return 1;
55 if (a->startTime > b->startTime) return -1;
56 return (int) (a->pid - b->pid);
57}
58
59static int compareThreadsResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
60 if (a->threads < b->threads) return 1;
61 if (a->threads > b->threads) return -1;
62 return (int) (a->pid - b->pid);
63}
64// clang-format on
65
66const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) {
67 ffListClear(result);
68 if (options->nProcesses == 0) {
69 return nullptr;
70 }
71
72 // A baseline collected for a different set of counters is answered here rather than in
73 // ffPrepareTopProcesses, which has no way to report an error to the caller. The module that
74 // triggered the mismatch is the one that sees it; see ffPrepareTopProcesses.
75 if (options->showTypes != preparedTypes && (options->showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) != 0) {
76 return "`top` modules with different `showTypes` cannot share a run";
77 }
78
79 // Memory usage and thread count are instantaneous; when neither CPU time nor disk IO
80 // counters are requested, a single snapshot suffices and no sampling wait is needed.
81 const bool sampleOnce = (options->showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) == 0;
82
83 if (sampleOnce) {
84 FF_LIST_AUTO_DESTROY snapshots = ffListCreate();
85 const char* error = ffTopGetProcessSnapshot(&snapshots, options->showTypes);
86 if (error) {
87 FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, snapshots) {
88 ffStrbufDestroy(&item->name);
89 }
90 return error;
91 }
92 if (snapshots.length == 0) {
93 return "No processes found";
94 }
95
96 FF_LIST_FOR_EACH (FFTopProcessSnapshot, snap, snapshots) {
97 FFTopProcessResult* item = FF_LIST_ADD(FFTopProcessResult, *result);
98 item->pid = snap->pid;
99 item->memBytes = snap->memBytes;
100 item->bytesRead = 0;
101 item->bytesWritten = 0;
102 item->cpuPercent = 0;
103 item->startTime = snap->startTime;
104 item->threads = snap->threads;
105 ffStrbufInitMove(&item->name, &snap->name);
106 }
107 } else {
108 if (startTick == 0) {
109 ffPrepareTopProcesses(options->showTypes);
110 }
111
112 double elapsedTime = ffTimeGetTick() - startTick;
113 if (elapsedTime < (double) options->waitTime) {
114 ffTimeSleep(options->waitTime - (uint32_t) elapsedTime);
115 }
116
117 if (first.length == 0) {
118 return "No processes found";
119 }
120
121 ffListReserve(result, sizeof(FFTopProcessResult), first.length < options->nProcesses ? first.length : options->nProcesses);
122
123 FF_LIST_AUTO_DESTROY second = ffListCreateA(sizeof(FFTopProcessSnapshot), first.length);
124 const char* error = ffTopGetProcessSnapshot(&second, options->showTypes);
125 const double elapsed = ffTimeGetTick() - startTick;
126
127 if (error || elapsed <= 0) {
128 FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, second) {
129 ffStrbufDestroy(&item->name);
130 }
131 return error ?: "Invalid process sampling interval";
132 }
133
134 FF_LIST_FOR_EACH (FFTopProcessSnapshot, oldItem, first) {
135 FFTopProcessSnapshot* newItem = nullptr;
136 FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, second) {
137 if (item->pid == oldItem->pid) {
138 newItem = item;
139 break;
140 }
141 }
142 if (!newItem || newItem->startTime != oldItem->startTime || newItem->cpuTime < oldItem->cpuTime || newItem->bytesRead < oldItem->bytesRead || newItem->bytesWritten < oldItem->bytesWritten) {
143 ffStrbufDestroy(&oldItem->name);
144 continue;
145 }
146
147 FFTopProcessResult* item = FF_LIST_ADD(FFTopProcessResult, *result);
148 item->pid = newItem->pid;
149 item->memBytes = newItem->memBytes;
150 item->bytesRead = (newItem->bytesRead - oldItem->bytesRead) * 1000u / (uint64_t) elapsed;
151 item->bytesWritten = (newItem->bytesWritten - oldItem->bytesWritten) * 1000u / (uint64_t) elapsed;
152 item->cpuPercent = (double) (newItem->cpuTime - oldItem->cpuTime) / elapsed * 100.0;
153 item->startTime = newItem->startTime;
154 item->threads = newItem->threads;
155 ffStrbufInitMove(&item->name, &oldItem->name);
156 }
157
158 // Reuse `second` as the baseline of the next call
159 ffListDestroy(&first);
160 ffListInitMove(&first, &second);
161 startTick = ffTimeGetTick();
162 }
163
164 const void* compare = options->sort == FF_TOP_TYPE_DISK_WRITE ? (void*) compareDiskWriteResults
165 : options->sort == FF_TOP_TYPE_DISK_READ ? (void*) compareDiskReadResults
166 : options->sort == FF_TOP_TYPE_MEMORY ? (void*) compareMemoryResults
167 : options->sort == FF_TOP_TYPE_START_TIME ? (void*) compareStartTimeResults
168 : options->sort == FF_TOP_TYPE_THREADS ? (void*) compareThreadsResults
169 : (void*) compareCpuResults;
170 ffListSort(result, sizeof(FFTopProcessResult), (void*) compare);
171 if (result->length > options->nProcesses) {
172 FFTopProcessResult* items = (FFTopProcessResult*) result->data;
173 for (uint32_t i = options->nProcesses; i < result->length; ++i) {
174 ffStrbufDestroy(&items[i].name);
175 }
176 result->length = options->nProcesses;
177 }
178
179 return nullptr;
180}