Top: adds new module

This commit is contained in:
Carter Li
2026-08-24 16:05:36 +08:00
committed by 李通洲
parent 5edda068f2
commit 83cc7a94a4
22 changed files with 1183 additions and 2 deletions
+13
View File
@@ -484,6 +484,7 @@ set(LIBFASTFETCH_SRC
src/detection/physicalmemory/physicalmemory.c
src/detection/publicip/publicip.c
src/detection/terminaltheme/terminaltheme.c
src/detection/top/top.c
src/detection/terminalfont/terminalfont.c
src/detection/terminalshell/terminalshell.c
src/detection/version/version.c
@@ -559,6 +560,7 @@ set(LIBFASTFETCH_SRC
src/modules/terminalsize/terminalsize.c
src/modules/theme/theme.c
src/modules/title/title.c
src/modules/top/top.c
src/modules/tpm/tpm.c
src/modules/uptime/uptime.c
src/modules/users/users.c
@@ -651,6 +653,7 @@ if(LINUX)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/top/top_linux.c
src/detection/tpm/tpm_linux.c
src/detection/uptime/uptime_linux.c
src/detection/users/users_linux.c
@@ -822,11 +825,13 @@ elseif(FreeBSD)
if(DragonFly)
list(APPEND LIBFASTFETCH_SRC
src/detection/bluetooth/bluetooth_nosupport.c
src/detection/top/top_dbsd.c
src/detection/wifi/wifi_nosupport.c
)
else()
list(APPEND LIBFASTFETCH_SRC
src/detection/bluetooth/bluetooth_bsd.c
src/detection/top/top_bsd.c
src/detection/wifi/wifi_bsd.c
)
endif()
@@ -904,6 +909,7 @@ elseif(NetBSD)
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/tpm/tpm_nosupport.c
src/detection/top/top_nbsd.c
src/detection/uptime/uptime_bsd.c
src/detection/users/users_linux.c
src/detection/wallpaper/wallpaper_linux.c
@@ -990,6 +996,7 @@ elseif(OpenBSD)
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/tpm/tpm_nosupport.c
src/detection/top/top_obsd.c
src/detection/uptime/uptime_bsd.c
src/detection/users/users_obsd.c
src/detection/wallpaper/wallpaper_linux.c
@@ -1057,6 +1064,7 @@ elseif(APPLE)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_apple.c
src/detection/top/top_apple.c
src/detection/tpm/tpm_apple.c
src/detection/uptime/uptime_bsd.c
src/detection/users/users_linux.c
@@ -1149,6 +1157,7 @@ elseif(WIN32)
src/detection/terminalshell/terminalshell_windows.c
src/detection/terminalsize/terminalsize_windows.c
src/detection/theme/theme_windows.c
src/detection/top/top_windows.c
src/detection/tpm/tpm_windows.c
src/detection/uptime/uptime_windows.c
src/detection/users/users_windows.c
@@ -1231,6 +1240,7 @@ elseif(SunOS)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/top/top_sunos.c
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_sunos.c
src/detection/users/users_linux.c
@@ -1301,6 +1311,7 @@ elseif(Haiku)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_nosupport.c
src/detection/top/top_haiku.c
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_haiku.c
src/detection/users/users_linux.c
@@ -1385,6 +1396,7 @@ elseif(GNU)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/top/top_gnu.c
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_linux.c
src/detection/users/users_linux.c
@@ -1995,6 +2007,7 @@ elseif(SunOS)
elseif(GNU)
target_link_libraries(libfastfetch
PRIVATE "m"
PRIVATE "ps"
)
elseif(ANDROID)
target_link_libraries(libfastfetch
+75 -2
View File
@@ -322,7 +322,7 @@
"type": "string"
},
"cpuFormat": {
"description": "Output format for the `CPU` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {vendor}: Vendor\n 3. {cores-physical}: Physical core count\n 4. {cores-logical}: Logical core count\n 5. {cores-online}: Online core count\n 6. {freq-base}: Base frequency (formatted)\n 7. {freq-max}: Max frequency (formatted)\n 8. {temperature}: Temperature (formatted)\n 9. {core-types}: Logical core count grouped by frequency\n 10. {packages}: Processor package count\n 11. {march}: CPU microarchitecture\n 12. {numa-nodes}: NUMA node count\n 13. {code-name}: CPU code name\n 14. {technology}: CPU technology",
"description": "Output format for the `CPU` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {vendor}: Vendor\n 3. {cores-physical}: Physical core count\n 4. {cores-logical}: Logical core count\n 5. {cores-online}: Online core count\n 6. {freq-base}: Base frequency (formatted)\n 7. {freq-max}: Max frequency (formatted)\n 8. {temperature}: Temperature (formatted)\n 9. {core-types}: Logical core count grouped by frequency\n 10. {packages}: Processor package count\n 11. {march}: CPU microarchitecture\n 12. {numa-nodes}: NUMA node count",
"type": "string"
},
"cpucacheFormat": {
@@ -505,6 +505,10 @@
"description": "Output format for the `Title` module. See Wiki for formatting syntax\n 1. {user-name}: User name\n 2. {host-name}: Host name\n 3. {home-dir}: Home directory\n 4. {exe-path}: Executable path of current process\n 5. {user-shell}: User's default shell\n 6. {user-name-colored}: User name (colored)\n 7. {at-symbol-colored}: @ symbol (colored)\n 8. {host-name-colored}: Host name (colored)\n 9. {full-user-name}: Full user name\n 10. {user-id}: UID (*nix) / SID (Windows)\n 11. {pid}: PID of current process\n 12. {cwd}: CWD with home dir replaced by `~`",
"type": "string"
},
"topFormat": {
"description": "Output format for the `Top` module. See Wiki for formatting syntax\n 1. {name}: Process name\n 2. {path}: Executable path\n 3. {pid}: Process ID\n 4. {cpu}: CPU usage\n 5. {mem}: Memory usage (RSS) in bytes\n 6. {disk-read}: Disk read bytes per second\n 7. {disk-write}: Disk write bytes per second\n 8. {cpu-percentage}: CPU usage percentage\n 9. {mem-formatted}: Memory usage (RSS) formatted\n 10. {disk-read-formatted}: Disk read formatted\n 11. {disk-write-formatted}: Disk write formatted",
"type": "string"
},
"themeFormat": {
"description": "Output format for the `Theme` module. See Wiki for formatting syntax\n 1. {theme1}: Theme part 1\n 2. {theme2}: Theme part 2",
"type": "string"
@@ -542,7 +546,7 @@
"type": "string"
},
"wifiFormat": {
"description": "Output format for the `Wifi` module. See Wiki for formatting syntax\n 1. {inf-desc}: Interface description\n 2. {inf-status}: Interface status\n 3. {status}: Connection status\n 4. {ssid}: Connection SSID\n 5. {bssid}: Connection BSSID\n 6. {protocol}: Connection protocol\n 7. {signal-quality}: Connection signal quality (percentage num)\n 8. {rx-rate}: Connection RX rate\n 9. {tx-rate}: Connection TX rate\n 10. {security}: Connection Security algorithm\n 11. {signal-quality-bar}: Connection signal quality (percentage bar)\n 12. {channel}: Connection channel number\n 13. {band}: Connection channel band in GHz",
"description": "Output format for the `Wifi` module. See Wiki for formatting syntax\n 1. {inf-desc}: Interface description\n 2. {inf-status}: Interface status\n 3. {status}: Connection status\n 4. {ssid}: Connection SSID\n 5. {bssid}: Connection BSSID\n 6. {protocol}: Connection protocol\n 7. {signal-quality}: Connection signal quality (percentage num)\n 8. {rx-rate}: Connection RX rate\n 9. {tx-rate}: Connection TX rate\n 10. {security}: Connection Security algorithm\n 11. {signal-quality-bar}: Connection signal quality (percentage bar)\n 12. {channel}: Connection channel number\n 13. {channel-width}: Connection channel width in MHz\n 14. {band}: Connection channel band in GHz",
"type": "string"
},
"wmthemeFormat": {
@@ -1592,6 +1596,7 @@
"poweradapter",
"processes",
"publicip",
"top",
"separator",
"shell",
"sound",
@@ -4424,6 +4429,74 @@
}
}
},
{
"title": "Top",
"type": "object",
"additionalProperties": false,
"properties": {
"type": {
"const": "top",
"description": "Print processes with the highest CPU or memory usage"
},
"sort": {
"type": "string",
"description": "Sort processes by CPU, memory or disk IO usage",
"enum": [
"cpu",
"memory",
"disk-read",
"disk-write"
],
"default": "cpu"
},
"processes": {
"type": "integer",
"description": "Number of top processes to print",
"minimum": 1,
"default": 5
},
"waitTime": {
"type": "integer",
"description": "Wait time between process samples, in milliseconds",
"minimum": 1,
"default": 500
},
"compact": {
"type": "boolean",
"description": "Print all process on a single line",
"default": false
},
"kernelTask": {
"type": "boolean",
"description": "Whether to include kernel tasks (kworker, System, kernel_task) in the process list",
"default": false
},
"percent": {
"$ref": "#/$defs/percent"
},
"key": {
"$ref": "#/$defs/key"
},
"keyColor": {
"$ref": "#/$defs/keyColor"
},
"keyIcon": {
"$ref": "#/$defs/keyIcon"
},
"keyWidth": {
"$ref": "#/$defs/keyWidth"
},
"outputColor": {
"$ref": "#/$defs/outputColor"
},
"format": {
"$ref": "#/$defs/topFormat"
},
"condition": {
"$ref": "#/$defs/conditions"
}
}
},
{
"title": "TPM",
"type": "object",
+8
View File
@@ -111,6 +111,14 @@ void ffPrepareCommandOption(FFdata* data) {
break;
#endif
#if !FF_MODULE_DISABLE_TOP
case 'T':
case 't':
FF_IF_MODULE_MATCH(ffTopModuleInfo.name)
ffPrepareTopProcesses();
break;
#endif
#if !FF_MODULE_DISABLE_WEATHER
case 'W':
case 'w':
+10
View File
@@ -195,6 +195,16 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
}
#endif
#if !FF_MODULE_DISABLE_TOP
case 't':
case 'T': {
if (ffStrEqualsIgnCase(type, ffTopModuleInfo.name)) {
ffPrepareTopProcesses();
}
break;
}
#endif
#if !FF_MODULE_DISABLE_WEATHER
case 'w':
case 'W': {
+117
View File
@@ -0,0 +1,117 @@
#include "top.h"
#include "common/time.h"
static FFlist first;
static double startTick;
void ffPrepareTopProcesses(void) {
if (startTick != 0) {
return; // Already prepared
}
ffListInit(&first);
startTick = ffTimeGetTick();
ffTopGetProcessSnapshot(&first);
}
// clang-format off
static int compareCpuResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
if (a->cpuPercent < b->cpuPercent) return 1;
if (a->cpuPercent > b->cpuPercent) return -1;
return (int) (a->pid - b->pid);
}
static int compareMemoryResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
if (a->memBytes < b->memBytes) return 1;
if (a->memBytes > b->memBytes) return -1;
return (int) (a->pid - b->pid);
}
static int compareDiskReadResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
if (a->bytesRead < b->bytesRead) return 1;
if (a->bytesRead > b->bytesRead) return -1;
return (int) (a->pid - b->pid);
}
static int compareDiskWriteResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
if (a->bytesWritten < b->bytesWritten) return 1;
if (a->bytesWritten > b->bytesWritten) return -1;
return (int) (a->pid - b->pid);
}
// clang-format on
const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) {
ffListClear(result);
if (options->nProcesses == 0) {
return nullptr;
}
if (startTick == 0) {
ffPrepareTopProcesses();
}
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 (!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);
}
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
: (void*) compareCpuResults;
ffListSort(result, sizeof(FFTopProcessResult), (void*) compare);
if (result->length > options->nProcesses) {
FFTopProcessResult* items = (FFTopProcessResult*) result->data;
for (uint32_t i = options->nProcesses; i < result->length; ++i) {
ffStrbufDestroy(&items[i].name);
}
result->length = options->nProcesses;
}
// Reuse `second` as the baseline of the next call
ffListDestroy(&first);
ffListInitMove(&first, &second);
startTick = ffTimeGetTick();
return nullptr;
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "fastfetch.h"
#include "modules/top/option.h"
typedef struct FFTopProcessResult {
FFstrbuf name;
uint64_t startTime;
double cpuPercent;
uint64_t memBytes;
uint64_t bytesRead;
uint64_t bytesWritten;
uint32_t pid;
} FFTopProcessResult;
typedef struct FFTopProcessSnapshot {
FFstrbuf name; // comm
uint64_t cpuTime; // user_time + system_time
uint64_t memBytes; // RSS
uint64_t bytesRead; // Physical storage read
uint64_t bytesWritten; // Physical storage written
uint64_t startTime; // Differs between platforms
uint32_t pid;
} FFTopProcessSnapshot;
const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result);
const char* ffTopGetProcessSnapshot(FFlist* snapshots);
+57
View File
@@ -0,0 +1,57 @@
#include "top.h"
#include "common/mallocHelper.h"
#include <errno.h>
#include <sys/sysctl.h>
#include <libproc.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
FF_AUTO_FREE struct kinfo_proc* processes = nullptr;
for (int attempts = 0;; ++attempts) {
length += length / 8 + sizeof(struct kinfo_proc);
struct kinfo_proc* newProcesses = (struct kinfo_proc*) realloc(processes, length);
if (!newProcesses) {
return "realloc(struct kinfo_proc[]) failed";
}
processes = newProcesses;
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) == 0) {
break;
}
if ((errno != ENOMEM && errno != EINVAL) || attempts >= 4) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}) failed";
}
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &processes[i];
pid_t pid = proc->kp_proc.p_pid;
if (pid <= 0) {
continue;
}
struct rusage_info_v2 rusage;
if (proc_pid_rusage(pid, RUSAGE_INFO_V2, (rusage_info_t*) &rusage) != 0) {
continue; // The process may have exited
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, proc->kp_proc.p_comm);
item->pid = (uint32_t) pid;
item->cpuTime = (rusage.ri_user_time + rusage.ri_system_time) / 1000000u; // ns -> ms
item->memBytes = rusage.ri_resident_size;
item->bytesRead = rusage.ri_diskio_bytesread;
item->bytesWritten = rusage.ri_diskio_byteswritten;
item->startTime = rusage.ri_proc_start_abstime;
}
return nullptr;
}
+67
View File
@@ -0,0 +1,67 @@
#include "top.h"
#include "common/mallocHelper.h"
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
#include <sys/user.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
FF_AUTO_FREE struct kinfo_proc* processes = nullptr;
for (int attempts = 0;; ++attempts) {
length += length / 8 + sizeof(struct kinfo_proc);
struct kinfo_proc* newProcesses = (struct kinfo_proc*) realloc(processes, length);
if (!newProcesses) {
return "realloc(struct kinfo_proc[]) failed";
}
processes = newProcesses;
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) == 0) {
break;
}
if ((errno != ENOMEM && errno != EINVAL) || attempts >= 4) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
}
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &processes[i];
if (proc->ki_flag & P_KPROC) {
continue;
}
uint32_t pid = (uint32_t) proc->ki_pid;
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, proc->ki_comm);
item->pid = pid;
uint64_t userMs = (uint64_t) proc->ki_rusage.ru_utime.tv_sec * 1000 +
(uint64_t) proc->ki_rusage.ru_utime.tv_usec / 1000;
uint64_t sysMs = (uint64_t) proc->ki_rusage.ru_stime.tv_sec * 1000 +
(uint64_t) proc->ki_rusage.ru_stime.tv_usec / 1000;
item->cpuTime = userMs + sysMs;
item->memBytes = (uint64_t) proc->ki_rssize * (uint64_t) pageSize;
item->startTime = (uint64_t) proc->ki_start.tv_sec * 1000 +
(uint64_t) proc->ki_start.tv_usec / 1000;
item->bytesRead = (uint64_t) proc->ki_rusage.ru_inblock * DEV_BSIZE;
item->bytesWritten = (uint64_t) proc->ki_rusage.ru_oublock * DEV_BSIZE;
}
return NULL;
}
+67
View File
@@ -0,0 +1,67 @@
#include "top.h"
#include "common/mallocHelper.h"
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
#include <sys/user.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
FF_AUTO_FREE struct kinfo_proc* processes = nullptr;
for (int attempts = 0;; ++attempts) {
length += length / 8 + sizeof(struct kinfo_proc);
struct kinfo_proc* newProcesses = (struct kinfo_proc*) realloc(processes, length);
if (!newProcesses) {
return "realloc(struct kinfo_proc[]) failed";
}
processes = newProcesses;
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) == 0) {
break;
}
if ((errno != ENOMEM && errno != EINVAL) || attempts >= 4) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}) failed";
}
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &processes[i];
// Kernel threads are reported with P_SYSTEM flags and pid -1.
if ((proc->kp_flags & P_SYSTEM) || proc->kp_stat == SZOMB) {
continue;
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, proc->kp_comm);
item->pid = (uint32_t) proc->kp_pid;
// kp_ru only accounts for exited lwps; the cpu times of live lwps are
// kept in microseconds in kp_lwp.kl_uticks/kl_sticks (see calcru(9)).
uint64_t exitedMs = (uint64_t) (proc->kp_ru.ru_utime.tv_sec + proc->kp_ru.ru_stime.tv_sec) * 1000 +
(uint64_t) (proc->kp_ru.ru_utime.tv_usec + proc->kp_ru.ru_stime.tv_usec) / 1000;
uint64_t liveMs = (proc->kp_lwp.kl_uticks + proc->kp_lwp.kl_sticks) / 1000;
item->cpuTime = exitedMs + liveMs;
item->memBytes = (uint64_t) proc->kp_vm_rssize * pageSize;
item->startTime = (uint64_t) proc->kp_start.tv_sec * 1000 +
(uint64_t) proc->kp_start.tv_usec / 1000;
item->bytesRead = (uint64_t) (proc->kp_ru.ru_inblock + proc->kp_lwp.kl_ru.ru_inblock) * DEV_BSIZE;
item->bytesWritten = (uint64_t) (proc->kp_ru.ru_oublock + proc->kp_lwp.kl_ru.ru_oublock) * DEV_BSIZE;
}
return nullptr;
}
+78
View File
@@ -0,0 +1,78 @@
#include "top.h"
#include <hurd.h>
#include <string.h>
#include <ps.h>
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;
}
+47
View File
@@ -0,0 +1,47 @@
#include "top.h"
#include <OS.h>
#include <string.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots) {
int32 cookie = 0;
team_info team;
while (get_next_team_info(&cookie, &team) == B_OK) {
if (team.team == B_SYSTEM_TEAM) {
continue; // The kernel team
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, team.name);
item->pid = (uint32_t) team.team;
// User and kernel time of all threads of the team combined in microseconds.
item->cpuTime = 0;
team_usage_info usage;
if (get_team_usage_info(team.team, B_TEAM_USAGE_SELF, &usage) == B_OK) {
item->cpuTime = ((uint64_t) usage.user_time + (uint64_t) usage.kernel_time) / 1000;
}
// Sum up the physically resident memory of all areas of the team;
// ram_size is the amount of physical pages committed to an area,
// whereas size is just its virtual reservation.
uint64_t memBytes = 0;
ssize_t areaCookie = 0;
area_info area;
while (get_next_area_info(team.team, &areaCookie, &area) == B_OK) {
memBytes += area.ram_size;
}
item->memBytes = memBytes;
// start_time is microseconds since boot.
item->startTime = (uint64_t) team.start_time;
// Storage io counters are not exposed by Haiku; leave them zeroed.
item->bytesRead = 0;
item->bytesWritten = 0;
}
return nullptr;
}
+126
View File
@@ -0,0 +1,126 @@
#include "top.h"
#include "common/io.h"
#include "common/memrchr.h"
#include "common/strutil.h"
#include <inttypes.h>
#define PF_KTHREAD 0x00200000 // defined in kernel include/linux/sched.h
static bool parseStat(const char* buffer, size_t length, FFTopProcessSnapshot* result) {
const char* open = strchr(buffer, '(');
const char* close = (const char*) memrchr(buffer, ')', length);
if (!open || !close || close <= open || close + 2 >= buffer + length) {
return false;
}
ffStrbufSetNS(&result->name, (uint32_t) (close - open - 1), open + 1);
const char* cursor = close + 2; // state, then fields 4..
if (*cursor == '\0') {
return false;
}
++cursor;
uint64_t values[19];
for (uint32_t field = 4; field <= 22; ++field) {
while (*cursor == ' ') {
++cursor;
}
if (*cursor == '\0') {
return false;
}
char* end;
errno = 0;
unsigned long long value = strtoull(cursor, &end, 10);
if (end == cursor || errno == ERANGE) {
return false;
}
values[field - 4] = (uint64_t) value;
cursor = end;
}
if (values[5] & PF_KTHREAD) { // values[5] is field 9 (flags)
return false;
}
result->cpuTime = values[11] + values[12]; // user + system
result->startTime = values[18];
return true;
}
const char* ffTopGetProcessSnapshot(FFlist* snapshots) {
const long ticks = sysconf(_SC_CLK_TCK);
const long pageSize = instance.state.platform.sysinfo.pageSize;
if (ticks <= 0 || pageSize <= 0) {
return "sysconf(_SC_CLK_TCK or _SC_PAGESIZE) failed";
}
FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc");
if (!dir) {
return "opendir(\"/proc\") failed";
}
int procfd = dirfd(dir);
struct dirent* entry;
char statBuffer[4096];
char statmBuffer[256];
while ((entry = readdir(dir))) {
if (!entry->d_name[0] || !ffCharIsDigit(entry->d_name[0])) {
continue;
}
unsigned long pid = strtoul(entry->d_name, nullptr, 10);
if (pid == 0 || pid > UINT32_MAX) {
continue;
}
FF_AUTO_CLOSE_FD int subfd = openat(procfd, entry->d_name, O_RDONLY | O_DIRECTORY);
if (subfd < 0) {
continue;
}
ssize_t statLength;
if ((statLength = ffReadFileDataRelative(subfd, "stat", sizeof(statBuffer) - 1, statBuffer)) < 0) {
continue;
}
statBuffer[statLength] = '\0';
ssize_t statmLength;
if ((statmLength = ffReadFileDataRelative(subfd, "statm", sizeof(statmBuffer) - 1, statmBuffer)) < 0) {
continue;
}
statmBuffer[statmLength] = '\0';
char ioBuffer[512];
ssize_t ioLength;
if ((ioLength = ffReadFileDataRelative(subfd, "io", sizeof(ioBuffer) - 1, ioBuffer)) < 0) {
continue;
}
ioBuffer[ioLength] = '\0';
auto snapshot = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInit(&snapshot->name);
if (!parseStat(statBuffer, (size_t) statLength, snapshot)) {
ffStrbufDestroy(&snapshot->name);
--snapshots->length;
continue;
}
unsigned long long rssPages;
if (__builtin_expect(sscanf(statmBuffer, "%*llu %llu", &rssPages) != 1, false)) {
ffStrbufDestroy(&snapshot->name);
--snapshots->length;
continue;
}
snapshot->pid = (uint32_t) pid;
snapshot->memBytes = rssPages * (uint64_t) pageSize;
snapshot->bytesRead = 0;
snapshot->bytesWritten = 0;
sscanf(ioBuffer, "rchar: %*" SCNu64 "\nwchar: %*" SCNu64 "\nsyscr: %*" SCNu64 "\nsyscw: %*" SCNu64 "\nread_bytes: %" SCNu64 "\nwrite_bytes: %" SCNu64, &snapshot->bytesRead, &snapshot->bytesWritten);
snapshot->cpuTime = snapshot->cpuTime * 1000u / (uint64_t) ticks;
}
return nullptr;
}
+65
View File
@@ -0,0 +1,65 @@
#include "top.h"
#include "common/mallocHelper.h"
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <errno.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots) {
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), INT_MAX };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
FF_AUTO_FREE struct kinfo_proc2* processes = nullptr;
for (int attempts = 0;; ++attempts) {
length += length / 8 + sizeof(struct kinfo_proc2);
struct kinfo_proc2* newProcesses = (struct kinfo_proc2*) realloc(processes, length);
if (!newProcesses) {
return "realloc(struct kinfo_proc2[]) failed";
}
processes = newProcesses;
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) == 0) {
break;
}
if ((errno != ENOMEM && errno != EINVAL) || attempts >= 4) {
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL}) failed";
}
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc2));
uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc2* proc = &processes[i];
if ((proc->p_flag & P_SYSTEM) || !proc->p_uvalid) { // Kernel process or zombie
continue;
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, proc->p_comm);
item->pid = (uint32_t) proc->p_pid;
uint64_t userMs = (uint64_t) proc->p_uutime_sec * 1000 +
(uint64_t) proc->p_uutime_usec / 1000;
uint64_t sysMs = (uint64_t) proc->p_ustime_sec * 1000 +
(uint64_t) proc->p_ustime_usec / 1000;
item->cpuTime = userMs + sysMs;
item->memBytes = (uint64_t) proc->p_vm_rssize * (uint64_t) pageSize;
item->startTime = (uint64_t) proc->p_ustart_sec * 1000 +
(uint64_t) proc->p_ustart_usec / 1000;
item->bytesRead = (uint64_t) proc->p_uru_inblock * DEV_BSIZE;
item->bytesWritten = (uint64_t) proc->p_uru_oublock * DEV_BSIZE;
}
return NULL;
}
+44
View File
@@ -0,0 +1,44 @@
#include "top.h"
#include <sys/types.h>
#include <sys/param.h> // DEV_BSIZE
#include <sys/sysctl.h>
#include <kvm.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots) {
kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
if (!kd) {
return "kvm_open() failed";
}
int count = 0;
// KERN_PROC_ALL returns all user-level processes, excluding kernel processes and threads
const struct kinfo_proc* processes = kvm_getprocs(kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &count);
if (!processes) {
kvm_close(kd);
return "kvm_getprocs() failed";
}
const uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
for (int i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &processes[i];
if (!proc->p_uvalid) { // Zombie process; p_u* members are invalid
continue;
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, proc->p_comm);
item->pid = (uint32_t) proc->p_pid;
item->cpuTime = ((uint64_t) proc->p_uutime_sec * 1000 + (uint64_t) proc->p_uutime_usec / 1000) +
((uint64_t) proc->p_ustime_sec * 1000 + (uint64_t) proc->p_ustime_usec / 1000);
item->memBytes = (uint64_t) proc->p_vm_rssize * pageSize;
item->startTime = (uint64_t) proc->p_ustart_sec * 1000 + (uint64_t) proc->p_ustart_usec / 1000;
item->bytesRead = (uint64_t) proc->p_uru_inblock * DEV_BSIZE;
item->bytesWritten = (uint64_t) proc->p_uru_oublock * DEV_BSIZE;
}
kvm_close(kd);
return nullptr;
}
+66
View File
@@ -0,0 +1,66 @@
#include "top.h"
#include "common/io.h"
#include "common/strutil.h"
#include <stdio.h>
#include <procfs.h>
#include <sys/param.h> // DEV_BSIZE
const char* ffTopGetProcessSnapshot(FFlist* snapshots) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/proc");
if (!dirp) {
return "opendir(\"/proc\") failed";
}
int procfd = dirfd(dirp);
struct dirent* dp;
while ((dp = readdir(dirp))) {
if (!ffCharIsDigit(dp->d_name[0])) { // Skip "." and ".."
continue;
}
FF_AUTO_CLOSE_FD int subfd = openat(procfd, dp->d_name, O_RDONLY | O_DIRECTORY);
if (subfd < 0) {
continue;
}
// The data model of the returned structure depends on the data model of
// the calling process, not of the observed process.
psinfo_t psinfo;
if (ffReadFileDataRelative(subfd, "psinfo", sizeof(psinfo), &psinfo) != (ssize_t) sizeof(psinfo)) {
continue; // The process may have exited
}
// pr_nlwp is zero for system processes and zombies
if (psinfo.pr_nlwp == 0 || psinfo.pr_lwp.pr_sname == 'Z') {
continue;
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, psinfo.pr_fname);
item->pid = (uint32_t) psinfo.pr_pid;
item->cpuTime = (uint64_t) psinfo.pr_time.tv_sec * 1000 +
(uint64_t) psinfo.pr_time.tv_nsec / 1000000;
item->memBytes = (uint64_t) psinfo.pr_rssize * 1024;
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;
}
}
return nullptr;
}
+48
View File
@@ -0,0 +1,48 @@
#include "top.h"
#include "common/mallocHelper.h"
#include "common/windows/unicode.h"
#include <ntstatus.h>
#include <windows.h>
#include <winternl.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots) {
FF_AUTO_FREE SYSTEM_PROCESS_INFORMATION* pstart = nullptr;
// Multiple attempts in case processes change while
// we are in the middle of querying them.
ULONG size = 0;
for (int attempts = 0;; ++attempts) {
if (size) {
pstart = (SYSTEM_PROCESS_INFORMATION*) realloc(pstart, size);
assert(pstart);
}
NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, pstart, size, &size);
if (NT_SUCCESS(status)) {
break;
} else if (status == STATUS_INFO_LENGTH_MISMATCH && attempts < 4) {
size += sizeof(SYSTEM_PROCESS_INFORMATION) * 5;
} else {
return "NtQuerySystemInformation(SystemProcessInformation) failed";
}
}
for (SYSTEM_PROCESS_INFORMATION* info = pstart;; info = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) info + info->NextEntryOffset)) {
uintptr_t pidValue = (uintptr_t) info->UniqueProcessId;
if (pidValue <= UINT32_MAX && info->CreateTime.QuadPart > 0) {
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
item->pid = (uint32_t) pidValue;
item->startTime = info->CreateTime.QuadPart > 0 ? (uint64_t) info->CreateTime.QuadPart : 0;
item->cpuTime = ((uint64_t) info->UserTime.QuadPart + (uint64_t) info->KernelTime.QuadPart) / 10000u;
item->memBytes = (uint64_t) info->VirtualMemoryCounters.WorkingSetSize;
item->bytesRead = (uint64_t) info->IoCounters.ReadTransferCount;
item->bytesWritten = (uint64_t) info->IoCounters.WriteTransferCount;
ffStrbufInitNWS(&item->name, info->ImageName.Length / sizeof(wchar_t), info->ImageName.Buffer);
}
if (info->NextEntryOffset == 0) {
break;
}
}
return nullptr;
}
+3
View File
@@ -271,6 +271,9 @@ static FFModuleBaseInfo* T[] = {
#if !FF_MODULE_DISABLE_TITLE
&ffTitleModuleInfo,
#endif
#if !FF_MODULE_DISABLE_TOP
&ffTopModuleInfo,
#endif
#if !FF_MODULE_DISABLE_THEME
&ffThemeModuleInfo,
#endif
+1
View File
@@ -64,6 +64,7 @@
#include "modules/terminaltheme/terminaltheme.h"
#include "modules/theme/theme.h"
#include "modules/title/title.h"
#include "modules/top/top.h"
#include "modules/tpm/tpm.h"
#include "modules/uptime/uptime.h"
#include "modules/users/users.h"
+1
View File
@@ -64,6 +64,7 @@
#include "modules/terminaltheme/option.h"
#include "modules/theme/option.h"
#include "modules/title/option.h"
#include "modules/top/option.h"
#include "modules/tpm/option.h"
#include "modules/uptime/option.h"
#include "modules/users/option.h"
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#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 struct FFTopOptions {
FFModuleArgs moduleArgs;
FFTopSortType sort;
uint32_t nProcesses;
uint32_t waitTime;
FFPercentageModuleConfig percent;
bool compact;
bool kernelTask; // Whether to include kernel tasks (kworker, System, kernel_task) in the process list
} FFTopOptions;
static_assert(sizeof(FFTopOptions) <= FF_OPTION_MAX_SIZE, "FFTopOptions size exceeds maximum allowed size");
+229
View File
@@ -0,0 +1,229 @@
#include "common/jsonconfig.h"
#include "common/percent.h"
#include "common/printing.h"
#include "common/size.h"
#include "detection/top/top.h"
#include "modules/top/top.h"
static void printTopResult(FFTopOptions* options, uint32_t index, uint32_t total, FFTopProcessResult* process) {
FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;
if (options->moduleArgs.outputFormat.length == 0) {
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 ");
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");
}
ffStrbufPutTo(&output, stdout);
} else {
FF_STRBUF_AUTO_DESTROY cpuFormatted = ffStrbufCreate();
if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) {
ffPercentAppendNum(&cpuFormatted, process->cpuPercent, options->percent, false, &options->moduleArgs);
}
FF_STRBUF_AUTO_DESTROY memFormatted = ffStrbufCreate();
ffSizeAppendNum(process->memBytes, &memFormatted);
FF_STRBUF_AUTO_DESTROY diskReadFormatted = ffStrbufCreate();
ffSizeAppendNum(process->bytesRead, &diskReadFormatted);
ffStrbufAppendS(&diskReadFormatted, "/s");
FF_STRBUF_AUTO_DESTROY diskWriteFormatted = ffStrbufCreate();
ffSizeAppendNum(process->bytesWritten, &diskWriteFormatted);
ffStrbufAppendS(&diskWriteFormatted, "/s");
FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Top), (uint8_t) index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){
FF_ARG(process->name, "name"),
FF_ARG(process->pid, "pid"),
FF_ARG(process->cpuPercent, "cpu"),
FF_ARG(process->memBytes, "mem"),
FF_ARG(process->bytesRead, "disk-read"),
FF_ARG(process->bytesWritten, "disk-write"),
FF_ARG(cpuFormatted, "cpu-percentage"),
FF_ARG(memFormatted, "mem-formatted"),
FF_ARG(diskReadFormatted, "disk-read-formatted"),
FF_ARG(diskWriteFormatted, "disk-write-formatted"),
}));
}
}
bool ffPrintTop(FFTopOptions* options) {
FF_LIST_AUTO_DESTROY results = ffListCreate();
const char* error = ffDetectTopProcesses(options, &results);
if (error) {
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
return false;
}
if (!options->compact) {
FF_LIST_FOR_EACH (FFTopProcessResult, process, results) {
printTopResult(options, (uint32_t) (process - (FFTopProcessResult*) results.data), results.length, process);
}
} else {
ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
FF_LIST_FOR_EACH (FFTopProcessResult, process, results) {
ffStrbufWriteTo(&process->name, stdout);
if ((void*) process != results.data) {
putchar(' ');
}
}
}
FF_LIST_FOR_EACH (FFTopProcessResult, item, results) {
ffStrbufDestroy(&item->name);
}
return true;
}
void ffParseTopJsonObject(FFTopOptions* options, yyjson_val* module) {
yyjson_val *key, *val;
size_t idx, max;
yyjson_obj_foreach (module, idx, max, key, val) {
if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) {
continue;
}
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 },
{},
});
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;
}
continue;
} else if (unsafe_yyjson_equals_str(key, "processes")) {
options->nProcesses = (uint32_t) yyjson_get_uint(val);
} else if (unsafe_yyjson_equals_str(key, "waitTime")) {
options->waitTime = (uint32_t) yyjson_get_uint(val);
} else if (unsafe_yyjson_equals_str(key, "compact")) {
options->compact = yyjson_get_bool(val);
} else if (ffPercentParseJsonObject(key, val, &options->percent)) {
continue;
} else {
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
}
}
}
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_obj_add_uint(doc, module, "processes", options->nProcesses);
yyjson_mut_obj_add_uint(doc, module, "waitTime", options->waitTime);
yyjson_mut_obj_add_bool(doc, module, "compact", options->compact);
yyjson_mut_obj_add_bool(doc, module, "kernelTask", options->kernelTask);
ffPercentGenerateJsonConfig(doc, module, options->percent);
}
bool ffGenerateTopJsonResult(FFTopOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
FF_LIST_AUTO_DESTROY results = ffListCreate();
const char* error = ffDetectTopProcesses(options, &results);
if (error) {
yyjson_mut_obj_add_str(doc, module, "error", error);
return false;
}
yyjson_mut_val* array = yyjson_mut_obj_add_arr(doc, module, "result");
FF_LIST_FOR_EACH (FFTopProcessResult, process, results) {
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);
yyjson_mut_obj_add_uint(doc, item, "startTime", process->startTime);
}
FF_LIST_FOR_EACH (FFTopProcessResult, item, results) {
ffStrbufDestroy(&item->name);
}
return true;
}
void ffInitTopOptions(FFTopOptions* options) {
ffOptionInitModuleArg(&options->moduleArgs, "󰍛");
options->sort = FF_TOP_SORT_CPU;
options->nProcesses = 5;
options->waitTime = 500;
options->compact = false;
options->kernelTask = false;
options->percent = (FFPercentageModuleConfig){ 50, 80, 0 };
}
void ffDestroyTopOptions(FFTopOptions* options) {
ffOptionDestroyModuleArg(&options->moduleArgs);
}
FFModuleBaseInfo ffTopModuleInfo = {
.name = "Top",
.description = "Print processes with the highest CPU, memory or disk I/O usage",
.displayName = {
.en = "Top Processes",
.ar = "أعلى العمليات",
.cs = "Nejvytíženější procesy",
.de = "Top-Prozesse",
.es = "Procesos principales",
.fr = "Processus principaux",
.gl = "Procesos principais",
.he = "התהליכים המובילים",
.id = "Proses teratas",
.it = "Processi principali",
.ja = "上位プロセス",
.ko = "상위 프로세스",
.pl = "Najaktywniejsze procesy",
.pt = "Processos principais",
.ru = "Топ процессов",
.tr = "En yoğun işlemler",
.uk = "Найактивніші процеси",
.vi = "Tiến trình hàng đầu",
.zh_CN = "进程排行",
.zh_TW = "進程排行",
},
.initOptions = (void*) ffInitTopOptions,
.destroyOptions = (void*) ffDestroyTopOptions,
.parseJsonObject = (void*) ffParseTopJsonObject,
.printModule = (void*) ffPrintTop,
.generateJsonResult = (void*) ffGenerateTopJsonResult,
.generateJsonConfig = (void*) ffGenerateTopJsonConfig,
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]){
{ "Process name", "name" },
{ "Executable path", "path" },
{ "Process ID", "pid" },
{ "CPU usage", "cpu" },
{ "Memory usage (RSS) in bytes", "mem" },
{ "Disk read bytes per second", "disk-read" },
{ "Disk write bytes per second", "disk-write" },
{ "CPU usage percentage", "cpu-percentage" },
{ "Memory usage (RSS) formatted", "mem-formatted" },
{ "Disk read formatted", "disk-read-formatted" },
{ "Disk write formatted", "disk-write-formatted" },
})),
.defaultOrder = 36,
};
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "option.h"
void ffPrepareTopProcesses(void);
bool ffPrintTop(FFTopOptions* options);
void ffInitTopOptions(FFTopOptions* options);
void ffDestroyTopOptions(FFTopOptions* options);
extern FFModuleBaseInfo ffTopModuleInfo;