master ohmu/fastfetch-openbsd / src / modules / top / top.c
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};