summaryrefslogtreecommitdiffstats
path: root/src/detection/cpuusage
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/cpuusage
Add the files
Diffstat (limited to 'src/detection/cpuusage')
-rw-r--r--src/detection/cpuusage/cpuusage.c74
-rw-r--r--src/detection/cpuusage/cpuusage.h12
-rw-r--r--src/detection/cpuusage/cpuusage_apple.c33
-rw-r--r--src/detection/cpuusage/cpuusage_bsd.c58
-rw-r--r--src/detection/cpuusage/cpuusage_haiku.c27
-rw-r--r--src/detection/cpuusage/cpuusage_linux.c46
-rw-r--r--src/detection/cpuusage/cpuusage_nosupport.c6
-rw-r--r--src/detection/cpuusage/cpuusage_sunos.c38
-rw-r--r--src/detection/cpuusage/cpuusage_windows.c169
9 files changed, 463 insertions, 0 deletions
diff --git a/src/detection/cpuusage/cpuusage.c b/src/detection/cpuusage/cpuusage.c
new file mode 100644
index 0000000..a78ae7b
--- /dev/null
+++ b/src/detection/cpuusage/cpuusage.c
@@ -0,0 +1,74 @@
+#include "fastfetch.h"
+#include "detection/cpuusage/cpuusage.h"
+#include "common/time.h"
+
+#include <stdint.h>
+
+static FFlist cpuTimes1;
+static uint64_t startTime;
+
+void ffPrepareCPUUsage(void) {
+ if (startTime != 0) {
+ return; // Already prepared
+ }
+
+ ffListInit(&cpuTimes1);
+ ffGetCpuUsageInfo(&cpuTimes1);
+ startTime = ffTimeGetNow();
+}
+
+const char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result) {
+ const char* error = NULL;
+ if (startTime == 0) {
+ ffListInit(&cpuTimes1);
+ error = ffGetCpuUsageInfo(&cpuTimes1);
+ if (error) {
+ return error;
+ }
+ ffTimeSleep(options->waitTime);
+ } else {
+ uint64_t elapsedTime = ffTimeGetNow() - startTime;
+ if (elapsedTime < options->waitTime) {
+ ffTimeSleep(options->waitTime - (uint32_t) elapsedTime);
+ }
+ }
+
+ if (cpuTimes1.length == 0) {
+ return "No CPU cores found";
+ }
+
+ FF_LIST_AUTO_DESTROY cpuTimes2 = ffListCreate();
+ uint32_t retryCount = 0;
+
+retry:
+ error = ffGetCpuUsageInfo(&cpuTimes2);
+ if (error) {
+ return error;
+ }
+ if (cpuTimes1.length != cpuTimes2.length) {
+ return "Unexpected CPU usage result";
+ }
+
+ for (uint32_t i = 0; i < cpuTimes1.length; ++i) {
+ FFCpuUsageInfo* cpuTime1 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes1, i);
+ FFCpuUsageInfo* cpuTime2 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes2, i);
+ if (cpuTime2->totalAll <= cpuTime1->totalAll) {
+ if (++retryCount <= 3) {
+ ffListClear(&cpuTimes2);
+ ffTimeSleep(options->waitTime);
+ goto retry;
+ }
+ return "CPU time did not increase. Try increasing wait time.";
+ }
+ }
+
+ for (uint32_t i = 0; i < cpuTimes1.length; ++i) {
+ FFCpuUsageInfo* cpuTime1 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes1, i);
+ FFCpuUsageInfo* cpuTime2 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes2, i);
+ *FF_LIST_ADD(double, *result) = (double) (cpuTime2->inUseAll - cpuTime1->inUseAll) / (double) (cpuTime2->totalAll - cpuTime1->totalAll) * 100;
+ cpuTime1->inUseAll = cpuTime2->inUseAll;
+ cpuTime1->totalAll = cpuTime2->totalAll;
+ }
+ startTime = ffTimeGetNow();
+ return NULL;
+}
diff --git a/src/detection/cpuusage/cpuusage.h b/src/detection/cpuusage/cpuusage.h
new file mode 100644
index 0000000..353b729
--- /dev/null
+++ b/src/detection/cpuusage/cpuusage.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "fastfetch.h"
+#include "modules/cpuusage/option.h"
+
+typedef struct FFCpuUsageInfo {
+ uint64_t inUseAll;
+ uint64_t totalAll;
+} FFCpuUsageInfo;
+const char* ffGetCpuUsageInfo(FFlist* cpuTimes);
+
+const char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result); // list of double
diff --git a/src/detection/cpuusage/cpuusage_apple.c b/src/detection/cpuusage/cpuusage_apple.c
new file mode 100644
index 0000000..50a8b15
--- /dev/null
+++ b/src/detection/cpuusage/cpuusage_apple.c
@@ -0,0 +1,33 @@
+#include "fastfetch.h"
+#include "detection/cpuusage/cpuusage.h"
+
+#include <mach/processor_info.h>
+#include <mach/mach_host.h>
+#include <mach/vm_map.h>
+
+const char* ffGetCpuUsageInfo(FFlist* cpuTimes) {
+ natural_t numCPUs = 0U;
+ processor_info_array_t cpuInfo;
+ mach_msg_type_number_t numCpuInfo;
+
+ if (host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUs, &cpuInfo, &numCpuInfo) != KERN_SUCCESS) {
+ return "host_processor_info() failed";
+ }
+ if (numCPUs * CPU_STATE_MAX != numCpuInfo) {
+ return "Unexpected host_processor_info() result";
+ }
+
+ for (natural_t i = 0U; i < numCPUs; ++i) {
+ integer_t inUse = cpuInfo[CPU_STATE_MAX * i + CPU_STATE_USER] + cpuInfo[CPU_STATE_MAX * i + CPU_STATE_SYSTEM] + cpuInfo[CPU_STATE_MAX * i + CPU_STATE_NICE];
+ integer_t total = inUse + cpuInfo[CPU_STATE_MAX * i + CPU_STATE_IDLE];
+
+ FFCpuUsageInfo* info = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes);
+ *info = (FFCpuUsageInfo) {
+ .inUseAll = (uint64_t) inUse,
+ .totalAll = (uint64_t) total,
+ };
+ }
+
+ vm_deallocate(mach_task_self(), (vm_address_t) cpuInfo, numCpuInfo * sizeof(integer_t));
+ return NULL;
+}
diff --git a/src/detection/cpuusage/cpuusage_bsd.c b/src/detection/cpuusage/cpuusage_bsd.c
new file mode 100644
index 0000000..f690d15
--- /dev/null
+++ b/src/detection/cpuusage/cpuusage_bsd.c
@@ -0,0 +1,58 @@
+#include "detection/cpuusage/cpuusage.h"
+#include "common/mallocHelper.h"
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/resource.h>
+#include <stdlib.h>
+
+#if __OpenBSD__ || __NetBSD__
+ #include <sys/sched.h>
+#endif
+
+const char* ffGetCpuUsageInfo(FFlist* cpuTimes) {
+ size_t neededLength = 0;
+#if __OpenBSD__ || __NetBSD__
+ #ifdef KERN_CPTIME
+ int ctls[] = { CTL_KERN, KERN_CPTIME };
+ #else
+ int ctls[] = { CTL_KERN, KERN_CP_TIME };
+ #endif
+ if (sysctl(ctls, 2, NULL, &neededLength, NULL, 0) != 0) {
+ return "sysctl({CTL_KERN, KERN_CPTIME}, 2, NULL) failed";
+ }
+#else
+ if (sysctlbyname("kern.cp_times", NULL, &neededLength, NULL, 0) != 0) {
+ return "sysctlbyname(kern.cp_times, NULL) failed";
+ }
+#endif
+
+ uint32_t coreCount = (uint32_t) (neededLength / (CPUSTATES * sizeof(uint64_t)));
+ assert(coreCount > 0);
+
+ FF_AUTO_FREE uint64_t (*cpTimes)[CPUSTATES] = malloc(neededLength);
+
+#if __OpenBSD__ || __NetBSD__
+ if (sysctl(ctls, 2, cpTimes, &neededLength, NULL, 0) != 0) {
+ return "sysctl({CTL_KERN, KERN_CPTIME}, 2, NULL) failed";
+ }
+#else
+ if (sysctlbyname("kern.cp_times", cpTimes, &neededLength, NULL, 0) != 0) {
+ return "sysctlbyname(kern.cp_times, cpTime) failed";
+ }
+#endif
+
+ for (uint32_t i = 0; i < coreCount; ++i) {
+ uint64_t* cpTime = cpTimes[i];
+ uint64_t inUse = cpTime[CP_USER] + cpTime[CP_NICE] + cpTime[CP_SYS] + cpTime[CP_INTR];
+ uint64_t total = inUse + cpTime[CP_IDLE];
+
+ FFCpuUsageInfo* info = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes);
+ *info = (FFCpuUsageInfo) {
+ .inUseAll = inUse,
+ .totalAll = total,
+ };
+ }
+
+ return NULL;
+}
diff --git a/src/detection/cpuusage/cpuusage_haiku.c b/src/detection/cpuusage/cpuusage_haiku.c
new file mode 100644
index 0000000..f4d3c63
--- /dev/null
+++ b/src/detection/cpuusage/cpuusage_haiku.c
@@ -0,0 +1,27 @@
+#include "fastfetch.h"
+#include "detection/cpuusage/cpuusage.h"
+#include "common/mallocHelper.h"
+
+#include <OS.h>
+
+const char* ffGetCpuUsageInfo(FFlist* cpuTimes) {
+ system_info sysInfo;
+ if (get_system_info(&sysInfo) != B_OK) {
+ return "get_system_info() failed";
+ }
+
+ FF_AUTO_FREE cpu_info* cpuInfo = malloc(sizeof(*cpuInfo) * sysInfo.cpu_count);
+ if (get_cpu_info(0, sysInfo.cpu_count, cpuInfo) != B_OK) {
+ return "get_cpu_info() failed";
+ }
+
+ uint64_t uptime = (uint64_t) system_time();
+
+ for (uint32_t i = 0; i < sysInfo.cpu_count; ++i) {
+ FFCpuUsageInfo* info = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes);
+ info->inUseAll = (uint64_t) cpuInfo[i].active_time;
+ info->totalAll = uptime;
+ }
+
+ return NULL;
+}
diff --git a/src/detection/cpuusage/cpuusage_linux.c b/src/detection/cpuusage/cpuusage_linux.c
new file mode 100644
index 0000000..fd2f43b
--- /dev/null
+++ b/src/detection/cpuusage/cpuusage_linux.c
@@ -0,0 +1,46 @@
+#include "fastfetch.h"
+#include "detection/cpuusage/cpuusage.h"
+#include "common/io.h"
+
+#include <stdio.h>
+#include <inttypes.h>
+
+const char* ffGetCpuUsageInfo(FFlist* cpuTimes) {
+ char buf[PROC_FILE_BUFFSIZ];
+ ssize_t nRead = ffReadFileData("/proc/stat", ARRAY_SIZE(buf) - 1, buf);
+ if (nRead < 0) {
+#ifdef __ANDROID__
+ return "Accessing \"/proc/stat\" is restricted on Android O+";
+#else
+ return "ffReadFileData(\"/proc/stat\", ARRAY_SIZE(buf) - 1, buf) failed";
+#endif
+ }
+ buf[nRead] = '\0';
+
+ // Skip first line
+ char* start = NULL;
+ if ((start = strchr(buf, '\n')) == NULL) {
+ return "skip first line failed";
+ }
+ ++start;
+
+ uint64_t user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0, softirq = 0;
+ char* token = NULL;
+ while ((token = strchr(start, '\n'))) {
+ if (sscanf(start, "cpu%*d%" PRIu64 "%" PRIu64 "%" PRIu64 "%" PRIu64 "%" PRIu64 "%" PRIu64 "%" PRIu64 "%*[^\n]\n", &user, &nice, &system, &idle, &iowait, &irq, &softirq) == 7) {
+ uint64_t inUse = user + nice + system + irq + softirq;
+ uint64_t total = inUse + idle + iowait;
+
+ FFCpuUsageInfo* info = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes);
+ *info = (FFCpuUsageInfo) {
+ .inUseAll = inUse,
+ .totalAll = total,
+ };
+ } else {
+ break;
+ }
+ start = token + 1;
+ }
+
+ return NULL;
+}
diff --git a/src/detection/cpuusage/cpuusage_nosupport.c b/src/detection/cpuusage/cpuusage_nosupport.c
new file mode 100644
index 0000000..faade7b
--- /dev/null
+++ b/src/detection/cpuusage/cpuusage_nosupport.c
@@ -0,0 +1,6 @@
+#include "fastfetch.h"
+#include "detection/cpuusage/cpuusage.h"
+
+const char* ffGetCpuUsageInfo(FF_A_UNUSED FFlist* cpuTimes) {
+ return "Not support on this platform";
+}
diff --git a/src/detection/cpuusage/cpuusage_sunos.c b/src/detection/cpuusage/cpuusage_sunos.c
new file mode 100644
index 0000000..813d74d
--- /dev/null
+++ b/src/detection/cpuusage/cpuusage_sunos.c
@@ -0,0 +1,38 @@
+#include "fastfetch.h"
+#include "detection/cpuusage/cpuusage.h"
+
+#include <kstat.h>
+#include <sys/sysinfo.h>
+
+static inline void kstatFreeWrap(kstat_ctl_t** pkc) {
+ assert(pkc);
+ if (*pkc) {
+ kstat_close(*pkc);
+ }
+}
+
+const char* ffGetCpuUsageInfo(FFlist* cpuTimes) {
+ FF_A_CLEANUP(kstatFreeWrap) kstat_ctl_t* kc = kstat_open();
+ if (!kc) {
+ return "kstat_open() failed";
+ }
+
+ for (int i = 0;; ++i) {
+ kstat_t* ks = kstat_lookup(kc, "cpu_stat", i, NULL);
+
+ cpu_stat_t cs;
+ if (!ks || kstat_read(kc, ks, &cs) < 0) {
+ break;
+ }
+
+ uint64_t inUse = cs.cpu_sysinfo.cpu[CPU_USER] + cs.cpu_sysinfo.cpu[CPU_KERNEL];
+ uint64_t total = inUse + cs.cpu_sysinfo.cpu[CPU_IDLE] + cs.cpu_sysinfo.cpu[CPU_WAIT];
+
+ FFCpuUsageInfo* info = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes);
+ *info = (FFCpuUsageInfo) {
+ .inUseAll = inUse,
+ .totalAll = total,
+ };
+ }
+ return NULL;
+}
diff --git a/src/detection/cpuusage/cpuusage_windows.c b/src/detection/cpuusage/cpuusage_windows.c
new file mode 100644
index 0000000..c23c28a
--- /dev/null
+++ b/src/detection/cpuusage/cpuusage_windows.c
@@ -0,0 +1,169 @@
+#include "detection/cpuusage/cpuusage.h"
+#include "common/mallocHelper.h"
+#include "common/debug.h"
+
+#include <ntstatus.h>
+#include <windows.h>
+#include <wchar.h>
+#include "common/windows/perflib_.h"
+#include "common/windows/nt.h"
+
+static const char* getInfoByNqsi(FFlist* cpuTimes) {
+ ULONG size = 0;
+ if (NtQuerySystemInformation(SystemProcessorPerformanceInformation, NULL, 0, &size) != STATUS_INFO_LENGTH_MISMATCH) {
+ return "NtQuerySystemInformation(SystemProcessorPerformanceInformation, NULL) failed";
+ }
+
+ SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* FF_AUTO_FREE pinfo = (SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION*) malloc(size);
+ if (!NT_SUCCESS(NtQuerySystemInformation(SystemProcessorPerformanceInformation, pinfo, size, &size))) {
+ return "NtQuerySystemInformation(SystemProcessorPerformanceInformation, size) failed";
+ }
+
+ for (uint32_t i = 0; i < size / sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION); ++i) {
+ SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* coreInfo = pinfo + i;
+
+ // KernelTime includes IdleTime and DpcTime.
+ coreInfo->KernelTime.QuadPart -= coreInfo->IdleTime.QuadPart;
+
+ uint64_t inUse = (uint64_t) (coreInfo->UserTime.QuadPart + coreInfo->KernelTime.QuadPart);
+ uint64_t total = inUse + (uint64_t) coreInfo->IdleTime.QuadPart;
+
+ FFCpuUsageInfo* info = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes);
+ *info = (FFCpuUsageInfo) {
+ .inUseAll = inUse,
+ .totalAll = total,
+ };
+ }
+
+ return NULL;
+}
+
+static const char* getInfoByPerflib(FFlist* cpuTimes) {
+ static HANDLE hQuery = NULL;
+
+ if (hQuery == NULL) {
+ struct FFPerfQuerySpec {
+ PERF_COUNTER_IDENTIFIER Identifier;
+ WCHAR Name[16];
+ } querySpec = {
+ .Identifier = {
+ // Processor Information GUID
+ // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\_V2Providers\{383487a6-3676-4870-a4e7-d45b30c35629}\{b4fc721a-0378-476f-89ba-a5a79f810b36}
+ .CounterSetGuid = { 0xb4fc721a, 0x0378, 0x476f, { 0x89, 0xba, 0xa5, 0xa7, 0x9f, 0x81, 0x0b, 0x36 } },
+ .Size = sizeof(querySpec),
+ .CounterId = PERF_WILDCARD_COUNTER, // https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data
+ .InstanceId = PERF_WILDCARD_COUNTER,
+ },
+ .Name = PERF_WILDCARD_INSTANCE,
+ };
+
+ if (PerfOpenQueryHandle(NULL, &hQuery) != ERROR_SUCCESS) {
+ PerfCloseQueryHandle(hQuery);
+ hQuery = INVALID_HANDLE_VALUE;
+ return "PerfOpenQueryHandle() failed";
+ }
+
+ if (PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec)) != ERROR_SUCCESS) {
+ PerfCloseQueryHandle(hQuery);
+ hQuery = INVALID_HANDLE_VALUE;
+ return "PerfAddCounters() failed";
+ }
+
+ if (querySpec.Identifier.Status != ERROR_SUCCESS) {
+ PerfCloseQueryHandle(hQuery);
+ hQuery = INVALID_HANDLE_VALUE;
+ return "PerfAddCounters() reports invalid identifier";
+ }
+ }
+
+ if (hQuery == INVALID_HANDLE_VALUE) {
+ return "Init hQuery failed";
+ }
+
+ DWORD dataSize = 0;
+ if (PerfQueryCounterData(hQuery, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY) {
+ return "PerfQueryCounterData(NULL) failed";
+ }
+
+ if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER)) {
+ return "instance doesn't exist";
+ }
+
+ FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = (PERF_DATA_HEADER*) malloc(dataSize);
+ if (PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize) != ERROR_SUCCESS) {
+ return "PerfQueryCounterData(pDataHeader) failed";
+ }
+
+ PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*) (pDataHeader + 1);
+ if (pCounterHeader->dwType != PERF_COUNTERSET) {
+ return "Invalid counter type";
+ }
+
+ PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*) (pCounterHeader + 1);
+ if (pMultiCounters->dwCounters == 0) {
+ return "No CPU counters found";
+ }
+
+ PERF_MULTI_INSTANCES* pMultiInstances = (PERF_MULTI_INSTANCES*) ((BYTE*) pMultiCounters + pMultiCounters->dwSize);
+ if (pMultiInstances->dwInstances == 0) {
+ return "No CPU instances found";
+ }
+
+ PERF_INSTANCE_HEADER* pInstanceHeader = (PERF_INSTANCE_HEADER*) (pMultiInstances + 1);
+ for (DWORD iInstance = 0; iInstance < pMultiInstances->dwInstances; ++iInstance) {
+ const wchar_t* instanceName = (const wchar_t*) ((BYTE*) pInstanceHeader + sizeof(*pInstanceHeader));
+
+ PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*) ((BYTE*) pInstanceHeader + pInstanceHeader->Size);
+
+ uint64_t processorUtility = UINT64_MAX, utilityBase = UINT64_MAX;
+ for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++) {
+ DWORD* pCounterIds = (DWORD*) (pMultiCounters + 1);
+ // https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data
+ switch (pCounterIds[iCounter]) {
+ case 26: // % Processor Utility (#26, Type=PERF_AVERAGE_BULK)
+ assert(pCounterData->dwDataSize == sizeof(uint64_t));
+ processorUtility = *(uint64_t*) (pCounterData + 1);
+ break;
+ case 27: // % Utility Base (#27, Type=PERF_AVERAGE_BASE)
+ assert(pCounterData->dwDataSize == sizeof(uint32_t));
+ utilityBase = *(uint32_t*) (pCounterData + 1) * 100LLU;
+ break;
+ }
+
+ pCounterData = (PERF_COUNTER_DATA*) ((BYTE*) pCounterData + pCounterData->dwSize);
+ }
+
+ if (wcschr(instanceName, L'_') == NULL /* ignore `_Total` */) {
+ if (processorUtility == UINT64_MAX) {
+ return "Counter \"% Processor Utility\" are not supported";
+ }
+
+ FFCpuUsageInfo* info = FF_LIST_ADD(FFCpuUsageInfo, *cpuTimes);
+ *info = (FFCpuUsageInfo) {
+ .inUseAll = processorUtility,
+ .totalAll = utilityBase,
+ };
+ }
+
+ pInstanceHeader = (PERF_INSTANCE_HEADER*) pCounterData;
+ }
+
+ return NULL;
+}
+
+const char* ffGetCpuUsageInfo(FFlist* cpuTimes) {
+ const char* error = NULL;
+
+ if (ffIsWindows10OrGreater()) {
+ error = getInfoByPerflib(cpuTimes);
+ FF_DEBUG("Get CPU usage info by Perflib: %s", error ?: "success");
+ if (!error) {
+ return NULL;
+ }
+ ffListClear(cpuTimes);
+ }
+
+ error = getInfoByNqsi(cpuTimes);
+ FF_DEBUG("Get CPU usage info by NtQuerySystemInformation: %s", error ?: "success");
+ return error;
+}