ohmu
·
2026-10-15
1#include "common/jsonconfig.h"
2#include "common/percent.h"
3#include "common/printing.h"
4#include "common/size.h"
5#include "detection/top/top.h"
6#include "modules/top/top.h"
7
8static void printTopResult(FFTopOptions* options, uint32_t index, uint32_t total, FFTopProcessResult* process) {
9 FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;
10 if (options->moduleArgs.outputFormat.length == 0) {
11 ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Top), total == 1 ? 0 : (uint8_t) (index + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
12
13 FF_STRBUF_AUTO_DESTROY output = ffStrbufCreate();
14 if (process->name.length == 0) {
15 ffStrbufAppendS(&output, "<unknown>");
16 } else {
17 ffStrbufAppend(&output, &process->name);
18 }
19 ffStrbufAppendF(&output, " (%u)", process->pid);
20 if ((options->showTypes & FF_TOP_TYPE_CPU) && (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)) {
21 ffStrbufAppendS(&output, " - CPU ");
22 ffPercentAppendNum(&output, process->cpuPercent, options->percent, false, &options->moduleArgs);
23 }
24 if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) {
25 if (options->showTypes & FF_TOP_TYPE_MEMORY) {
26 ffStrbufAppendS(&output, " - MEM ");
27 ffSizeAppendNum(process->memBytes, &output);
28 }
29 if (options->showTypes & FF_TOP_TYPE_DISK) {
30 ffStrbufAppendS(&output, " - DSK ");
31 ffSizeAppendNum(process->bytesRead, &output);
32 ffStrbufAppendS(&output, "/s / ");
33 ffSizeAppendNum(process->bytesWritten, &output);
34 ffStrbufAppendS(&output, "/s");
35 }
36 if (options->showTypes & FF_TOP_TYPE_THREADS) {
37 ffStrbufAppendF(&output, " - THR %u", process->threads);
38 }
39 }
40 ffStrbufPutTo(&output, stdout);
41 } else {
42 FF_STRBUF_AUTO_DESTROY cpuFormatted = ffStrbufCreate();
43 if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) {
44 ffPercentAppendNum(&cpuFormatted, process->cpuPercent, options->percent, false, &options->moduleArgs);
45 }
46 FF_STRBUF_AUTO_DESTROY memFormatted = ffStrbufCreate();
47 ffSizeAppendNum(process->memBytes, &memFormatted);
48 FF_STRBUF_AUTO_DESTROY diskReadFormatted = ffStrbufCreate();
49 ffSizeAppendNum(process->bytesRead, &diskReadFormatted);
50 ffStrbufAppendS(&diskReadFormatted, "/s");
51 FF_STRBUF_AUTO_DESTROY diskWriteFormatted = ffStrbufCreate();
52 ffSizeAppendNum(process->bytesWritten, &diskWriteFormatted);
53 ffStrbufAppendS(&diskWriteFormatted, "/s");
54 FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Top), total == 1 ? 0 : (uint8_t) (index + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){
55 FF_ARG(process->name, "name"),
56 FF_ARG(process->pid, "pid"),
57 FF_ARG(process->cpuPercent, "cpu"),
58 FF_ARG(process->memBytes, "mem"),
59 FF_ARG(process->bytesRead, "disk-read"),
60 FF_ARG(process->bytesWritten, "disk-write"),
61 FF_ARG(process->threads, "threads"),
62 FF_ARG(cpuFormatted, "cpu-percentage"),
63 FF_ARG(memFormatted, "mem-formatted"),
64 FF_ARG(diskReadFormatted, "disk-read-formatted"),
65 FF_ARG(diskWriteFormatted, "disk-write-formatted"),
66 }));
67 }
68}
69
70bool ffPrintTop(FFTopOptions* options) {
71 FF_LIST_AUTO_DESTROY results = ffListCreate();
72 const char* error = ffDetectTopProcesses(options, &results);
73 if (error) {
74 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
75 return false;
76 }
77
78 if (!options->compact) {
79 FF_LIST_FOR_EACH (FFTopProcessResult, process, results) {
80 printTopResult(options, (uint32_t) (process - (FFTopProcessResult*) results.data), results.length, process);
81 }
82 } else {
83 ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
84 FF_LIST_FOR_EACH (FFTopProcessResult, process, results) {
85 if ((void*) process != results.data) {
86 putchar(' ');
87 }
88 ffStrbufWriteTo(&process->name, stdout);
89 }
90 putchar('\n');
91 }
92
93 FF_LIST_FOR_EACH (FFTopProcessResult, item, results) {
94 ffStrbufDestroy(&item->name);
95 }
96 return true;
97}
98
99void ffParseTopJsonObject(FFTopOptions* options, yyjson_val* module) {
100 yyjson_val *key, *val;
101 size_t idx, max;
102 yyjson_obj_foreach (module, idx, max, key, val) {
103 if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) {
104 continue;
105 }
106 if (unsafe_yyjson_equals_str(key, "sort")) {
107 int value;
108 const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]){
109 { "cpu", FF_TOP_TYPE_CPU },
110 { "memory", FF_TOP_TYPE_MEMORY },
111 { "disk-read", FF_TOP_TYPE_DISK_READ },
112 { "disk-write", FF_TOP_TYPE_DISK_WRITE },
113 { "start-time", FF_TOP_TYPE_START_TIME },
114 { "threads", FF_TOP_TYPE_THREADS },
115 {},
116 });
117 if (error) {
118 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
119 } else {
120 options->sort = (FFTopTypes) value;
121 }
122 continue;
123 } else if (unsafe_yyjson_equals_str(key, "showTypes")) {
124 if (yyjson_is_arr(val)) {
125 options->showTypes = 0;
126 yyjson_val* item;
127 size_t aidx, amax;
128 yyjson_arr_foreach (val, aidx, amax, item) {
129 int value;
130 const char* error = ffJsonConfigParseEnum(item, &value, (FFKeyValuePair[]){
131 { "cpu", FF_TOP_TYPE_CPU },
132 { "memory", FF_TOP_TYPE_MEMORY },
133 { "disk", FF_TOP_TYPE_DISK },
134 { "threads", FF_TOP_TYPE_THREADS },
135 {},
136 });
137 if (error == nullptr) {
138 options->showTypes |= (FFTopTypes) value;
139 }
140 if (error) {
141 options->showTypes = FF_TOP_TYPE_CPU | FF_TOP_TYPE_MEMORY | FF_TOP_TYPE_DISK;
142 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
143 break;
144 }
145 }
146 } else {
147 int value;
148 const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]){
149 { "cpu", FF_TOP_TYPE_CPU },
150 { "memory", FF_TOP_TYPE_MEMORY },
151 { "disk", FF_TOP_TYPE_DISK },
152 { "threads", FF_TOP_TYPE_THREADS },
153 {},
154 });
155 if (error == nullptr) {
156 options->showTypes = (FFTopTypes) value;
157 }
158 if (error) {
159 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
160 }
161 }
162 continue;
163 } else if (unsafe_yyjson_equals_str(key, "processes")) {
164 options->nProcesses = (uint32_t) yyjson_get_uint(val);
165 } else if (unsafe_yyjson_equals_str(key, "waitTime")) {
166 options->waitTime = (uint32_t) yyjson_get_uint(val);
167 } else if (unsafe_yyjson_equals_str(key, "compact")) {
168 options->compact = yyjson_get_bool(val);
169 } else if (ffPercentParseJsonObject(key, val, &options->percent)) {
170 continue;
171 } else {
172 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
173 }
174 }
175}
176
177[[gnu::cold]]
178void ffGenerateTopJsonConfig(FFTopOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
179 ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
180 yyjson_mut_obj_add_str(doc, module, "type", "top");
181 yyjson_mut_val* showTypes = yyjson_mut_arr(doc);
182 if (options->showTypes & FF_TOP_TYPE_CPU) {
183 yyjson_mut_arr_add_str(doc, showTypes, "cpu");
184 }
185 if (options->showTypes & FF_TOP_TYPE_MEMORY) {
186 yyjson_mut_arr_add_str(doc, showTypes, "memory");
187 }
188 if (options->showTypes & FF_TOP_TYPE_DISK) {
189 yyjson_mut_arr_add_str(doc, showTypes, "disk");
190 }
191 if (options->showTypes & FF_TOP_TYPE_THREADS) {
192 yyjson_mut_arr_add_str(doc, showTypes, "threads");
193 }
194 yyjson_mut_obj_add_val(doc, module, "showTypes", showTypes);
195 if (options->sort == FF_TOP_TYPE_CPU) {
196 yyjson_mut_obj_add_str(doc, module, "sort", "cpu");
197 } else if (options->sort == FF_TOP_TYPE_MEMORY) {
198 yyjson_mut_obj_add_str(doc, module, "sort", "memory");
199 } else if (options->sort == FF_TOP_TYPE_DISK_READ) {
200 yyjson_mut_obj_add_str(doc, module, "sort", "disk-read");
201 } else if (options->sort == FF_TOP_TYPE_DISK_WRITE) {
202 yyjson_mut_obj_add_str(doc, module, "sort", "disk-write");
203 } else if (options->sort == FF_TOP_TYPE_START_TIME) {
204 yyjson_mut_obj_add_str(doc, module, "sort", "start-time");
205 } else if (options->sort == FF_TOP_TYPE_THREADS) {
206 yyjson_mut_obj_add_str(doc, module, "sort", "threads");
207 }
208 yyjson_mut_obj_add_uint(doc, module, "processes", options->nProcesses);
209 yyjson_mut_obj_add_uint(doc, module, "waitTime", options->waitTime);
210 yyjson_mut_obj_add_bool(doc, module, "compact", options->compact);
211 ffPercentGenerateJsonConfig(doc, module, options->percent);
212}
213
214bool ffGenerateTopJsonResult(FFTopOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
215 FF_LIST_AUTO_DESTROY results = ffListCreate();
216 const char* error = ffDetectTopProcesses(options, &results);
217 if (error) {
218 yyjson_mut_obj_add_str(doc, module, "error", error);
219 return false;
220 }
221 yyjson_mut_val* array = yyjson_mut_obj_add_arr(doc, module, "result");
222 FF_LIST_FOR_EACH (FFTopProcessResult, process, results) {
223 yyjson_mut_val* item = yyjson_mut_arr_add_obj(doc, array);
224 yyjson_mut_obj_add_strbuf(doc, item, "name", &process->name);
225 yyjson_mut_obj_add_uint(doc, item, "pid", process->pid);
226 if (options->showTypes & FF_TOP_TYPE_CPU) {
227 yyjson_mut_obj_add_real(doc, item, "cpuPercent", process->cpuPercent);
228 }
229 if (options->showTypes & FF_TOP_TYPE_MEMORY) {
230 yyjson_mut_obj_add_uint(doc, item, "mem", process->memBytes);
231 }
232 if (options->showTypes & FF_TOP_TYPE_DISK) {
233 yyjson_mut_obj_add_uint(doc, item, "bytesRead", process->bytesRead);
234 yyjson_mut_obj_add_uint(doc, item, "bytesWritten", process->bytesWritten);
235 }
236 if (options->showTypes & FF_TOP_TYPE_THREADS) {
237 yyjson_mut_obj_add_uint(doc, item, "threads", process->threads);
238 }
239 yyjson_mut_obj_add_uint(doc, item, "startTime", process->startTime);
240 }
241
242 FF_LIST_FOR_EACH (FFTopProcessResult, item, results) {
243 ffStrbufDestroy(&item->name);
244 }
245 return true;
246}
247
248void ffInitTopOptions(FFTopOptions* options) {
249 ffOptionInitModuleArg(&options->moduleArgs, "");
250 options->sort = FF_TOP_TYPE_CPU;
251 options->showTypes = FF_TOP_TYPE_CPU | FF_TOP_TYPE_MEMORY
252#if !__GNU__ && !__HAIKU__
253 | FF_TOP_TYPE_DISK
254#endif
255 ;
256 options->nProcesses = 5;
257 options->waitTime = 250;
258 options->compact = false;
259 options->percent = (FFPercentageModuleConfig){ 50, 80, 0 };
260}
261
262void ffDestroyTopOptions(FFTopOptions* options) {
263 ffOptionDestroyModuleArg(&options->moduleArgs);
264}
265
266FFModuleBaseInfo ffTopModuleInfo = {
267 .name = "Top",
268 .description = "Print processes with the highest CPU, memory or disk I/O usage",
269 .displayName = {
270 .en = "Top Processes",
271 .ar = "أعلى العمليات",
272 .cs = "Nejvytíženější procesy",
273 .de = "Top-Prozesse",
274 .es = "Procesos principales",
275 .fi = "Raskaimmat Prosessit",
276 .fr = "Processus principaux",
277 .gl = "Procesos principais",
278 .he = "התהליכים המובילים",
279 .id = "Proses teratas",
280 .it = "Processi principali",
281 .ja = "上位プロセス",
282 .ko = "상위 프로세스",
283 .pl = "Najaktywniejsze procesy",
284 .pt = "Processos principais",
285 .ru = "Топ процессов",
286 .tr = "En Yoğun Süreçler",
287 .uk = "Найактивніші процеси",
288 .vi = "Tiến trình hàng đầu",
289 .zh_CN = "进程排行",
290 .zh_TW = "進程排行",
291 },
292 .initOptions = (void*) ffInitTopOptions,
293 .destroyOptions = (void*) ffDestroyTopOptions,
294 .parseJsonObject = (void*) ffParseTopJsonObject,
295 .printModule = (void*) ffPrintTop,
296 .generateJsonResult = (void*) ffGenerateTopJsonResult,
297 .generateJsonConfig = (void*) ffGenerateTopJsonConfig,
298 .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]){
299 { "Process name", "name" },
300 { "Process ID", "pid" },
301 { "CPU usage", "cpu" },
302 { "Memory usage (RSS) in bytes", "mem" },
303 { "Disk read bytes per second (0 if unsupported)", "disk-read" },
304 { "Disk write bytes per second (0 if unsupported)", "disk-write" },
305 { "Number of threads", "threads" },
306 { "CPU usage percentage", "cpu-percentage" },
307 { "Memory usage (RSS) formatted", "mem-formatted" },
308 { "Disk read formatted", "disk-read-formatted" },
309 { "Disk write formatted", "disk-write-formatted" },
310 })),
311 .defaultOrder = 37,
312};