Top: adds thread count detection

This commit is contained in:
李通洲
2026-08-31 20:27:22 +08:00
parent 6eef800ea2
commit 4ff931c06f
15 changed files with 113 additions and 33 deletions
+10 -7
View File
@@ -506,7 +506,7 @@
"type": "string"
},
"topFormat": {
"description": "Output format for the `Top` module. See Wiki for formatting syntax\n 1. {name}: Process name\n 2. {pid}: Process ID\n 3. {cpu}: CPU usage\n 4. {mem}: Memory usage (RSS) in bytes\n 5. {disk-read}: Disk read bytes per second (0 if unsupported)\n 6. {disk-write}: Disk write bytes per second (0 if unsupported)\n 7. {cpu-percentage}: CPU usage percentage\n 8. {mem-formatted}: Memory usage (RSS) formatted\n 9. {disk-read-formatted}: Disk read formatted\n 10. {disk-write-formatted}: Disk write formatted",
"description": "Output format for the `Top` module. See Wiki for formatting syntax\n 1. {name}: Process name\n 2. {pid}: Process ID\n 3. {cpu}: CPU usage\n 4. {mem}: Memory usage (RSS) in bytes\n 5. {disk-read}: Disk read bytes per second (0 if unsupported)\n 6. {disk-write}: Disk write bytes per second (0 if unsupported)\n 7. {threads}: Number of threads\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": {
@@ -4441,22 +4441,23 @@
"properties": {
"type": {
"const": "top",
"description": "Print processes with the highest CPU, memory, disk I/O usage or latest start time"
"description": "Print processes with the highest CPU, memory, disk I/O usage, latest start time or thread count"
},
"sort": {
"type": "string",
"description": "Sort processes by CPU, memory, disk IO usage or start time",
"description": "Sort processes by CPU, memory, disk IO usage, start time or thread count",
"enum": [
"cpu",
"memory",
"disk-read",
"disk-write",
"start-time"
"start-time",
"threads"
],
"default": "cpu"
},
"showTypes": {
"description": "Specify which resource usages are detected and displayed.\nIf memory is enabled is enabled alone, only a single process snapshot is taken and `waitTime` sampling wait is skipped",
"description": "Specify which resource usages are detected and displayed.\nIf memory or threads is enabled is enabled alone, only a single process snapshot is taken and `waitTime` sampling wait is skipped",
"default": [
"cpu",
"memory",
@@ -4468,7 +4469,8 @@
"enum": [
"cpu",
"memory",
"disk"
"disk",
"threads"
]
},
{
@@ -4479,7 +4481,8 @@
"enum": [
"cpu",
"memory",
"disk"
"disk",
"threads"
]
}
}
+11 -2
View File
@@ -59,6 +59,12 @@ static int compareStartTimeResults(const FFTopProcessResult* a, const FFTopProce
if (a->startTime > b->startTime) return -1;
return (int) (a->pid - b->pid);
}
static int compareThreadsResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
if (a->threads < b->threads) return 1;
if (a->threads > b->threads) return -1;
return (int) (a->pid - b->pid);
}
// clang-format on
const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) {
@@ -67,8 +73,8 @@ const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) {
return nullptr;
}
// Memory usage is instantaneous; when neither CPU time nor disk IO counters
// are requested, a single snapshot suffices and no sampling wait is needed.
// Memory usage and thread count are instantaneous; when neither CPU time nor disk IO
// counters are requested, a single snapshot suffices and no sampling wait is needed.
const bool sampleOnce = (options->showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) == 0;
if (sampleOnce) {
@@ -92,6 +98,7 @@ const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) {
item->bytesWritten = 0;
item->cpuPercent = 0;
item->startTime = snap->startTime;
item->threads = snap->threads;
ffStrbufInitMove(&item->name, &snap->name);
}
} else {
@@ -141,6 +148,7 @@ const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) {
item->bytesWritten = (newItem->bytesWritten - oldItem->bytesWritten) * 1000u / (uint64_t) elapsed;
item->cpuPercent = (double) (newItem->cpuTime - oldItem->cpuTime) / elapsed * 100.0;
item->startTime = newItem->startTime;
item->threads = newItem->threads;
ffStrbufInitMove(&item->name, &oldItem->name);
}
@@ -154,6 +162,7 @@ const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) {
: options->sort == FF_TOP_TYPE_DISK_READ ? (void*) compareDiskReadResults
: options->sort == FF_TOP_TYPE_MEMORY ? (void*) compareMemoryResults
: options->sort == FF_TOP_TYPE_START_TIME ? (void*) compareStartTimeResults
: options->sort == FF_TOP_TYPE_THREADS ? (void*) compareThreadsResults
: (void*) compareCpuResults;
ffListSort(result, sizeof(FFTopProcessResult), (void*) compare);
if (result->length > options->nProcesses) {
+2
View File
@@ -10,6 +10,7 @@ typedef struct FFTopProcessResult {
uint64_t bytesRead;
uint64_t bytesWritten;
uint64_t startTime;
uint32_t threads;
uint32_t pid;
} FFTopProcessResult;
@@ -20,6 +21,7 @@ typedef struct FFTopProcessSnapshot {
uint64_t bytesRead; // Physical storage read
uint64_t bytesWritten; // Physical storage written
uint64_t startTime; // Differs between platforms
uint32_t threads;
uint32_t pid;
} FFTopProcessSnapshot;
+8 -1
View File
@@ -5,7 +5,7 @@
#include <sys/sysctl.h>
#include <libproc.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
size_t length;
@@ -54,6 +54,13 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
item->bytesRead = rusage.ri_diskio_bytesread;
item->bytesWritten = rusage.ri_diskio_byteswritten;
item->startTime = rusage.ri_proc_start_abstime;
item->threads = 0;
if (showTypes & FF_TOP_TYPE_THREADS) {
struct proc_taskinfo taskInfo;
if (proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskInfo, sizeof(taskInfo)) == sizeof(taskInfo)) {
item->threads = (uint32_t) taskInfo.pti_threadnum;
}
}
}
return nullptr;
+2 -1
View File
@@ -6,7 +6,7 @@
#include <sys/sysctl.h>
#include <sys/user.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC };
size_t length;
@@ -51,6 +51,7 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
item->bytesRead = (uint64_t) proc->ki_rusage.ru_inblock * DEV_BSIZE;
item->bytesWritten = (uint64_t) proc->ki_rusage.ru_oublock * DEV_BSIZE;
item->threads = (uint32_t) proc->ki_numthreads;
}
return NULL;
+2 -1
View File
@@ -6,7 +6,7 @@
#include <sys/sysctl.h>
#include <sys/user.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
size_t length;
@@ -52,6 +52,7 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
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;
item->threads = (uint32_t) proc->kp_nthreads;
}
return nullptr;
+9 -2
View File
@@ -4,7 +4,7 @@
#include <string.h>
#include <ps.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
struct ps_context* context = nullptr;
if (ps_context_create(getproc(), &context) != 0) {
return "ps_context_create(getproc()) failed";
@@ -24,7 +24,7 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
// 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) {
if (proc_stat_list_set_flags(list, PSTAT_ARGS | PSTAT_STATE | PSTAT_TASK_BASIC | ((showTypes & FF_TOP_TYPE_THREADS) ? PSTAT_NUM_THREADS : 0)) != 0) {
error = "proc_stat_list_set_flags() failed";
goto done;
}
@@ -60,6 +60,13 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
item->cpuTime = 0;
item->memBytes = 0;
item->startTime = 0;
item->threads = 0;
if (showTypes & FF_TOP_TYPE_THREADS) {
if (proc_stat_has(stat, PSTAT_NUM_THREADS)) {
item->threads = (uint32_t) proc_stat_num_threads(stat);
}
}
if (proc_stat_has(stat, PSTAT_TASK_BASIC)) {
const task_basic_info_t info = proc_stat_task_basic_info(stat);
+2 -1
View File
@@ -3,7 +3,7 @@
#include <OS.h>
#include <string.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
int32 cookie = 0;
team_info team;
while (get_next_team_info(&cookie, &team) == B_OK) {
@@ -41,6 +41,7 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
// Storage io counters are not exposed by Haiku; leave them zeroed.
item->bytesRead = 0;
item->bytesWritten = 0;
item->threads = (uint32_t) team.thread_count;
}
return nullptr;
+8 -2
View File
@@ -24,8 +24,6 @@ static bool parseStat(const char* buffer, size_t length, FFTopProcessSnapshot* r
return false;
}
result->cpuTime = 0;
char* p;
for (uint32_t field = 3; field <= 22 /*start time*/; ++field) {
switch (field) {
@@ -43,6 +41,11 @@ static bool parseStat(const char* buffer, size_t length, FFTopProcessSnapshot* r
cursor = p;
break;
case 20: // number of threads
result->threads = (uint32_t) strtoul(cursor, &p, 10);
cursor = p;
break;
case 22: // start time
result->startTime = (uint64_t) strtoull(cursor, &p, 10);
cursor = p;
@@ -128,6 +131,9 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
auto snapshot = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInit(&snapshot->name);
snapshot->threads = 0;
snapshot->cpuTime = 0;
snapshot->startTime = 0;
if (!parseStat(statBuffer, (size_t) statLength, snapshot)) {
ffStrbufDestroy(&snapshot->name);
+9 -1
View File
@@ -6,7 +6,7 @@
#include <sys/sysctl.h>
#include <errno.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), INT_MAX };
size_t length;
@@ -50,6 +50,14 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
item->bytesRead = (uint64_t) proc->p_uru_inblock * DEV_BSIZE;
item->bytesWritten = (uint64_t) proc->p_uru_oublock * DEV_BSIZE;
item->threads = 0;
if (showTypes & FF_TOP_TYPE_THREADS) {
int lwpRequest[] = { CTL_KERN, KERN_LWP, proc->p_pid, sizeof(struct kinfo_lwp), 0 };
size_t lwpLength = 0;
if (sysctl(lwpRequest, ARRAY_SIZE(lwpRequest), nullptr, &lwpLength, nullptr, 0) == 0) {
item->threads = (uint32_t) (lwpLength / sizeof(struct kinfo_lwp));
}
}
}
return NULL;
+18 -1
View File
@@ -5,7 +5,7 @@
#include <sys/sysctl.h>
#include <kvm.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
if (!kd) {
return "kvm_open() failed";
@@ -37,6 +37,23 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
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;
item->threads = 0;
}
if (showTypes & FF_TOP_TYPE_THREADS) {
int threadCount = 0;
const struct kinfo_proc* threads = kvm_getprocs(kd,
KERN_PROC_ALL | KERN_PROC_SHOW_THREADS, 0, sizeof(struct kinfo_proc), &threadCount);
if (threads) {
for (uint32_t i = 0; i < snapshots->length; ++i) {
FFTopProcessSnapshot* item = FF_LIST_GET(FFTopProcessSnapshot, *snapshots, i);
for (int j = 0; j < threadCount; ++j) {
if (threads[j].p_pid == (pid_t) item->pid && threads[j].p_tid != -1) {
++item->threads;
}
}
}
}
}
kvm_close(kd);
+1
View File
@@ -60,6 +60,7 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
item->bytesRead = 0;
item->bytesWritten = 0;
item->threads = (uint32_t) psinfo.pr_nlwp;
if (showTypes & FF_TOP_TYPE_DISK) {
prusage_t usage;
if (ffReadFileDataRelative(subfd, "usage", sizeof(usage), &usage) == (ssize_t) sizeof(usage)) {
+1
View File
@@ -38,6 +38,7 @@ const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
item->memBytes = (uint64_t) info->VirtualMemoryCounters.WorkingSetSize;
item->bytesRead = (uint64_t) info->IoCounters.ReadTransferCount;
item->bytesWritten = (uint64_t) info->IoCounters.WriteTransferCount;
item->threads = info->NumberOfThreads;
ffStrbufInitNWS(&item->name, info->ImageName.Length / sizeof(wchar_t), info->ImageName.Buffer);
}
if (info->NextEntryOffset == 0) {
+1
View File
@@ -9,6 +9,7 @@ typedef enum FFTopTypes: uint8_t {
FF_TOP_TYPE_DISK_READ = 1 << 2,
FF_TOP_TYPE_DISK_WRITE = 1 << 3,
FF_TOP_TYPE_START_TIME = 1 << 4,
FF_TOP_TYPE_THREADS = 1 << 5,
FF_TOP_TYPE_DISK = FF_TOP_TYPE_DISK_READ | FF_TOP_TYPE_DISK_WRITE,
} FFTopTypes;
+29 -14
View File
@@ -28,6 +28,9 @@ static void printTopResult(FFTopOptions* options, uint32_t index, uint32_t total
ffSizeAppendNum(process->bytesWritten, &output);
ffStrbufAppendS(&output, "/s");
}
if (options->showTypes & FF_TOP_TYPE_THREADS) {
ffStrbufAppendF(&output, " - THR %u", process->threads);
}
}
ffStrbufPutTo(&output, stdout);
} else {
@@ -44,17 +47,18 @@ static void printTopResult(FFTopOptions* options, uint32_t index, uint32_t total
ffSizeAppendNum(process->bytesWritten, &diskWriteFormatted);
ffStrbufAppendS(&diskWriteFormatted, "/s");
FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Top), total == 1 ? 0 : (uint8_t) (index + 1), &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"),
}));
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(process->threads, "threads"),
FF_ARG(cpuFormatted, "cpu-percentage"),
FF_ARG(memFormatted, "mem-formatted"),
FF_ARG(diskReadFormatted, "disk-read-formatted"),
FF_ARG(diskWriteFormatted, "disk-write-formatted"),
}));
}
}
@@ -121,6 +125,7 @@ void ffParseTopJsonObject(FFTopOptions* options, yyjson_val* module) {
{ "cpu", FF_TOP_TYPE_CPU },
{ "memory", FF_TOP_TYPE_MEMORY },
{ "disk", FF_TOP_TYPE_DISK },
{ "threads", FF_TOP_TYPE_THREADS },
{},
});
if (error == nullptr) {
@@ -138,6 +143,7 @@ void ffParseTopJsonObject(FFTopOptions* options, yyjson_val* module) {
{ "cpu", FF_TOP_TYPE_CPU },
{ "memory", FF_TOP_TYPE_MEMORY },
{ "disk", FF_TOP_TYPE_DISK },
{ "threads", FF_TOP_TYPE_THREADS },
{},
});
if (error == nullptr) {
@@ -175,6 +181,9 @@ void ffGenerateTopJsonConfig(FFTopOptions* options, yyjson_mut_doc* doc, yyjson_
if (options->showTypes & FF_TOP_TYPE_DISK) {
yyjson_mut_arr_add_str(doc, showTypes, "disk");
}
if (options->showTypes & FF_TOP_TYPE_THREADS) {
yyjson_mut_arr_add_str(doc, showTypes, "threads");
}
yyjson_mut_obj_add_val(doc, module, "showTypes", showTypes);
if (options->sort == FF_TOP_TYPE_CPU) {
yyjson_mut_obj_add_str(doc, module, "sort", "cpu");
@@ -186,6 +195,8 @@ void ffGenerateTopJsonConfig(FFTopOptions* options, yyjson_mut_doc* doc, yyjson_
yyjson_mut_obj_add_str(doc, module, "sort", "disk-write");
} else if (options->sort == FF_TOP_TYPE_START_TIME) {
yyjson_mut_obj_add_str(doc, module, "sort", "start-time");
} else if (options->sort == FF_TOP_TYPE_THREADS) {
yyjson_mut_obj_add_str(doc, module, "sort", "threads");
}
yyjson_mut_obj_add_uint(doc, module, "processes", options->nProcesses);
yyjson_mut_obj_add_uint(doc, module, "waitTime", options->waitTime);
@@ -215,6 +226,9 @@ bool ffGenerateTopJsonResult(FFTopOptions* options, yyjson_mut_doc* doc, yyjson_
yyjson_mut_obj_add_uint(doc, item, "bytesRead", process->bytesRead);
yyjson_mut_obj_add_uint(doc, item, "bytesWritten", process->bytesWritten);
}
if (options->showTypes & FF_TOP_TYPE_THREADS) {
yyjson_mut_obj_add_uint(doc, item, "threads", process->threads);
}
yyjson_mut_obj_add_uint(doc, item, "startTime", process->startTime);
}
@@ -228,10 +242,10 @@ void ffInitTopOptions(FFTopOptions* options) {
ffOptionInitModuleArg(&options->moduleArgs, "󰍛");
options->sort = FF_TOP_TYPE_CPU;
options->showTypes = FF_TOP_TYPE_CPU | FF_TOP_TYPE_MEMORY
#if !__GNU__ && !__HAIKU__
#if !__GNU__ && !__HAIKU__
| FF_TOP_TYPE_DISK
#endif
;
#endif
;
options->nProcesses = 5;
options->waitTime = 500;
options->compact = false;
@@ -280,6 +294,7 @@ FFModuleBaseInfo ffTopModuleInfo = {
{ "Memory usage (RSS) in bytes", "mem" },
{ "Disk read bytes per second (0 if unsupported)", "disk-read" },
{ "Disk write bytes per second (0 if unsupported)", "disk-write" },
{ "Number of threads", "threads" },
{ "CPU usage percentage", "cpu-percentage" },
{ "Memory usage (RSS) formatted", "mem-formatted" },
{ "Disk read formatted", "disk-read-formatted" },