diff --git a/Action.c b/Action.c index 1d3bccc51..c003acb1e 100644 --- a/Action.c +++ b/Action.c @@ -646,6 +646,29 @@ static Htop_Reaction actionTogglePauseUpdate(State* st) { return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_KEEP_FOLLOWING; } +#ifdef NVIDIA_JETSON +#include "NvidiaJetson.h" +#include "ProcessTable.h" + +static Htop_Reaction actionToggleGpuFilter(State* st) { + static Hashtable *stash = NULL; + + Hashtable *GpuPidMatchList = NvidiaJetson_GetPidMatchList(); + if (GpuPidMatchList) { + st->showGpuProcesses = !st->showGpuProcesses; + + ProcessTable *pt = (ProcessTable *)st->host->activeTable; + if (st->showGpuProcesses) { + stash = pt->pidMatchList; + pt->pidMatchList = GpuPidMatchList; + } else { + pt->pidMatchList = stash; + } + } + return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_KEEP_FOLLOWING; +} +#endif + static const struct { const char* key; bool roInactive; @@ -658,6 +681,9 @@ static const struct { { .key = " F3 /: ", .roInactive = false, .info = "incremental name search" }, { .key = " F4 \\: ", .roInactive = false, .info = "incremental name filtering" }, { .key = " F5 t: ", .roInactive = false, .info = "tree view" }, +#ifdef NVIDIA_JETSON + { .key = " g: ", .roInactive = false, .info = "show GPU processes (root only)" }, +#endif { .key = " p: ", .roInactive = false, .info = "toggle program path" }, { .key = " m: ", .roInactive = false, .info = "toggle merged command" }, { .key = " Z: ", .roInactive = false, .info = "pause/resume process updates" }, @@ -933,6 +959,9 @@ void Action_setBindings(Htop_Action* keys) { keys['a'] = actionSetAffinity; keys['c'] = actionTagAllChildren; keys['e'] = actionShowEnvScreen; +#ifdef NVIDIA_JETSON + keys['g'] = actionToggleGpuFilter; +#endif keys['h'] = actionHelp; keys['k'] = actionKill; keys['l'] = actionLsof; diff --git a/Action.h b/Action.h index caade03cd..772885575 100644 --- a/Action.h +++ b/Action.h @@ -39,6 +39,9 @@ typedef struct State_ { bool pauseUpdate; bool hideSelection; bool hideMeters; +#ifdef NVIDIA_JETSON + bool showGpuProcesses; +#endif } State; static inline bool State_hideFunctionBar(const State* st) { diff --git a/CPUMeter.c b/CPUMeter.c index 69da88db0..4ae231eac 100644 --- a/CPUMeter.c +++ b/CPUMeter.c @@ -98,17 +98,30 @@ static void CPUMeter_updateValues(Meter* this) { } } + #ifdef NVIDIA_JETSON + if (settings->showCPUTemperature) { + char c = 'C'; + double cpuTemperature = this->values[CPU_METER_TEMPERATURE]; + if (settings->degreeFahrenheit) { + c = 'F'; + cpuTemperature = ConvCelsiusToFahrenheit(cpuTemperature); + } + /* snprintf correctly represents double NAN numbers as 'nan' */ + xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%.1f%s%c", cpuTemperature, CRT_degreeSign, c); + } + #else #ifdef BUILD_WITH_CPU_TEMP if (settings->showCPUTemperature) { double cpuTemperature = this->values[CPU_METER_TEMPERATURE]; if (isNaN(cpuTemperature)) xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "N/A"); else if (settings->degreeFahrenheit) - xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%3d%sF", (int)(cpuTemperature * 9 / 5 + 32), CRT_degreeSign); + xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%3d%sF", (int)(ConvCelsiusToFahrenheit(cpuTemperature)), CRT_degreeSign); else xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), "%d%sC", (int)cpuTemperature, CRT_degreeSign); } #endif + #endif xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%s%s%s%s%s", cpuUsageBuffer, diff --git a/CRT.c b/CRT.c index 9265c6a39..0a1666e21 100644 --- a/CRT.c +++ b/CRT.c @@ -231,6 +231,9 @@ static int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = { [DYNAMIC_MAGENTA] = ColorPair(Magenta, Black), [DYNAMIC_YELLOW] = ColorPair(Yellow, Black), [DYNAMIC_WHITE] = ColorPair(White, Black), +#ifdef NVIDIA_JETSON + [GPU_FILTER] = A_BOLD | ColorPair(Red, Cyan), +#endif }, [COLORSCHEME_MONOCHROME] = { [RESET_COLOR] = A_NORMAL, diff --git a/CRT.h b/CRT.h index 576635621..1e4934ec2 100644 --- a/CRT.h +++ b/CRT.h @@ -158,6 +158,9 @@ typedef enum ColorElements_ { DYNAMIC_MAGENTA, DYNAMIC_YELLOW, DYNAMIC_WHITE, +#ifdef NVIDIA_JETSON + GPU_FILTER, +#endif LAST_COLORELEMENT } ColorElements; diff --git a/DisplayOptionsPanel.c b/DisplayOptionsPanel.c index 4bbd1613f..8195a2c01 100644 --- a/DisplayOptionsPanel.c +++ b/DisplayOptionsPanel.c @@ -173,10 +173,10 @@ DisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager* Panel_add(super, (Object*) CheckItem_newByRef("Also show CPU frequency", &(settings->showCPUFrequency))); #ifdef BUILD_WITH_CPU_TEMP Panel_add(super, (Object*) CheckItem_newByRef( - #if defined(HTOP_LINUX) - "Also show CPU temperature (requires libsensors)", - #elif defined(HTOP_FREEBSD) + #if defined(HTOP_FREEBSD) || defined(NVIDIA_JETSON) "Also show CPU temperature", + #elif defined(HTOP_LINUX) + "Also show CPU temperature (requires libsensors)", #else #error Unknown temperature implementation! #endif diff --git a/Machine.c b/Machine.c index 8ac235d1d..16864ed1d 100644 --- a/Machine.c +++ b/Machine.c @@ -6,6 +6,7 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "NvidiaJetson.h" #include "config.h" // IWYU pragma: keep #include "Machine.h" @@ -128,4 +129,8 @@ void Machine_scanTables(Machine* this) { } Row_setUidColumnWidth(this->maxUserId); + + #ifdef NVIDIA_JETSON + NvidiaJetson_LoadGpuProcessTable(this->activeTable->table); + #endif } diff --git a/MainPanel.c b/MainPanel.c index b665e0e26..6ca834525 100644 --- a/MainPanel.c +++ b/MainPanel.c @@ -195,6 +195,11 @@ static void MainPanel_drawFunctionBar(Panel* super, bool hideFunctionBar) { if (this->state->pauseUpdate) { FunctionBar_append("PAUSED", CRT_colors[PAUSED]); } +#ifdef NVIDIA_JETSON + if (this->state->showGpuProcesses) { + FunctionBar_append("GPU", CRT_colors[GPU_FILTER]); + } +#endif } static void MainPanel_printHeader(Panel* super) { diff --git a/Makefile.am b/Makefile.am index cf2ccee43..214f64c52 100644 --- a/Makefile.am +++ b/Makefile.am @@ -215,6 +215,11 @@ linux_platform_sources = \ zfs/ZfsArcMeter.c \ zfs/ZfsCompressedArcMeter.c +if NVIDIA_JETSON +linux_platform_headers += linux/NvidiaJetson.h +linux_platform_sources += linux/NvidiaJetson.c +endif + if HAVE_DELAYACCT linux_platform_headers += linux/LibNl.h linux_platform_sources += linux/LibNl.c diff --git a/XUtils.h b/XUtils.h index 32e9446df..fb739f754 100644 --- a/XUtils.h +++ b/XUtils.h @@ -123,6 +123,13 @@ char* xStrndup(const char* str, size_t len); ATTR_NONNULL ATTR_ACCESS3_W(2, 3) ssize_t xReadfile(const char* pathname, void* buffer, size_t count); +#ifdef NVIDIA_JETSON /* uncomment if you need this functionality somewhere else */ +ATTR_NONNULL ATTR_ACCESS3_W(2, 3) +static inline double xReadNumberFromFile(const char *pathname, char *buf, const size_t len) { + ssize_t nread = xReadfile(pathname, buf, len); + return nread > 0 ? strtod(buf, NULL) : NAN; +} +#endif ATTR_NONNULL ATTR_ACCESS3_W(3, 4) ssize_t xReadfileat(openat_arg_t dirfd, const char* pathname, void* buffer, size_t count); @@ -178,4 +185,9 @@ static inline int xDirfd(DIR* dirp) { return r; } + +static inline double ConvCelsiusToFahrenheit(const double celsius) { + return celsius * 9 / 5 + 32; +} + #endif diff --git a/configure.ac b/configure.ac index 391299e6a..6a3a70720 100644 --- a/configure.ac +++ b/configure.ac @@ -1140,7 +1140,47 @@ case "$enable_sensors" in AC_MSG_ERROR([bad value '$enable_sensors' for --enable-sensors]) ;; esac -if test "$enable_sensors" = yes || test "$my_htop_platform" = freebsd; then + +AC_ARG_ENABLE([nvidia-jetson], + [AS_HELP_STRING([--enable-nvidia-jetson], + [enable nvidia jetson support @<:@default=check@:>@])], + [], + [enable_nvidia_jetson=check]) +case "$enable_nvidia_jetson" in + no) + ;; + check) + if test -f "/etc/nv_tegra_release"; then + if grep -q "BOARD" "/etc/nv_tegra_release"; then + enable_nvidia_jetson=yes + fi + fi + ;; + yes) + if test -f "/etc/nv_tegra_release"; then + if grep -q "BOARD" "/etc/nv_tegra_release"; then + enable_nvidia_jetson=yes + else + enable_nvidia_jetson=no + fi + else + enable_nvidia_jetson=no + fi + ;; + *) + AC_MSG_ERROR([bad value '$enable_nvidia_jetson' for --enable-nvidia-jetson]) + ;; +esac + +if test "$enable_nvidia_jetson" = yes; then + AC_DEFINE([NVIDIA_JETSON], [1], [Detected correct NVIDIA Jetson board]) +else + AC_MSG_NOTICE([This is not a NVIDIA Jetson board]) +fi + +AM_CONDITIONAL([NVIDIA_JETSON], [test "$enable_nvidia_jetson" = yes]) + +if test "$enable_sensors" = yes || test "$my_htop_platform" = freebsd || test "$enable_nvidia_jetson" = yes; then AC_DEFINE([BUILD_WITH_CPU_TEMP], [1], [Define if CPU temperature option should be enabled.]) fi @@ -1278,6 +1318,7 @@ AC_MSG_RESULT([ (Linux) delay accounting: $enable_delayacct (Linux) sensors: $enable_sensors (Linux) capabilities: $enable_capabilities + (Linux) nvidia-jeston $enable_nvidia_jetson unicode: $enable_unicode affinity: $enable_affinity unwind: $enable_unwind diff --git a/linux/LinuxMachine.c b/linux/LinuxMachine.c index 188358ef1..3efac845f 100644 --- a/linux/LinuxMachine.c +++ b/linux/LinuxMachine.c @@ -32,6 +32,7 @@ in the source distribution for its full text. #include "UsersTable.h" #include "XUtils.h" +#include "linux/NvidiaJetson.h" #include "linux/Platform.h" // needed for GNU/hurd to get PATH_MAX // IWYU pragma: keep #ifdef HAVE_SENSORS_SENSORS_H @@ -741,15 +742,20 @@ void Machine_scan(Machine* super) { const Settings* settings = super->settings; if (settings->showCPUFrequency #ifdef HAVE_SENSORS_SENSORS_H - || settings->showCPUTemperature + || settings->showCPUTemperature /* TODO: looks like this line in the condition might be removed */ #endif ) LinuxMachine_scanCPUFrequency(this); + #ifdef NVIDIA_JETSON + if (settings->showCPUTemperature) + NvidiaJetson_getCPUTemperatures(this->cpuData, super->existingCPUs); + #else #ifdef HAVE_SENSORS_SENSORS_H if (settings->showCPUTemperature) LibSensors_getCPUTemperatures(this->cpuData, super->existingCPUs, super->activeCPUs); #endif + #endif } Machine* Machine_new(UsersTable* usersTable, uid_t userId) { @@ -792,6 +798,10 @@ Machine* Machine_new(UsersTable* usersTable, uid_t userId) { // Initialize CPU count LinuxMachine_updateCPUcount(this); + #ifdef NVIDIA_JETSON + NvidiaJetson_FindSensors(); + #endif + #ifdef HAVE_SENSORS_SENSORS_H // Fetch CPU topology LinuxMachine_fetchCPUTopologyFromCPUinfo(this); diff --git a/linux/LinuxMachine.h b/linux/LinuxMachine.h index 4bc334dda..d9089b6a3 100644 --- a/linux/LinuxMachine.h +++ b/linux/LinuxMachine.h @@ -46,9 +46,10 @@ typedef struct CPUData_ { double frequency; - #ifdef HAVE_SENSORS_SENSORS_H + #ifdef BUILD_WITH_CPU_TEMP double temperature; - + #endif + #ifdef HAVE_SENSORS_SENSORS_H int physicalID; /* different for each CPU socket */ int coreID; /* same for hyperthreading */ int ccdID; /* same for each AMD chiplet */ diff --git a/linux/LinuxProcess.c b/linux/LinuxProcess.c index 3a250283a..a99105d8b 100644 --- a/linux/LinuxProcess.c +++ b/linux/LinuxProcess.c @@ -6,6 +6,7 @@ Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "NvidiaJetson.h" #include "config.h" // IWYU pragma: keep #include "linux/LinuxProcess.h" @@ -112,6 +113,9 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { #endif [GPU_TIME] = { .name = "GPU_TIME", .title = "GPU_TIME ", .description = "Total GPU time", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, [GPU_PERCENT] = { .name = "GPU_PERCENT", .title = " GPU% ", .description = "Percentage of the GPU time the process used in the last sampling", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, }, +#ifdef NVIDIA_JETSON + [GPU_MEM]= { .name = "GPU_MEM", .title = "GPU_M ", .description = "GPU memory allocated for the process", .flags = 0, .defaultSortDesc = true, }, +#endif }; Process* LinuxProcess_new(const Machine* host) { @@ -362,6 +366,9 @@ static void LinuxProcess_rowWriteField(const Row* super, RichString* str, Proces xSnprintf(buffer, n, "N/A "); } break; +#ifdef NVIDIA_JETSON + case GPU_MEM: Row_printKBytes(str, lp->gpu_mem, coloring); return; +#endif default: Process_writeField(this, str, field); return; @@ -466,6 +473,10 @@ static int LinuxProcess_compareByKey(const Process* v1, const Process* v2, Proce return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time); case ISCONTAINER: return SPACESHIP_NUMBER(v1->isRunningInContainer, v2->isRunningInContainer); +#ifdef NVIDIA_JETSON + case GPU_MEM: + return SPACESHIP_NUMBER(p1->gpu_mem, p2->gpu_mem); +#endif default: return Process_compareByKey_Base(v1, v2, key); } diff --git a/linux/LinuxProcess.h b/linux/LinuxProcess.h index fafd7d004..2d7525f4f 100644 --- a/linux/LinuxProcess.h +++ b/linux/LinuxProcess.h @@ -115,6 +115,10 @@ typedef struct LinuxProcess_ { /* Activity of GPU: 0 if active, otherwise time of last scan in milliseconds */ uint64_t gpu_activityMs; + #ifdef NVIDIA_JETSON + uint64_t gpu_mem; + #endif + /* Autogroup scheduling (CFS) information */ long int autogroup_id; int autogroup_nice; diff --git a/linux/NvidiaJetson.c b/linux/NvidiaJetson.c new file mode 100644 index 000000000..7975101fe --- /dev/null +++ b/linux/NvidiaJetson.c @@ -0,0 +1,322 @@ +#include "config.h" +#ifdef NVIDIA_JETSON + + +#include "CRT.h" +#include "Hashtable.h" +#include "Macros.h" +#include "Meter.h" +#include "XUtils.h" +#include "linux/NvidiaJetson.h" +#include "linux/LinuxProcess.h" + + +#include +#include +#include +#include + + +/* + +NVIDIA Jetson devices support is located here. + +Jetson has: +- one CPU temperature sensor per 8 cores. +- one GPU temperature sensor, on Jetson Orin it goes off if no GPU load: user gets error on file open +- the process table where kernel nvgpu driver collects GPU clients (experimental, root access only): + * process id + * process name + * memory in kilobytes allocated for GPU (Jetson device shares system RAM for GPU) + +The code tries to find out the correct sensors during the application startup. As an example, the sensors +location for NVIDIA Jetson Orin: +- CPU temperature: /sys/devices/virtual/thermal/thermal_zone0/type +- GPU temperature: /sys/devices/virtual/thermal/thermal_zone1/type +- GPU frequency: /sys/class/devfreq/17000000.gpu/cur_freq +- GPU curr load: /sys/class/devfreq/17000000.gpu/device/load + +Measure: +- The GPU frequency is provided in Hz, shown in MHz. +- The CPU/GPU temperatures are provided in Celsius multipled by 1000 (milli Celsius), shown in Cesius +- The Farenheit support is not provided + +If htop starts with root privileges (effective user id is 0), the experimental code activates. +It reads the fixed sysfs file /sys/kernel/debug/nvmap/iovmm/clients with the following content, e.g.: +``` +CLIENT PROCESS PID SIZE +user gpu_burn 7979 23525644K +user gnome_shell 8119 5800K +user Xorg 2651 17876K +total 23549320K +``` +Unfortunately, the /sys/kernel/debug/... files are allowed to read only for the root user, that's why the restriction applies. + +The patch out of this file adds a separate field 'GPU_MEM', which reads data from LinuxProcess::gpu_mem field. +The field stores memory allocated for GPU in kilobytes. It is populated by the function +NvidiaJetson_LoadGpuProcessTable (the implementation is located here), which is called at the end of the function +Machine_scanTables. + +Additionally, the new Action is added: actionToggleGpuFilter, which is activated by 'g' hot key (the help is updated +appropriately). The GpuFilter shows only the processes which currently utilize GPU (i.e. highly extended +nvmap/iovmm/clients table). It is achieved by the filtering machinery associated with ProcessTable::pidMatchList. +The code below constructs GPU_PID_MATCH_LIST hash table, then actionToggleGpuFilter either stores it to the +ProcessTable::pidMatchList or restores old value of ProcessTable::pidMatchList. + +The separate LinuxProcess's PROCESS_FLAG_LINUX_GPU_JETSON (or something ...) flag isn't added for GPU_MEM, because +currently the functionality of population LinuxProcess::gpu_mem is shared with the GPU consumers filter construction. +So, even if GPU_MEM field is not activated, the filter showing GPU consumers should work. This kind of architecture is +chosen intentially since it saves memory for the hash table GPU_PID_MATCH_LIST (which is now actually a set), and therefore +increases performance. All other approaches convert GPU_PID_MATCH_LIST to a true key/value storage (key = pid, +value = gpu memory allocated) with further merge code. + +*/ + + +/* global paths per each sensor */ +char CPU_TEMP_SENSOR_PATH[64]; +char GPU_TEMP_SENSOR_PATH[64]; +char GPU_FREQ_SENSOR_PATH[64]; +char GPU_LOAD_SENSOR_PATH[64]; + + +/* #define DEBUG_ME 1 */ +#define MAX_GPU_PROCESS_COUNT 256UL +static Hashtable *GPU_PID_MATCH_LIST = NULL; + + +static void NVidiaJetsonHashtableFunctor_ResetGpuMem(ATTR_UNUSED ht_key_t key, void* val, ATTR_UNUSED void* userData) { + LinuxProcess *lp = val; + lp->gpu_mem = 0; +} + + +Hashtable *NvidiaJetson_GetPidMatchList(void) { + return GPU_PID_MATCH_LIST; +} + + +void NvidiaJetson_LoadGpuProcessTable(Hashtable *pidHash) { + static int IsRootUser = -1; + + /* needs root privileges */ + if (!IsRootUser) return; + + /* first time function is called */ + if (IsRootUser == -1) { +#ifdef DEBUG_ME + IsRootUser = 1; +#else + IsRootUser = geteuid() == 0; +#endif + if (!IsRootUser) return; + + GPU_PID_MATCH_LIST = Hashtable_new(MAX_GPU_PROCESS_COUNT, false); + } + +#ifdef DEBUG_ME + FILE *input = fopen("/home/philimonov/tmp/test.file", "r"); +#else + FILE *input = fopen("/sys/kernel/debug/nvmap/iovmm/clients", "r"); +#endif + if (input == NULL) return; + + /* reset all knowledge about GPU allocations */ + Hashtable_foreach(pidHash, NVidiaJetsonHashtableFunctor_ResetGpuMem, NULL); + Hashtable_clear(GPU_PID_MATCH_LIST); + + /* construct new knowledge regarding GPU allocations */ + static const char sentinel = -128; + static const size_t line_sz = 256; + + char line[line_sz], *last = &line[sizeof(line)-1]; *last = sentinel; + char *saveptr, *endptr; + while(fgets(line, sizeof(line), input)) { + if(String_startsWith(line, "CLIENT")) continue; // skip header + if(String_startsWith(line, "total")) break; //final line, skip for now + if(*last == '\0') { + /* overflow */ + *last = sentinel; + continue; + } + /* char *user = */ strtok_r(line, " \n", &saveptr); + /* char *proc = */ strtok_r(NULL, " \n", &saveptr); + char *pidPtr = strtok_r(NULL, " \n", &saveptr); + unsigned int pid = strtoul(pidPtr, NULL, 10); + + char *memPtr = strtok_r(NULL, " \n", &saveptr); + uint64_t gpumem = strtoull(memPtr, &endptr, 10); + /* memory allocation howed in kylobytes*/ + if (endptr && *endptr != 'K') { + switch (*endptr) { + case 'M': gpumem *= 1024; break; + case 'G': gpumem *= 1024*1024; break; + default: gpumem = 0; break; + } + } + + LinuxProcess *lp = Hashtable_get(pidHash, pid); + if (lp) { + lp->gpu_mem = gpumem; + } + + Hashtable_put(GPU_PID_MATCH_LIST, pid, (void*)1); + } + fclose(input); +} + + +static inline bool IsJetsonOrinGPU(const char *name) { + return strstr(name, "gpu"); +} + + +static inline bool IsJetsonXavierGPU(const char *name) { + return strstr(name, "gv11"); +} + + +static void NvidiaJetson_FindGPUDevice(void) { + const struct dirent* entry; + + DIR* dir = opendir("/sys/class/devfreq"); + if (!dir) + return; + + while ((entry = readdir(dir)) != NULL) { + const char* name = entry->d_name; + + if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) + continue; + + if (IsJetsonOrinGPU(name) || IsJetsonXavierGPU(name)) { + xSnprintf(GPU_FREQ_SENSOR_PATH, sizeof(GPU_FREQ_SENSOR_PATH), "/sys/class/devfreq/%s/cur_freq", name); + xSnprintf(GPU_LOAD_SENSOR_PATH, sizeof(GPU_LOAD_SENSOR_PATH), "/sys/class/devfreq/%s/device/load", name); + break; + } + } + closedir(dir); +} + + +static void NvidiaJetson_GoThroughThermalZones(void) { + char path[64], content[4]; + const struct dirent* entry; + + DIR* dir = opendir("/sys/devices/virtual/thermal"); + if (!dir) + return; + + while ((entry = readdir(dir)) != NULL && (CPU_TEMP_SENSOR_PATH[0] == 0 || GPU_TEMP_SENSOR_PATH[0] == 0)) { + const char* name = entry->d_name; + ssize_t ret; + + if (String_eq(name, ".") || String_eq(name, "..")) + continue; + + if (!String_startsWith(name, "thermal_zone")) + continue; + + xSnprintf(path, sizeof(path), "/sys/devices/virtual/thermal/%s/type", name); + ret = xReadfile(path, content, sizeof(content)); + if (ret <= 0) + continue; + + content[0] = tolower(content[0]); + content[1] = tolower(content[1]); + content[2] = tolower(content[2]); + content[3] = tolower(content[3]); + + if (CPU_TEMP_SENSOR_PATH[0] == 0 && String_startsWith(content, "cpu")) { + xSnprintf(CPU_TEMP_SENSOR_PATH, sizeof(CPU_TEMP_SENSOR_PATH), "/sys/devices/virtual/thermal/%s/temp", name); + } + if (GPU_TEMP_SENSOR_PATH[0] == 0 && String_startsWith(content, "gpu")) { + xSnprintf(GPU_TEMP_SENSOR_PATH, sizeof(GPU_TEMP_SENSOR_PATH), "/sys/devices/virtual/thermal/%s/temp", name); + } + } + closedir(dir); +} + + +void NvidiaJetson_FindSensors(void) { + NvidiaJetson_GoThroughThermalZones(); + NvidiaJetson_FindGPUDevice(); +} + + +void NvidiaJetson_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs) { + char buffer[22]; + double temp = xReadNumberFromFile(CPU_TEMP_SENSOR_PATH, buffer, sizeof(buffer)) / 1000.0; + for (unsigned int i=0; i<=existingCPUs; ++i) + cpus[i].temperature = temp; +} + + +enum JetsonValues { + JETSON_GPU_LOAD = 0, + JETSON_GPU_TEMP = 1, + JETSON_GPU_FREQ = 2, + JETSON_GPU_TOTAL_COUNT, +}; + + +static void JetsonGPUMeter_updateValues(Meter* this) { + char buffer[22]; + this->values[JETSON_GPU_LOAD] = xReadNumberFromFile(GPU_LOAD_SENSOR_PATH, buffer, sizeof(buffer)); + this->curItems = 1; /* only show bar for JETSON_GPU_LOAD */ + + this->values[JETSON_GPU_TEMP] = xReadNumberFromFile(GPU_TEMP_SENSOR_PATH, buffer, sizeof(buffer)) / 1000.0; + this->values[JETSON_GPU_FREQ] = xReadNumberFromFile(GPU_FREQ_SENSOR_PATH, buffer, sizeof(buffer)) / 1000000.0; + double percent = this->values[0] / 10.0; + + char c = 'C'; + double gpuTemperature = this->values[JETSON_GPU_TEMP]; + if (this->host->settings->degreeFahrenheit) { + gpuTemperature = ConvCelsiusToFahrenheit(gpuTemperature); + c = 'F'; + } + + unsigned int gpuFrequency = this->values[JETSON_GPU_FREQ]; + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1f%% %3uMHz %.1f%s%c", + percent, gpuFrequency, gpuTemperature, CRT_degreeSign, c + ); +} + +static void JetsonGPUMeter_display(const Object* cast, RichString* out) { + char buffer[32]; + const Meter* this = (const Meter*)cast; + + RichString_writeAscii(out, CRT_colors[METER_TEXT], ":"); + xSnprintf(buffer, sizeof(buffer), "%.1f", this->values[JETSON_GPU_LOAD]); + RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer); + + RichString_appendAscii(out, CRT_colors[METER_TEXT], " freq:"); + xSnprintf(buffer, sizeof(buffer), "%3uMHz", (unsigned)this->values[JETSON_GPU_FREQ]); + RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer); + + RichString_appendAscii(out, CRT_colors[METER_TEXT], " temp:"); + xSnprintf(buffer, sizeof(buffer), "%.1f%sC", this->values[JETSON_GPU_TEMP], CRT_degreeSign); + RichString_appendWide(out, CRT_colors[METER_VALUE], buffer); +} + +static const int JetsonGPUMeter_attributes[] = { + DEFAULT_COLOR +}; + +const MeterClass JetsonGPUMeter_class = { + .super = { + .extends = Class(Meter), + .delete = Meter_delete, + .display = JetsonGPUMeter_display, + }, + .updateValues = JetsonGPUMeter_updateValues, + .defaultMode = BAR_METERMODE, + .supportedModes = METERMODE_DEFAULT_SUPPORTED, + .maxItems = JETSON_GPU_TOTAL_COUNT, + .total = 1000.0, + .attributes = JetsonGPUMeter_attributes, + .name = "jetson_gpu", + .uiName = "Jetson GPU", + .caption = "GPU" +}; +#endif diff --git a/linux/NvidiaJetson.h b/linux/NvidiaJetson.h new file mode 100644 index 000000000..804e2a78a --- /dev/null +++ b/linux/NvidiaJetson.h @@ -0,0 +1,19 @@ +#ifndef HEADER_NVIDIA_JETSON +#define HEADER_NVIDIA_JETSON + +#include "config.h" +#ifdef NVIDIA_JETSON +#include "Hashtable.h" +#include "Meter.h" +#include "linux/LinuxMachine.h" + +void NvidiaJetson_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs); +void NvidiaJetson_FindSensors(void); + +void NvidiaJetson_LoadGpuProcessTable(Hashtable *pidHash); +Hashtable *NvidiaJetson_GetPidMatchList(void); + +extern const MeterClass JetsonGPUMeter_class; +#endif + +#endif diff --git a/linux/Platform.c b/linux/Platform.c index ddaf1324d..703a958e5 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -56,6 +56,7 @@ in the source distribution for its full text. #include "linux/IOPriorityPanel.h" #include "linux/LinuxMachine.h" #include "linux/LinuxProcess.h" +#include "linux/NvidiaJetson.h" #include "linux/SELinuxMeter.h" #include "linux/SystemdMeter.h" #include "linux/ZramMeter.h" @@ -254,6 +255,9 @@ const MeterClass* const Platform_meterTypes[] = { &SystemdUserMeter_class, &FileDescriptorMeter_class, &GPUMeter_class, +#ifdef NVIDIA_JETSON + &JetsonGPUMeter_class, +#endif NULL }; @@ -360,7 +364,7 @@ double Platform_setCPUValues(Meter* this, unsigned int cpu) { v[CPU_METER_FREQUENCY] = cpuData->frequency; -#ifdef HAVE_SENSORS_SENSORS_H +#ifdef BUILD_WITH_CPU_TEMP v[CPU_METER_TEMPERATURE] = cpuData->temperature; #else v[CPU_METER_TEMPERATURE] = NAN; diff --git a/linux/ProcessField.h b/linux/ProcessField.h index 47c4199fe..7655dbd4b 100644 --- a/linux/ProcessField.h +++ b/linux/ProcessField.h @@ -51,6 +51,7 @@ in the source distribution for its full text. GPU_TIME = 132, \ GPU_PERCENT = 133, \ ISCONTAINER = 134, \ + GPU_MEM = 135, \ // End of list