diff --git a/doc/json_schema.json b/doc/json_schema.json index a34d00fcf..e948d9285 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -4449,6 +4449,36 @@ ], "default": "cpu" }, + "showTypes": { + "description": "Specify which resource usages are detected and displayed.\nIf memory is enabled is enabled alone, only a single process snapshot is taken and `waitTime` sampling wait is skipped", + "default": [ + "cpu", + "memory", + "disk" + ], + "oneOf": [ + { + "type": "string", + "enum": [ + "cpu", + "memory", + "disk" + ] + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "cpu", + "memory", + "disk" + ] + } + } + ] + }, "processes": { "type": "integer", "description": "Number of top processes to print", @@ -4457,7 +4487,7 @@ }, "waitTime": { "type": "integer", - "description": "Wait time between process samples, in milliseconds", + "description": "Wait time between process samples, in milliseconds. Ignored when showTypes contains \"memory\" only", "minimum": 1, "default": 500 }, diff --git a/src/common/impl/commandoption.c b/src/common/impl/commandoption.c index d8922a48e..fbbcc18db 100644 --- a/src/common/impl/commandoption.c +++ b/src/common/impl/commandoption.c @@ -114,8 +114,11 @@ void ffPrepareCommandOption(FFdata* data) { #if !FF_MODULE_DISABLE_TOP case 'T': case 't': - FF_IF_MODULE_MATCH(ffTopModuleInfo.name) - ffPrepareTopProcesses(); + FF_IF_MODULE_MATCH(ffTopModuleInfo.name) { + [[gnu::cleanup(ffDestroyTopOptions)]] FFTopOptions options; + ffInitTopOptions(&options); + ffPrepareTopProcesses(options.showTypes); + } break; #endif diff --git a/src/common/impl/jsonconfig.c b/src/common/impl/jsonconfig.c index 3d26e922e..a7597ca40 100644 --- a/src/common/impl/jsonconfig.c +++ b/src/common/impl/jsonconfig.c @@ -199,7 +199,12 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) { case 't': case 'T': { if (ffStrEqualsIgnCase(type, ffTopModuleInfo.name)) { - ffPrepareTopProcesses(); + [[gnu::cleanup(ffDestroyTopOptions)]] FFTopOptions options; + ffInitTopOptions(&options); + if (module) { + ffTopModuleInfo.parseJsonObject(&options, module); + } + ffPrepareTopProcesses(options.showTypes); } break; } diff --git a/src/detection/top/top.c b/src/detection/top/top.c index 659b47f0e..5c1b8b645 100644 --- a/src/detection/top/top.c +++ b/src/detection/top/top.c @@ -4,15 +4,29 @@ static FFlist first; static double startTick; +static FFTopTypes preparedShowTypes = FF_TOP_TYPE_CPU | FF_TOP_TYPE_MEMORY | FF_TOP_TYPE_DISK; -void ffPrepareTopProcesses(void) { - if (startTick != 0) { +void ffPrepareTopProcesses(FFTopTypes showTypes) { + if ((showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) == 0) { + return; // Memory usage is instantaneous; no baseline snapshot is needed + } + + if (startTick != 0 && preparedShowTypes == showTypes) { return; // Already prepared } + if (startTick != 0) { + // The set of requested types changed; discard the stale baseline + FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, first) { + ffStrbufDestroy(&item->name); + } + ffListDestroy(&first); + } + ffListInit(&first); startTick = ffTimeGetTick(); - ffTopGetProcessSnapshot(&first); + preparedShowTypes = showTypes; + ffTopGetProcessSnapshot(&first, showTypes); } // clang-format off @@ -47,57 +61,92 @@ const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) { return nullptr; } - if (startTick == 0) { - ffPrepareTopProcesses(); - } + // Memory usage is instantaneous; when neither CPU time nor disk IO counters + // are requested, a single snapshot suffices and no sampling wait is needed. + const bool sampleOnce = (options->showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) == 0; - double elapsedTime = ffTimeGetTick() - startTick; - if (elapsedTime < (double) options->waitTime) { - ffTimeSleep(options->waitTime - (uint32_t) elapsedTime); - } - - if (first.length == 0) { - return "No processes found"; - } - - FF_LIST_AUTO_DESTROY second = ffListCreate(); - const char* error = ffTopGetProcessSnapshot(&second); - const double elapsed = ffTimeGetTick() - startTick; - - if (error || elapsed <= 0) { - FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, second) { - ffStrbufDestroy(&item->name); - } - return error ?: "Invalid process sampling interval"; - } - - ffListInitA(result, sizeof(FFTopProcessResult), first.length < options->nProcesses ? first.length : options->nProcesses); - FF_LIST_FOR_EACH (FFTopProcessSnapshot, oldItem, first) { - FFTopProcessSnapshot* newItem = nullptr; - FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, second) { - if (item->pid == oldItem->pid) { - newItem = item; - break; + if (sampleOnce) { + FF_LIST_AUTO_DESTROY snapshots = ffListCreate(); + const char* error = ffTopGetProcessSnapshot(&snapshots, options->showTypes); + if (error) { + FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, snapshots) { + ffStrbufDestroy(&item->name); } + return error; } - if (!newItem || newItem->startTime != oldItem->startTime || newItem->cpuTime < oldItem->cpuTime || newItem->bytesRead < oldItem->bytesRead || newItem->bytesWritten < oldItem->bytesWritten) { - ffStrbufDestroy(&oldItem->name); - continue; + if (snapshots.length == 0) { + return "No processes found"; } - FFTopProcessResult* item = FF_LIST_ADD(FFTopProcessResult, *result); - item->pid = newItem->pid; - item->memBytes = newItem->memBytes; - item->bytesRead = (newItem->bytesRead - oldItem->bytesRead) * 1000u / (uint64_t) elapsed; - item->bytesWritten = (newItem->bytesWritten - oldItem->bytesWritten) * 1000u / (uint64_t) elapsed; - item->cpuPercent = (double) (newItem->cpuTime - oldItem->cpuTime) / elapsed * 100.0; - item->startTime = newItem->startTime; - ffStrbufInitMove(&item->name, &oldItem->name); + FF_LIST_FOR_EACH (FFTopProcessSnapshot, snap, snapshots) { + FFTopProcessResult* item = FF_LIST_ADD(FFTopProcessResult, *result); + item->pid = snap->pid; + item->memBytes = snap->memBytes; + item->bytesRead = 0; + item->bytesWritten = 0; + item->cpuPercent = 0; + item->startTime = snap->startTime; + ffStrbufInitMove(&item->name, &snap->name); + } + } else { + if (startTick == 0 || preparedShowTypes != options->showTypes) { + ffPrepareTopProcesses(options->showTypes); + } + + double elapsedTime = ffTimeGetTick() - startTick; + if (elapsedTime < (double) options->waitTime) { + ffTimeSleep(options->waitTime - (uint32_t) elapsedTime); + } + + if (first.length == 0) { + return "No processes found"; + } + + ffListReserve(result, sizeof(FFTopProcessResult), first.length < options->nProcesses ? first.length : options->nProcesses); + + FF_LIST_AUTO_DESTROY second = ffListCreateA(sizeof(FFTopProcessSnapshot), first.length); + const char* error = ffTopGetProcessSnapshot(&second, options->showTypes); + const double elapsed = ffTimeGetTick() - startTick; + + if (error || elapsed <= 0) { + FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, second) { + ffStrbufDestroy(&item->name); + } + return error ?: "Invalid process sampling interval"; + } + + FF_LIST_FOR_EACH (FFTopProcessSnapshot, oldItem, first) { + FFTopProcessSnapshot* newItem = nullptr; + FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, second) { + if (item->pid == oldItem->pid) { + newItem = item; + break; + } + } + if (!newItem || newItem->startTime != oldItem->startTime || newItem->cpuTime < oldItem->cpuTime || newItem->bytesRead < oldItem->bytesRead || newItem->bytesWritten < oldItem->bytesWritten) { + ffStrbufDestroy(&oldItem->name); + continue; + } + + FFTopProcessResult* item = FF_LIST_ADD(FFTopProcessResult, *result); + item->pid = newItem->pid; + item->memBytes = newItem->memBytes; + item->bytesRead = (newItem->bytesRead - oldItem->bytesRead) * 1000u / (uint64_t) elapsed; + item->bytesWritten = (newItem->bytesWritten - oldItem->bytesWritten) * 1000u / (uint64_t) elapsed; + item->cpuPercent = (double) (newItem->cpuTime - oldItem->cpuTime) / elapsed * 100.0; + item->startTime = newItem->startTime; + ffStrbufInitMove(&item->name, &oldItem->name); + } + + // Reuse `second` as the baseline of the next call + ffListDestroy(&first); + ffListInitMove(&first, &second); + startTick = ffTimeGetTick(); } - const void* compare = options->sort == FF_TOP_SORT_DISK_WRITE ? (void*) compareDiskWriteResults - : options->sort == FF_TOP_SORT_DISK_READ ? (void*) compareDiskReadResults - : options->sort == FF_TOP_SORT_MEMORY ? (void*) compareMemoryResults + const void* compare = options->sort == FF_TOP_TYPE_DISK_WRITE ? (void*) compareDiskWriteResults + : options->sort == FF_TOP_TYPE_DISK_READ ? (void*) compareDiskReadResults + : options->sort == FF_TOP_TYPE_MEMORY ? (void*) compareMemoryResults : (void*) compareCpuResults; ffListSort(result, sizeof(FFTopProcessResult), (void*) compare); if (result->length > options->nProcesses) { @@ -108,10 +157,5 @@ const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) { result->length = options->nProcesses; } - // Reuse `second` as the baseline of the next call - ffListDestroy(&first); - ffListInitMove(&first, &second); - startTick = ffTimeGetTick(); - return nullptr; } diff --git a/src/detection/top/top.h b/src/detection/top/top.h index b732a1fd6..de1289b4a 100644 --- a/src/detection/top/top.h +++ b/src/detection/top/top.h @@ -5,11 +5,11 @@ typedef struct FFTopProcessResult { FFstrbuf name; - uint64_t startTime; double cpuPercent; uint64_t memBytes; uint64_t bytesRead; uint64_t bytesWritten; + uint64_t startTime; uint32_t pid; } FFTopProcessResult; @@ -24,4 +24,4 @@ typedef struct FFTopProcessSnapshot { } FFTopProcessSnapshot; const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result); -const char* ffTopGetProcessSnapshot(FFlist* snapshots); +const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes); diff --git a/src/detection/top/top_apple.c b/src/detection/top/top_apple.c index 0fdcb00be..fc8f5c998 100644 --- a/src/detection/top/top_apple.c +++ b/src/detection/top/top_apple.c @@ -5,7 +5,7 @@ #include #include -const char* ffTopGetProcessSnapshot(FFlist* snapshots) { +const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) { int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; size_t length; diff --git a/src/detection/top/top_bsd.c b/src/detection/top/top_bsd.c index 87a67bba2..620b234b1 100644 --- a/src/detection/top/top_bsd.c +++ b/src/detection/top/top_bsd.c @@ -6,7 +6,7 @@ #include #include -const char* ffTopGetProcessSnapshot(FFlist* snapshots) { +const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) { int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC }; size_t length; diff --git a/src/detection/top/top_gnu.c b/src/detection/top/top_gnu.c index 3545ddac0..9bbe37aff 100644 --- a/src/detection/top/top_gnu.c +++ b/src/detection/top/top_gnu.c @@ -1,78 +1,78 @@ -#include "top.h" - -#include -#include -#include - -const char* ffTopGetProcessSnapshot(FFlist* snapshots) { - struct ps_context* context = nullptr; - if (ps_context_create(getproc(), &context) != 0) { - return "ps_context_create(getproc()) failed"; - } - - const char* error = nullptr; - struct proc_stat_list* list = nullptr; - if (proc_stat_list_create(context, &list) != 0) { - error = "proc_stat_list_create() failed"; - goto done; - } - - if (proc_stat_list_add_all(list, nullptr, nullptr) != 0) { - error = "proc_stat_list_add_all() failed"; - goto done; - } - - // Flags that cannot be fetched for some process are simply left unset; - // each field must be checked before use. - if (proc_stat_list_set_flags(list, PSTAT_ARGS | PSTAT_STATE | PSTAT_TASK_BASIC) != 0) { - error = "proc_stat_list_set_flags() failed"; - goto done; - } - - for (unsigned i = 0; i < list->num_procs; ++i) { - struct proc_stat* stat = list->proc_stats[i]; - - if (!proc_stat_has(stat, PSTAT_PID | PSTAT_STATE)) { - continue; - } - if (proc_stat_state(stat) & PSTAT_STATE_P_ZOMBIE) { - continue; - } - - FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots); - - // There is no kernel-side comm on the Hurd; use the first argument. - ffStrbufInit(&item->name); - if (proc_stat_has(stat, PSTAT_ARGS) && proc_stat_args(stat)) { - size_t len = strnlen(proc_stat_args(stat), proc_stat_args_len(stat)); - ffStrbufAppendNS(&item->name, (uint32_t) len, proc_stat_args(stat)); - } - if (item->name.length == 0) { - ffStrbufAppendS(&item->name, "(unknown)"); - } - - item->pid = (uint32_t) proc_stat_pid(stat); - - if (proc_stat_has(stat, PSTAT_TASK_BASIC)) { - const task_basic_info_t info = proc_stat_task_basic_info(stat); - item->cpuTime = (uint64_t) info->user_time.seconds * 1000 + - (uint64_t) info->user_time.microseconds / 1000 + - (uint64_t) info->system_time.seconds * 1000 + - (uint64_t) info->system_time.microseconds / 1000; - item->memBytes = (uint64_t) info->resident_size; - } - - // Neither process start times nor storage io counters are exposed by - // the Hurd; leave them zeroed. - item->startTime = 0; - item->bytesRead = 0; - item->bytesWritten = 0; - } - -done: - if (list) { - proc_stat_list_free(list); - } - ps_context_free(context); - return error; -} +#include "top.h" + +#include +#include +#include + +const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) { + struct ps_context* context = nullptr; + if (ps_context_create(getproc(), &context) != 0) { + return "ps_context_create(getproc()) failed"; + } + + const char* error = nullptr; + struct proc_stat_list* list = nullptr; + if (proc_stat_list_create(context, &list) != 0) { + error = "proc_stat_list_create() failed"; + goto done; + } + + if (proc_stat_list_add_all(list, nullptr, nullptr) != 0) { + error = "proc_stat_list_add_all() failed"; + goto done; + } + + // Flags that cannot be fetched for some process are simply left unset; + // each field must be checked before use. + if (proc_stat_list_set_flags(list, PSTAT_ARGS | PSTAT_STATE | PSTAT_TASK_BASIC) != 0) { + error = "proc_stat_list_set_flags() failed"; + goto done; + } + + for (unsigned i = 0; i < list->num_procs; ++i) { + struct proc_stat* stat = list->proc_stats[i]; + + if (!proc_stat_has(stat, PSTAT_PID | PSTAT_STATE)) { + continue; + } + if (proc_stat_state(stat) & PSTAT_STATE_P_ZOMBIE) { + continue; + } + + FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots); + + // There is no kernel-side comm on the Hurd; use the first argument. + ffStrbufInit(&item->name); + if (proc_stat_has(stat, PSTAT_ARGS) && proc_stat_args(stat)) { + size_t len = strnlen(proc_stat_args(stat), proc_stat_args_len(stat)); + ffStrbufAppendNS(&item->name, (uint32_t) len, proc_stat_args(stat)); + } + if (item->name.length == 0) { + ffStrbufAppendS(&item->name, "(unknown)"); + } + + item->pid = (uint32_t) proc_stat_pid(stat); + + if (proc_stat_has(stat, PSTAT_TASK_BASIC)) { + const task_basic_info_t info = proc_stat_task_basic_info(stat); + item->cpuTime = (uint64_t) info->user_time.seconds * 1000 + + (uint64_t) info->user_time.microseconds / 1000 + + (uint64_t) info->system_time.seconds * 1000 + + (uint64_t) info->system_time.microseconds / 1000; + item->memBytes = (uint64_t) info->resident_size; + } + + // Neither process start times nor storage io counters are exposed by + // the Hurd; leave them zeroed. + item->startTime = 0; + item->bytesRead = 0; + item->bytesWritten = 0; + } + +done: + if (list) { + proc_stat_list_free(list); + } + ps_context_free(context); + return error; +} diff --git a/src/detection/top/top_haiku.c b/src/detection/top/top_haiku.c index 39efdb2b0..78310737a 100644 --- a/src/detection/top/top_haiku.c +++ b/src/detection/top/top_haiku.c @@ -3,7 +3,7 @@ #include #include -const char* ffTopGetProcessSnapshot(FFlist* snapshots) { +const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) { int32 cookie = 0; team_info team; while (get_next_team_info(&cookie, &team) == B_OK) { diff --git a/src/detection/top/top_linux.c b/src/detection/top/top_linux.c index 1c78c5e2b..28349a17d 100644 --- a/src/detection/top/top_linux.c +++ b/src/detection/top/top_linux.c @@ -55,7 +55,7 @@ static bool parseStat(const char* buffer, size_t length, FFTopProcessSnapshot* r return true; } -const char* ffTopGetProcessSnapshot(FFlist* snapshots) { +const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) { const long ticks = sysconf(_SC_CLK_TCK); const long pageSize = instance.state.platform.sysinfo.pageSize; FF_DEBUG("Scanning /proc: clk_tck=%ld, pageSize=%ld", ticks, pageSize); @@ -98,20 +98,24 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots) { } statBuffer[statLength] = '\0'; - ssize_t statmLength; - if ((statmLength = ffReadFileDataRelative(subfd, "statm", sizeof(statmBuffer) - 1, statmBuffer)) < 0) { - FF_DEBUG("Failed to read /proc/%s/statm: %s", entry->d_name, strerror(errno)); - continue; + ssize_t statmLength = 0; + if (showTypes & FF_TOP_TYPE_MEMORY) { + if ((statmLength = ffReadFileDataRelative(subfd, "statm", sizeof(statmBuffer) - 1, statmBuffer)) < 0) { + FF_DEBUG("Failed to read /proc/%s/statm: %s", entry->d_name, strerror(errno)); + statmLength = 0; + } + statmBuffer[statmLength] = '\0'; } - statmBuffer[statmLength] = '\0'; char ioBuffer[512]; - ssize_t ioLength; - if ((ioLength = ffReadFileDataRelative(subfd, "io", sizeof(ioBuffer) - 1, ioBuffer)) < 0) { - FF_DEBUG("Failed to read /proc/%s/io: %s", entry->d_name, strerror(errno)); - ioLength = 0; + ssize_t ioLength = 0; + if (showTypes & FF_TOP_TYPE_DISK) { + if ((ioLength = ffReadFileDataRelative(subfd, "io", sizeof(ioBuffer) - 1, ioBuffer)) < 0) { + FF_DEBUG("Failed to read /proc/%s/io: %s", entry->d_name, strerror(errno)); + ioLength = 0; + } + ioBuffer[ioLength] = '\0'; } - ioBuffer[ioLength] = '\0'; auto snapshot = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots); ffStrbufInit(&snapshot->name); @@ -121,8 +125,8 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots) { continue; } - uint64_t rssPages; - if (__builtin_expect(sscanf(statmBuffer, "%*u %" SCNu64, &rssPages) != 1, false)) { + uint64_t rssPages = 0; + if (__builtin_expect((showTypes & FF_TOP_TYPE_MEMORY) && sscanf(statmBuffer, "%*u %" SCNu64, &rssPages) != 1, false)) { FF_DEBUG("Failed to parse statm of /proc/%lu", pid); ffStrbufDestroy(&snapshot->name); --snapshots->length; diff --git a/src/detection/top/top_nbsd.c b/src/detection/top/top_nbsd.c index 3fee77049..277354ed3 100644 --- a/src/detection/top/top_nbsd.c +++ b/src/detection/top/top_nbsd.c @@ -6,7 +6,7 @@ #include #include -const char* ffTopGetProcessSnapshot(FFlist* snapshots) { +const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) { int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), INT_MAX }; size_t length; diff --git a/src/detection/top/top_obsd.c b/src/detection/top/top_obsd.c index 99d7b0a8f..f7519bd00 100644 --- a/src/detection/top/top_obsd.c +++ b/src/detection/top/top_obsd.c @@ -5,7 +5,7 @@ #include #include -const char* ffTopGetProcessSnapshot(FFlist* snapshots) { +const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) { kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr); if (!kd) { return "kvm_open() failed"; diff --git a/src/detection/top/top_sunos.c b/src/detection/top/top_sunos.c index b00ce50ce..f7e6a5f68 100644 --- a/src/detection/top/top_sunos.c +++ b/src/detection/top/top_sunos.c @@ -7,7 +7,7 @@ #include #include // DEV_BSIZE -const char* ffTopGetProcessSnapshot(FFlist* snapshots) { +const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/proc"); if (!dirp) { return "opendir(\"/proc\") failed"; @@ -52,13 +52,14 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots) { item->startTime = (uint64_t) psinfo.pr_start.tv_sec * 1000 + (uint64_t) psinfo.pr_start.tv_nsec / 1000000; - prusage_t usage; - if (ffReadFileDataRelative(subfd, "usage", sizeof(usage), &usage) == (ssize_t) sizeof(usage)) { - item->bytesRead = (uint64_t) usage.pr_inblk * DEV_BSIZE; - item->bytesWritten = (uint64_t) usage.pr_oublk * DEV_BSIZE; - } else { - item->bytesRead = 0; - item->bytesWritten = 0; + item->bytesRead = 0; + item->bytesWritten = 0; + if (showTypes & FF_TOP_TYPE_DISK) { + prusage_t usage; + if (ffReadFileDataRelative(subfd, "usage", sizeof(usage), &usage) == (ssize_t) sizeof(usage)) { + item->bytesRead = (uint64_t) usage.pr_inblk * DEV_BSIZE; + item->bytesWritten = (uint64_t) usage.pr_oublk * DEV_BSIZE; + } } } diff --git a/src/detection/top/top_windows.c b/src/detection/top/top_windows.c index b27ad962e..59a5ab98d 100644 --- a/src/detection/top/top_windows.c +++ b/src/detection/top/top_windows.c @@ -7,7 +7,7 @@ #include #include -const char* ffTopGetProcessSnapshot(FFlist* snapshots) { +const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) { FF_AUTO_FREE SYSTEM_PROCESS_INFORMATION* pstart = nullptr; // Multiple attempts in case processes change while diff --git a/src/modules/top/option.h b/src/modules/top/option.h index 3b58118a2..82a138273 100644 --- a/src/modules/top/option.h +++ b/src/modules/top/option.h @@ -3,16 +3,19 @@ #include "common/option.h" #include "common/percent.h" -typedef enum FFTopSortType { - FF_TOP_SORT_CPU = 0, - FF_TOP_SORT_MEMORY, - FF_TOP_SORT_DISK_READ, - FF_TOP_SORT_DISK_WRITE, -} FFTopSortType; +typedef enum FFTopTypes: uint8_t { + FF_TOP_TYPE_CPU = 1 << 0, + FF_TOP_TYPE_MEMORY = 1 << 1, + FF_TOP_TYPE_DISK_READ = 1 << 2, + FF_TOP_TYPE_DISK_WRITE = 1 << 3, + + FF_TOP_TYPE_DISK = FF_TOP_TYPE_DISK_READ | FF_TOP_TYPE_DISK_WRITE, +} FFTopTypes; typedef struct FFTopOptions { FFModuleArgs moduleArgs; - FFTopSortType sort; + FFTopTypes sort; + FFTopTypes showTypes; // bitfields uint32_t nProcesses; uint32_t waitTime; FFPercentageModuleConfig percent; diff --git a/src/modules/top/top.c b/src/modules/top/top.c index b36a3a1cc..60ecda8a0 100644 --- a/src/modules/top/top.c +++ b/src/modules/top/top.c @@ -11,19 +11,23 @@ static void printTopResult(FFTopOptions* options, uint32_t index, uint32_t total ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Top), total == 1 ? 0 : (uint8_t) (index + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY output = ffStrbufCreate(); - ffStrbufAppendF(&output, "%s (%u) - ", process->name.chars, process->pid); - if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { - ffStrbufAppendS(&output, "CPU "); + ffStrbufAppendF(&output, "%s (%u)", process->name.chars, process->pid); + if ((options->showTypes & FF_TOP_TYPE_CPU) && (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)) { + ffStrbufAppendS(&output, " - CPU "); ffPercentAppendNum(&output, process->cpuPercent, options->percent, false, &options->moduleArgs); } if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) { - ffStrbufAppendS(&output, " - MEM "); - ffSizeAppendNum(process->memBytes, &output); - ffStrbufAppendS(&output, " - DSK "); - ffSizeAppendNum(process->bytesRead, &output); - ffStrbufAppendS(&output, "/s / "); - ffSizeAppendNum(process->bytesWritten, &output); - ffStrbufAppendS(&output, "/s"); + if (options->showTypes & FF_TOP_TYPE_MEMORY) { + ffStrbufAppendS(&output, " - MEM "); + ffSizeAppendNum(process->memBytes, &output); + } + if (options->showTypes & FF_TOP_TYPE_DISK) { + ffStrbufAppendS(&output, " - DSK "); + ffSizeAppendNum(process->bytesRead, &output); + ffStrbufAppendS(&output, "/s / "); + ffSizeAppendNum(process->bytesWritten, &output); + ffStrbufAppendS(&output, "/s"); + } } ffStrbufPutTo(&output, stdout); } else { @@ -92,16 +96,54 @@ void ffParseTopJsonObject(FFTopOptions* options, yyjson_val* module) { if (unsafe_yyjson_equals_str(key, "sort")) { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]){ - { "cpu", FF_TOP_SORT_CPU }, - { "memory", FF_TOP_SORT_MEMORY }, - { "disk-read", FF_TOP_SORT_DISK_READ }, - { "disk-write", FF_TOP_SORT_DISK_WRITE }, + { "cpu", FF_TOP_TYPE_CPU }, + { "memory", FF_TOP_TYPE_MEMORY }, + { "disk-read", FF_TOP_TYPE_DISK_READ }, + { "disk-write", FF_TOP_TYPE_DISK_WRITE }, {}, }); if (error) { ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); } else { - options->sort = (FFTopSortType) value; + options->sort = (FFTopTypes) value; + } + continue; + } else if (unsafe_yyjson_equals_str(key, "showTypes")) { + if (yyjson_is_arr(val)) { + options->showTypes = 0; + yyjson_val* item; + size_t aidx, amax; + yyjson_arr_foreach (val, aidx, amax, item) { + int value; + const char* error = ffJsonConfigParseEnum(item, &value, (FFKeyValuePair[]){ + { "cpu", FF_TOP_TYPE_CPU }, + { "memory", FF_TOP_TYPE_MEMORY }, + { "disk", FF_TOP_TYPE_DISK }, + {}, + }); + if (error == nullptr) { + options->showTypes |= (FFTopTypes) value; + } + if (error) { + options->showTypes = FF_TOP_TYPE_CPU | FF_TOP_TYPE_MEMORY | FF_TOP_TYPE_DISK; + ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); + break; + } + } + } else { + int value; + const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]){ + { "cpu", FF_TOP_TYPE_CPU }, + { "memory", FF_TOP_TYPE_MEMORY }, + { "disk", FF_TOP_TYPE_DISK }, + {}, + }); + if (error == nullptr) { + options->showTypes = (FFTopTypes) value; + } + if (error) { + ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); + } } continue; } else if (unsafe_yyjson_equals_str(key, "processes")) { @@ -121,19 +163,25 @@ void ffParseTopJsonObject(FFTopOptions* options, yyjson_val* module) { void ffGenerateTopJsonConfig(FFTopOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_str(doc, module, "type", "top"); - switch (options->sort) { - case FF_TOP_SORT_CPU: - yyjson_mut_obj_add_str(doc, module, "sort", "cpu"); - break; - case FF_TOP_SORT_MEMORY: - yyjson_mut_obj_add_str(doc, module, "sort", "memory"); - break; - case FF_TOP_SORT_DISK_READ: - yyjson_mut_obj_add_str(doc, module, "sort", "disk-read"); - break; - case FF_TOP_SORT_DISK_WRITE: - yyjson_mut_obj_add_str(doc, module, "sort", "disk-write"); - break; + yyjson_mut_val* showTypes = yyjson_mut_arr(doc); + if (options->showTypes & FF_TOP_TYPE_CPU) { + yyjson_mut_arr_add_str(doc, showTypes, "cpu"); + } + if (options->showTypes & FF_TOP_TYPE_MEMORY) { + yyjson_mut_arr_add_str(doc, showTypes, "memory"); + } + if (options->showTypes & FF_TOP_TYPE_DISK) { + yyjson_mut_arr_add_str(doc, showTypes, "disk"); + } + yyjson_mut_obj_add_val(doc, module, "showTypes", showTypes); + if (options->sort == FF_TOP_TYPE_CPU) { + yyjson_mut_obj_add_str(doc, module, "sort", "cpu"); + } else if (options->sort == FF_TOP_TYPE_MEMORY) { + yyjson_mut_obj_add_str(doc, module, "sort", "memory"); + } else if (options->sort == FF_TOP_TYPE_DISK_READ) { + yyjson_mut_obj_add_str(doc, module, "sort", "disk-read"); + } else if (options->sort == FF_TOP_TYPE_DISK_WRITE) { + yyjson_mut_obj_add_str(doc, module, "sort", "disk-write"); } yyjson_mut_obj_add_uint(doc, module, "processes", options->nProcesses); yyjson_mut_obj_add_uint(doc, module, "waitTime", options->waitTime); @@ -153,10 +201,16 @@ bool ffGenerateTopJsonResult(FFTopOptions* options, yyjson_mut_doc* doc, yyjson_ yyjson_mut_val* item = yyjson_mut_arr_add_obj(doc, array); yyjson_mut_obj_add_strbuf(doc, item, "name", &process->name); yyjson_mut_obj_add_uint(doc, item, "pid", process->pid); - yyjson_mut_obj_add_real(doc, item, "cpuPercent", process->cpuPercent); - yyjson_mut_obj_add_uint(doc, item, "mem", process->memBytes); - yyjson_mut_obj_add_uint(doc, item, "bytesRead", process->bytesRead); - yyjson_mut_obj_add_uint(doc, item, "bytesWritten", process->bytesWritten); + if (options->showTypes & FF_TOP_TYPE_CPU) { + yyjson_mut_obj_add_real(doc, item, "cpuPercent", process->cpuPercent); + } + if (options->showTypes & FF_TOP_TYPE_MEMORY) { + yyjson_mut_obj_add_uint(doc, item, "mem", process->memBytes); + } + if (options->showTypes & FF_TOP_TYPE_DISK) { + yyjson_mut_obj_add_uint(doc, item, "bytesRead", process->bytesRead); + yyjson_mut_obj_add_uint(doc, item, "bytesWritten", process->bytesWritten); + } yyjson_mut_obj_add_uint(doc, item, "startTime", process->startTime); } @@ -168,7 +222,8 @@ bool ffGenerateTopJsonResult(FFTopOptions* options, yyjson_mut_doc* doc, yyjson_ void ffInitTopOptions(FFTopOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰍛"); - options->sort = FF_TOP_SORT_CPU; + options->sort = FF_TOP_TYPE_CPU; + options->showTypes = FF_TOP_TYPE_CPU | FF_TOP_TYPE_MEMORY | FF_TOP_TYPE_DISK; options->nProcesses = 5; options->waitTime = 500; options->compact = false; diff --git a/src/modules/top/top.h b/src/modules/top/top.h index 144130fd6..48c13d6df 100644 --- a/src/modules/top/top.h +++ b/src/modules/top/top.h @@ -2,7 +2,7 @@ #include "option.h" -void ffPrepareTopProcesses(void); +void ffPrepareTopProcesses(FFTopTypes showTypes); bool ffPrintTop(FFTopOptions* options); void ffInitTopOptions(FFTopOptions* options);