diff --git a/CHANGELOG.md b/CHANGELOG.md index f4816265d..3416a5a86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,16 @@ Changes: * ImageMagick is no longer used for image logos on Windows and macOS, and has been replaced by the platform image frameworks (WIC on Windows, ImageIO on macOS). (Logo, Windows / macOS) - * Image logos on Windows and macOS no longer depend on ImageMagick being installed. + * Image logos on Windows and macOS no longer depend on ImageMagick being installed. * Image sources that only ImageMagick could decode, such as SVG, PDF and PostScript, are no longer supported on Windows: neither WIC nor the embedded sixel encoder can decode them. * The `--logo-recache` option has been replaced by `--logo-cache `, and the `logo.recache` JSON property has been renamed to `logo.cache`. (Logo) * `--logo-cache true` (the default) reuses a cached rendering when it is valid, and writes it back on a cache miss. * `--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) diff --git a/doc/json_schema.json b/doc/json_schema.json index aa472e6af..d64c5796b 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -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": { diff --git a/presets/all.jsonc b/presets/all.jsonc index 73901ca30..8b5f2d4d6 100644 --- a/presets/all.jsonc +++ b/presets/all.jsonc @@ -99,7 +99,7 @@ "gamepad", "mouse", "keyboard", - "top", + "tpm", "netio", "diskio", { @@ -110,7 +110,7 @@ "type": "weather", "timeout": 1000 }, - "tpm", + "top", "version", "break", "colors" diff --git a/presets/ci.jsonc b/presets/ci.jsonc index c18994df0..96e948f88 100644 --- a/presets/ci.jsonc +++ b/presets/ci.jsonc @@ -101,7 +101,7 @@ "gamepad", "mouse", "keyboard", - "top", + "tpm", "netio", "diskio", { @@ -112,7 +112,7 @@ "type": "weather", "timeout": 1000 }, - "tpm", + "top", "version", "logo", "break", diff --git a/src/common/impl/commandoption.c b/src/common/impl/commandoption.c index 0b1bac8dc..2b11078b3 100644 --- a/src/common/impl/commandoption.c +++ b/src/common/impl/commandoption.c @@ -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->printModule(optionBuf); } baseInfo->destroyOptions(optionBuf); return true; diff --git a/src/detection/diskio/diskio.c b/src/detection/diskio/diskio.c index a2f53439c..d68570b78 100644 --- a/src/detection/diskio/diskio.c +++ b/src/detection/diskio/diskio.c @@ -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; diff --git a/src/detection/netio/netio.c b/src/detection/netio/netio.c index 3c986a863..6ebfe1405 100644 --- a/src/detection/netio/netio.c +++ b/src/detection/netio/netio.c @@ -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; } } diff --git a/src/detection/top/top.c b/src/detection/top/top.c index dd4442a7e..8b50991ec 100644 --- a/src/detection/top/top.c +++ b/src/detection/top/top.c @@ -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); } diff --git a/src/modules/diskio/diskio.c b/src/modules/diskio/diskio.c index fac2d6459..202772606 100644 --- a/src/modules/diskio/diskio.c +++ b/src/modules/diskio/diskio.c @@ -165,7 +165,7 @@ void ffInitDiskIOOptions(FFDiskIOOptions* options) { ffStrbufInit(&options->namePrefix); options->detectTotal = false; - options->waitTime = 500; + options->waitTime = 250; } void ffDestroyDiskIOOptions(FFDiskIOOptions* options) { diff --git a/src/modules/netio/netio.c b/src/modules/netio/netio.c index ef3da5d6a..55218ce4d 100644 --- a/src/modules/netio/netio.c +++ b/src/modules/netio/netio.c @@ -192,7 +192,7 @@ void ffInitNetIOOptions(FFNetIOOptions* options) { #endif ; options->detectTotal = false; - options->waitTime = 500; + options->waitTime = 250; } void ffDestroyNetIOOptions(FFNetIOOptions* options) {