diff options
author | Christian Göttsche <cgzones@googlemail.com> | 2023-08-29 13:03:31 +0200 |
---|---|---|
committer | cgzones <cgzones@googlemail.com> | 2024-03-27 19:49:23 +0100 |
commit | f8c5bdd86478a852c8d3967630dd81fdd70fa030 (patch) | |
tree | 868c30988bd44ee8e8be487ad2dd22f23e3cc46b /linux/GPUMeter.c | |
parent | ec608672cea2b857a7b43832bfabae2bbe6c04da (diff) |
Linux: add GPU meter and process column
Based on the DRM client usage stats[1] add statistics for GPU time spend
and percentage utilization.
[1]: https://www.kernel.org/doc/html/latest/gpu/drm-usage-stats.html
Diffstat (limited to 'linux/GPUMeter.c')
-rw-r--r-- | linux/GPUMeter.c | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/linux/GPUMeter.c b/linux/GPUMeter.c new file mode 100644 index 00000000..fbd32ea8 --- /dev/null +++ b/linux/GPUMeter.c @@ -0,0 +1,179 @@ +/* +htop - GPUMeter.c +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "linux/GPUMeter.h" + +#include "CRT.h" +#include "RichString.h" +#include "linux/LinuxMachine.h" + + +static size_t activeMeters; + +bool GPUMeter_active(void) { + return activeMeters > 0; +} + +struct EngineData { + const char* key; /* owned by LinuxMachine */ + unsigned long long int timeDiff; +}; + +static struct EngineData GPUMeter_engineData[4]; +static unsigned long long int prevResidueTime, curResidueTime; +static double totalUsage; +static unsigned long long int totalGPUTimeDiff; + +static const int GPUMeter_attributes[] = { + GPU_ENGINE_1, + GPU_ENGINE_2, + GPU_ENGINE_3, + GPU_ENGINE_4, + GPU_RESIDUE, +}; + +static int humanTimeUnit(char* buffer, size_t size, unsigned long long int value) { + + if (value < 1000) + return xSnprintf(buffer, size, "%3lluns", value); + + if (value < 10000) + return xSnprintf(buffer, size, "%1llu.%1lluus", value / 1000, (value % 1000) / 100); + + value /= 1000; + + if (value < 1000) + return xSnprintf(buffer, size, "%3lluus", value); + + if (value < 10000) + return xSnprintf(buffer, size, "%1llu.%1llums", value / 1000, (value % 1000) / 100); + + value /= 1000; + + if (value < 1000) + return xSnprintf(buffer, size, "%3llums", value); + + if (value < 10000) + return xSnprintf(buffer, size, "%1llu.%1llus", value / 1000, (value % 1000) / 100); + + value /= 1000; + + if (value < 600) + return xSnprintf(buffer, size, "%3llus", value); + + value /= 60; + + if (value < 600) + return xSnprintf(buffer, size, "%3llum", value); + + value /= 60; + + if (value < 96) + return xSnprintf(buffer, size, "%3lluh", value); + + value /= 24; + + return xSnprintf(buffer, size, "%3llud", value); +} + +static void GPUMeter_updateValues(Meter* this) { + const Machine* host = this->host; + const LinuxMachine* lhost = (const LinuxMachine*) host; + const GPUEngineData* gpuEngineData; + char* buffer = this->txtBuffer; + size_t size = sizeof(this->txtBuffer); + int written; + unsigned int i; + uint64_t monotonictimeDelta; + + assert(ARRAYSIZE(GPUMeter_engineData) + 1 == ARRAYSIZE(GPUMeter_attributes)); + + totalGPUTimeDiff = saturatingSub(lhost->curGpuTime, lhost->prevGpuTime); + monotonictimeDelta = host->monotonicMs - host->prevMonotonicMs; + + prevResidueTime = curResidueTime; + curResidueTime = lhost->curGpuTime; + + for (gpuEngineData = lhost->gpuEngineData, i = 0; gpuEngineData && i < ARRAYSIZE(GPUMeter_engineData); gpuEngineData = gpuEngineData->next, i++) { + GPUMeter_engineData[i].key = gpuEngineData->key; + GPUMeter_engineData[i].timeDiff = saturatingSub(gpuEngineData->curTime, gpuEngineData->prevTime); + + curResidueTime = saturatingSub(curResidueTime, gpuEngineData->curTime); + + this->values[i] = 100.0 * GPUMeter_engineData[i].timeDiff / (1000 * 1000) / monotonictimeDelta; + } + + this->values[ARRAYSIZE(GPUMeter_engineData)] = 100.0 * saturatingSub(curResidueTime, prevResidueTime) / (1000 * 1000) / monotonictimeDelta; + + totalUsage = 100.0 * totalGPUTimeDiff / (1000 * 1000) / monotonictimeDelta; + written = snprintf(buffer, size, "%.1f", totalUsage); + METER_BUFFER_CHECK(buffer, size, written); + + METER_BUFFER_APPEND_CHR(buffer, size, '%'); +} + +static void GPUMeter_display(const Object* cast, RichString* out) { + char buffer[50]; + int written; + const Meter* this = (const Meter*)cast; + unsigned int i; + + RichString_writeAscii(out, CRT_colors[METER_TEXT], ":"); + written = xSnprintf(buffer, sizeof(buffer), "%4.1f", totalUsage); + RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written); + RichString_appendnAscii(out, CRT_colors[METER_TEXT], "%(", 2); + written = humanTimeUnit(buffer, sizeof(buffer), totalGPUTimeDiff); + RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written); + RichString_appendnAscii(out, CRT_colors[METER_TEXT], ")", 1); + + for (i = 0; i < ARRAYSIZE(GPUMeter_engineData); i++) { + if (!GPUMeter_engineData[i].key) + break; + + RichString_appendnAscii(out, CRT_colors[METER_TEXT], " ", 1); + RichString_appendAscii(out, CRT_colors[METER_TEXT], GPUMeter_engineData[i].key); + RichString_appendnAscii(out, CRT_colors[METER_TEXT], ":", 1); + if (isNonnegative(this->values[i])) + written = xSnprintf(buffer, sizeof(buffer), "%4.1f", this->values[i]); + else + written = xSnprintf(buffer, sizeof(buffer), " N/A"); + RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written); + RichString_appendnAscii(out, CRT_colors[METER_TEXT], "%(", 2); + written = humanTimeUnit(buffer, sizeof(buffer), GPUMeter_engineData[i].timeDiff); + RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written); + RichString_appendnAscii(out, CRT_colors[METER_TEXT], ")", 1); + } +} + +static void GPUMeter_init(Meter* this ATTR_UNUSED) { + activeMeters++; +} + +static void GPUMeter_done(Meter* this ATTR_UNUSED) { + assert(activeMeters > 0); + activeMeters--; +} + +const MeterClass GPUMeter_class = { + .super = { + .extends = Class(Meter), + .delete = Meter_delete, + .display = GPUMeter_display, + }, + .init = GPUMeter_init, + .done = GPUMeter_done, + .updateValues = GPUMeter_updateValues, + .defaultMode = BAR_METERMODE, + .maxItems = ARRAYSIZE(GPUMeter_engineData) + 1, + .total = 100.0, + .attributes = GPUMeter_attributes, + .name = "GPU", + .uiName = "GPU usage", + .caption = "GPU" +}; |