mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-09-12 10:22:12 +02:00
Top: adds new module
This commit is contained in:
@@ -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
@@ -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",
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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': {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user