NetIO / DiskIO: reduces waitTime & improves accurency

This commit is contained in:
李通洲
2026-09-17 00:30:40 +08:00
parent d1b747192f
commit 9bac273165
10 changed files with 64 additions and 60 deletions
+4 -1
View File
@@ -9,6 +9,9 @@ Changes:
* `--logo-cache false` ignores the image logo cache completely: nothing is read from it and nothing is written to it.
* `--logo-cache regen` does what `--logo-recache true` used to do.
* `logo.cache` accepts a boolean, or the string `"regen"`.
* The `waitTime` option of `DiskIO` and `NetIO` now defaults to `250` ms instead of `500`. (DiskIO / NetIO)
* The byte counters are maintained by the kernel as I/O happens, so a shorter sampling window still yields an accurate rate, and both modules now finish about 250 ms sooner.
* Note that the two modules wait concurrently, so enabling both does not cost twice the wait time.
Features:
* Improved image logo support
@@ -37,7 +40,7 @@ Features:
* Improved Wallpaper detection on macOS Sonoma and later (#2559, Wallpaper, macOS)
* The image path is now also extracted from the `Configuration` field of the wallpaper plist, and the `NSWorkspace` fallback is tried last.
* Removed the `kvm` dependency on OpenBSD by using `sysctl` directly. (General, OpenBSD)
* Modules that were selected on the command line via `--structure` / `-s` now honors module options configured in the JSON config. (CommandOption)
* Modules that were selected on the command line via `--structure` / `-s` now honor module options configured in the JSON config. (CommandOption)
Bugfixes:
* Fixed Base64 encoding producing wrong output for some inputs. (General)
+2 -2
View File
@@ -2710,7 +2710,7 @@
"waitTime": {
"type": "integer",
"description": "Wait time (in ms) used to measure the I/O rate (calculated in bytes/sec).\nAvoid setting this too low, as the kernel requires time to accurately update counters.\nIgnored if 'detectTotal' is true.",
"default": 500,
"default": 250,
"minimum": 1
},
"key": {
@@ -3611,7 +3611,7 @@
"waitTime": {
"type": "integer",
"description": "Wait time (in ms) used to measure the I/O rate (calculated in bytes/sec).\nAvoid setting this too low, as the kernel requires time to accurately update counters.\nIgnored if 'detectTotal' is true.",
"default": 500,
"default": 250,
"minimum": 1
},
"key": {
+2 -2
View File
@@ -99,7 +99,7 @@
"gamepad",
"mouse",
"keyboard",
"top",
"tpm",
"netio",
"diskio",
{
@@ -110,7 +110,7 @@
"type": "weather",
"timeout": 1000
},
"tpm",
"top",
"version",
"break",
"colors"
+2 -2
View File
@@ -101,7 +101,7 @@
"gamepad",
"mouse",
"keyboard",
"top",
"tpm",
"netio",
"diskio",
{
@@ -112,7 +112,7 @@
"type": "weather",
"timeout": 1000
},
"tpm",
"top",
"version",
"logo",
"break",
+22 -13
View File
@@ -63,6 +63,21 @@ bool ffParseModuleOptions(const char* key, const char* value) {
return false;
}
static yyjson_val* findStructureModuleConfig(FFdata* data, const char* moduleType);
// The module options live in the JSON config only. They must be merged here as well as in
// `parseStructureCommand`, because `ffPrepareXxx` takes the first snapshot while `ffDetectXxx`
// takes the second one. If the two disagree, they list different entries and the deltas are
// meaningless. Keeping both call sites on this single helper is what makes them agree.
static void initStructureModuleOptions(FFdata* data, FFModuleBaseInfo* baseInfo, void* options) {
baseInfo->initOptions(options);
yyjson_val* configModule = findStructureModuleConfig(data, baseInfo->name);
if (configModule != nullptr) {
baseInfo->parseJsonObject(options, configModule);
}
}
void ffPrepareCommandOption(FFdata* data) {
char* moduleType = nullptr;
size_t moduleLen = 0;
@@ -74,7 +89,7 @@ void ffPrepareCommandOption(FFdata* data) {
case 'C':
case 'c':
FF_IF_MODULE_MATCH(ffCPUUsageModuleInfo.name)
ffPrepareCPUUsage();
ffPrepareCPUUsage(); // The rate is derived from the CPU's own counters; no options are involved
break;
#endif
@@ -83,7 +98,7 @@ void ffPrepareCommandOption(FFdata* data) {
case 'd':
FF_IF_MODULE_MATCH(ffDiskIOModuleInfo.name) {
[[gnu::cleanup(ffDestroyDiskIOOptions)]] FFDiskIOOptions options;
ffInitDiskIOOptions(&options);
initStructureModuleOptions(data, &ffDiskIOModuleInfo, &options);
ffPrepareDiskIO(&options);
}
break;
@@ -94,7 +109,7 @@ void ffPrepareCommandOption(FFdata* data) {
case 'n':
FF_IF_MODULE_MATCH(ffNetIOModuleInfo.name) {
[[gnu::cleanup(ffDestroyNetIOOptions)]] FFNetIOOptions options;
ffInitNetIOOptions(&options);
initStructureModuleOptions(data, &ffNetIOModuleInfo, &options);
ffPrepareNetIO(&options);
}
break;
@@ -105,7 +120,7 @@ void ffPrepareCommandOption(FFdata* data) {
case 'p':
FF_IF_MODULE_MATCH(ffPublicIPModuleInfo.name) {
[[gnu::cleanup(ffDestroyPublicIpOptions)]] FFPublicIPOptions options;
ffInitPublicIpOptions(&options);
initStructureModuleOptions(data, &ffPublicIPModuleInfo, &options);
ffPreparePublicIp(&options);
}
break;
@@ -116,7 +131,7 @@ void ffPrepareCommandOption(FFdata* data) {
case 't':
FF_IF_MODULE_MATCH(ffTopModuleInfo.name) {
[[gnu::cleanup(ffDestroyTopOptions)]] FFTopOptions options;
ffInitTopOptions(&options);
initStructureModuleOptions(data, &ffTopModuleInfo, &options);
ffPrepareTopProcesses(options.showTypes);
}
break;
@@ -127,7 +142,7 @@ void ffPrepareCommandOption(FFdata* data) {
case 'w':
FF_IF_MODULE_MATCH(ffWeatherModuleInfo.name) {
[[gnu::cleanup(ffDestroyWeatherOptions)]] FFWeatherOptions options;
ffInitWeatherOptions(&options);
initStructureModuleOptions(data, &ffWeatherModuleInfo, &options);
ffPrepareWeather(&options);
}
break;
@@ -215,17 +230,11 @@ static bool parseStructureCommand(
FFModuleBaseInfo* baseInfo = *modules;
if (ffStrEqualsIgnCase(line, baseInfo->name)) {
alignas(uint64_t) uint8_t optionBuf[FF_OPTION_MAX_SIZE];
baseInfo->initOptions(optionBuf);
initStructureModuleOptions(data, baseInfo, optionBuf);
if (data->resultDoc != nullptr) {
fn(data, baseInfo, optionBuf);
} else {
yyjson_val* configModule = findStructureModuleConfig(data, baseInfo->name);
if (configModule != nullptr) {
baseInfo->parseJsonObject(optionBuf, configModule);
baseInfo->printModule(optionBuf);
} else {
baseInfo->printModule(optionBuf);
}
}
baseInfo->destroyOptions(optionBuf);
return true;
+11 -12
View File
@@ -5,7 +5,7 @@
const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options);
static FFlist ioCounters1;
static uint64_t time1;
static double time1;
void ffPrepareDiskIO(FFDiskIOOptions* options) {
if (options->detectTotal) {
@@ -16,9 +16,13 @@ void ffPrepareDiskIO(FFDiskIOOptions* options) {
return; // Already prepared
}
// The options cannot change between this call and `ffDetectDiskIO`: `ffPrepareCommandOption`
// and `parseStructureCommand` both build them through `initStructureModuleOptions`, which
// merges the module object from the JSON config. So the baseline always matches the second
// snapshot and needs no re-validation.
ffListInit(&ioCounters1);
ffDiskIOGetIoCounters(&ioCounters1, options);
time1 = ffTimeGetNow();
time1 = ffTimeGetTick();
}
const char* ffDetectDiskIO(FFlist* result, FFDiskIOOptions* options) {
@@ -33,22 +37,17 @@ const char* ffDetectDiskIO(FFlist* result, FFDiskIOOptions* options) {
}
if (time1 == 0) {
ffListInit(&ioCounters1);
error = ffDiskIOGetIoCounters(&ioCounters1, options);
if (error) {
return error;
}
time1 = ffTimeGetNow();
ffPrepareDiskIO(options);
}
if (ioCounters1.length == 0) {
return "No physical disk found";
}
uint64_t time2 = ffTimeGetNow();
while (time2 - time1 < options->waitTime) {
double time2 = ffTimeGetTick();
while (time2 - time1 < (double) options->waitTime) {
ffTimeSleep((uint32_t) (options->waitTime - (time2 - time1)));
time2 = ffTimeGetNow();
time2 = ffTimeGetTick();
}
error = ffDiskIOGetIoCounters(result, options);
@@ -72,7 +71,7 @@ const char* ffDetectDiskIO(FFlist* result, FFDiskIOOptions* options) {
uint64_t* prevValue = (uint64_t*) ((uint8_t*) icPrev + off);
uint64_t* currValue = (uint64_t*) ((uint8_t*) icCurr + off);
uint64_t temp = *currValue;
*currValue = (*currValue - *prevValue) * 1000 / (time2 - time1); // Calculate per second
*currValue = (uint64_t) ((double) (*currValue - *prevValue) * 1000.0 / (time2 - time1)); // Calculate per second
// For next function call
*prevValue = temp;
+11 -12
View File
@@ -3,7 +3,7 @@
#include "common/time.h"
static FFlist ioCounters1;
static uint64_t time1;
static double time1;
void ffPrepareNetIO(FFNetIOOptions* options) {
if (options->detectTotal) {
@@ -14,9 +14,13 @@ void ffPrepareNetIO(FFNetIOOptions* options) {
return; // Already prepared
}
// The options cannot change between this call and `ffDetectNetIO`: `ffPrepareCommandOption`
// and `parseStructureCommand` both build them through `initStructureModuleOptions`, which
// merges the module object from the JSON config. So the baseline always matches the second
// snapshot and needs no re-validation.
ffListInit(&ioCounters1);
ffNetIOGetIoCounters(&ioCounters1, options);
time1 = ffTimeGetNow();
time1 = ffTimeGetTick();
}
const char* ffDetectNetIO(FFlist* result, FFNetIOOptions* options) {
@@ -31,22 +35,17 @@ const char* ffDetectNetIO(FFlist* result, FFNetIOOptions* options) {
}
if (time1 == 0) {
ffListInit(&ioCounters1);
error = ffNetIOGetIoCounters(&ioCounters1, options);
if (error) {
return error;
}
time1 = ffTimeGetNow();
ffPrepareNetIO(options);
}
if (ioCounters1.length == 0) {
return "No network interfaces found";
}
uint64_t time2 = ffTimeGetNow();
while (time2 - time1 < options->waitTime) {
double time2 = ffTimeGetTick();
while (time2 - time1 < (double) options->waitTime) {
ffTimeSleep((uint32_t) (options->waitTime - (time2 - time1)));
time2 = ffTimeGetNow();
time2 = ffTimeGetTick();
}
error = ffNetIOGetIoCounters(result, options);
@@ -70,7 +69,7 @@ const char* ffDetectNetIO(FFlist* result, FFNetIOOptions* options) {
uint64_t* prevValue = (uint64_t*) ((uint8_t*) icPrev + off);
uint64_t* currValue = (uint64_t*) ((uint8_t*) icCurr + off);
uint64_t temp = *currValue;
*currValue = (*currValue - *prevValue) * 1000 / (time2 - time1); // Calculate per second
*currValue = (uint64_t) ((double) (*currValue - *prevValue) * 1000.0 / (time2 - time1)); // Calculate per second
*prevValue = temp;
}
}
+6 -12
View File
@@ -4,28 +4,22 @@
static FFlist first;
static double startTick;
static FFTopTypes preparedShowTypes = FF_TOP_TYPE_CPU | FF_TOP_TYPE_MEMORY | FF_TOP_TYPE_DISK;
void ffPrepareTopProcesses(FFTopTypes showTypes) {
if ((showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) == 0) {
return; // Memory usage is instantaneous; no baseline snapshot is needed
}
if (startTick != 0 && preparedShowTypes == showTypes) {
if (startTick != 0) {
return; // Already prepared
}
if (startTick != 0) {
// The set of requested types changed; discard the stale baseline
FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, first) {
ffStrbufDestroy(&item->name);
}
ffListDestroy(&first);
}
// `showTypes` cannot change between this call and `ffDetectTopProcesses`: `ffPrepareCommandOption`
// and `parseStructureCommand` both build the options through `initStructureModuleOptions`, which
// merges the module object from the JSON config. So the baseline always matches what the second
// snapshot collects and needs no re-validation.
ffListInit(&first);
startTick = ffTimeGetTick();
preparedShowTypes = showTypes;
ffTopGetProcessSnapshot(&first, showTypes);
}
@@ -102,7 +96,7 @@ const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) {
ffStrbufInitMove(&item->name, &snap->name);
}
} else {
if (startTick == 0 || preparedShowTypes != options->showTypes) {
if (startTick == 0) {
ffPrepareTopProcesses(options->showTypes);
}
+1 -1
View File
@@ -165,7 +165,7 @@ void ffInitDiskIOOptions(FFDiskIOOptions* options) {
ffStrbufInit(&options->namePrefix);
options->detectTotal = false;
options->waitTime = 500;
options->waitTime = 250;
}
void ffDestroyDiskIOOptions(FFDiskIOOptions* options) {
+1 -1
View File
@@ -192,7 +192,7 @@ void ffInitNetIOOptions(FFNetIOOptions* options) {
#endif
;
options->detectTotal = false;
options->waitTime = 500;
options->waitTime = 250;
}
void ffDestroyNetIOOptions(FFNetIOOptions* options) {