diff -Nru nvtop-1.1.0/cmake/modules/FindNVML.cmake nvtop-1.2.2/cmake/modules/FindNVML.cmake --- nvtop-1.1.0/cmake/modules/FindNVML.cmake 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/cmake/modules/FindNVML.cmake 1970-01-01 00:00:00.000000000 +0000 @@ -1,57 +0,0 @@ -# Version 1.2 -# Public Domain -# Written by Maxime SCHMITT - -#/////////////////////////////////////////////////////////////////////////////# -# # -# Search for Nvidia nvml library on the system # -# Call with find_package(NVML) # -# The module defines: # -# - NVML_FOUND - If NVML was found # -# - NVML_INCLUDE_DIRS - the NVML include directories # -# - NVML_LIBRARIES - the NVML library directories # -# - NVML_API_VERSION - the NVML api version # -# # -#/////////////////////////////////////////////////////////////////////////////# - -if (NVML_INCLUDE_DIRS AND NVML_LIBRARIES) - set(NVML_FIND_QUIETLY TRUE) -endif() - -# Headers -file(GLOB nvml_header_path_hint /usr/include/nvidia*/include /usr/local/cuda*/include /opt/cuda*/include /usr/lib/*linux-gnu /usr/local/cuda*/targets/*/include) -find_path(NVML_INCLUDE_DIRS NAMES nvml.h - PATHS ${nvml_header_path_hint} ${PROJECT_BINARY_DIR}/include) - -# library -if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # 64bit - file(GLOB nvml_lib_path_hint /usr/lib64/nvidia*/ /usr/lib/nvidia*/ /usr/local/cuda*/targets/*/lib/stubs/) -else() # assume 32bit - file(GLOB nvml_lib_path_hint /usr/lib32/nvidia*/ /usr/lib/nvidia*/ /usr/local/cuda*/targets/*/lib/stubs/) -endif() -find_library(NVML_LIBRARIES NAMES nvidia-ml libnvidia-ml.so.1 - PATHS ${nvml_lib_path_hint}) - -# Version -set(filename "${NVML_INCLUDE_DIRS}/nvml.h") -if (EXISTS ${filename}) - file(READ "${filename}" nvml_header) - set(nvml_api_version_match "NVML_API_VERSION") - - string(REGEX REPLACE ".*#[ \t]*define[ \t]*${nvml_api_version_match}[ \t]*([0-9]+).*" - "\\1" nvml_api_version "${nvml_header}") - - if (nvml_api_version STREQUAL nvml_header AND NOT quiet) - message(AUTHOR_WARNING "Unable to find nvml api version") - else() - set(NVML_API_VERSION "${nvml_api_version}") - endif() -endif(EXISTS ${filename}) - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(NVML - FOUND_VAR NVML_FOUND - REQUIRED_VARS NVML_INCLUDE_DIRS NVML_LIBRARIES - VERSION_VAR NVML_API_VERSION) - -mark_as_advanced(NVML_INCLUDE_DIRS NVML_LIBRARIES NVML_API_VERSION) diff -Nru nvtop-1.1.0/CMakeLists.txt nvtop-1.2.2/CMakeLists.txt --- nvtop-1.1.0/CMakeLists.txt 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/CMakeLists.txt 2021-07-24 13:07:26.000000000 +0000 @@ -4,7 +4,7 @@ # PROJECT # #///////////////////////////////////////////////////////////////////# -project(nvtop VERSION 1.1.0 +project(nvtop VERSION 1.2.2 LANGUAGES C) set(default_build_type "Release") @@ -22,20 +22,8 @@ # DEPENDENCIES # #///////////////////////////////////////////////////////////////////# -if(NVML_RETRIEVE_HEADER_ONLINE) - file(DOWNLOAD "https://raw.githubusercontent.com/NVIDIA/nvidia-settings/master/src/nvml.h" ${PROJECT_BINARY_DIR}/include/nvml.h) -endif() - list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_CURRENT_SOURCE_DIR}/cmake) -find_package(NVML REQUIRED) - -add_library(nvml INTERFACE IMPORTED) -set_property(TARGET nvml PROPERTY - INTERFACE_INCLUDE_DIRECTORIES ${NVML_INCLUDE_DIRS}) -set_property(TARGET nvml PROPERTY - INTERFACE_LINK_LIBRARIES ${NVML_LIBRARIES}) - set(CURSES_NEED_NCURSES TRUE) # Try to find ncurses with unicode support first set(CURSES_NEED_WIDE TRUE) diff -Nru nvtop-1.1.0/debian/changelog nvtop-1.2.2/debian/changelog --- nvtop-1.1.0/debian/changelog 2021-01-07 15:10:44.000000000 +0000 +++ nvtop-1.2.2/debian/changelog 2021-09-15 17:50:02.000000000 +0000 @@ -1,3 +1,12 @@ +nvtop (1.2.2-1) unstable; urgency=medium + + * Update to upstream release 1.2.2. + * Update the debian metadata fields to the latest version. + * Remove libnvidia-ml from the build dependencies. + * Bump Standards-Version to 4.6.0, no changes needed. + + -- Maxime Schmitt Wed, 15 Sep 2021 19:50:02 +0200 + nvtop (1.1.0-1) unstable; urgency=medium [ Maxime Schmitt ] diff -Nru nvtop-1.1.0/debian/control nvtop-1.2.2/debian/control --- nvtop-1.1.0/debian/control 2021-01-07 15:10:44.000000000 +0000 +++ nvtop-1.2.2/debian/control 2021-09-15 17:50:02.000000000 +0000 @@ -9,9 +9,8 @@ debhelper-compat (= 13), cmake (>= 3.1), libncurses-dev, - libnvidia-ml-dev, Rules-Requires-Root: no -Standards-Version: 4.5.1 +Standards-Version: 4.6.0 Homepage: https://github.com/Syllo/nvtop Vcs-Git: https://salsa.debian.org/nvidia-team/nvtop.git Vcs-Browser: https://salsa.debian.org/nvidia-team/nvtop diff -Nru nvtop-1.1.0/debian/upstream/metadata nvtop-1.2.2/debian/upstream/metadata --- nvtop-1.1.0/debian/upstream/metadata 2021-01-07 15:10:44.000000000 +0000 +++ nvtop-1.2.2/debian/upstream/metadata 2021-09-15 17:50:02.000000000 +0000 @@ -1,5 +1,5 @@ Name: Nvtop -Homepage: https://github.com/Syllo/nvtop +Repository-Browse: https://github.com/Syllo/nvtop Repository: https://github.com/Syllo/nvtop.git Bug-Database: https://github.com/Syllo/nvtop/issues Contact: Maxime Schmitt diff -Nru nvtop-1.1.0/Dockerfile nvtop-1.2.2/Dockerfile --- nvtop-1.1.0/Dockerfile 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/Dockerfile 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,17 @@ +FROM nvidia/driver:418.87.01-ubuntu18.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update +RUN apt-get install -yq build-essential cmake libncurses5-dev libncursesw5-dev + +COPY . /nvtop +WORKDIR /nvtop +RUN mkdir -p /nvtop/build && \ + cd /nvtop/build && \ + cmake .. && \ + make && \ + make install + +ENV LANG C.UTF-8 +ENTRYPOINT [ "/usr/local/bin/nvtop" ] diff -Nru nvtop-1.1.0/.dockerignore nvtop-1.2.2/.dockerignore --- nvtop-1.1.0/.dockerignore 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/.dockerignore 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,4 @@ +*.o +*ctags +build/ +cmake-build*/ diff -Nru nvtop-1.1.0/.gitignore nvtop-1.2.2/.gitignore --- nvtop-1.1.0/.gitignore 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/.gitignore 2021-07-24 13:07:26.000000000 +0000 @@ -1,3 +1,4 @@ *.o *ctags -build/* +build/ +cmake-build*/ diff -Nru nvtop-1.1.0/include/ini.h nvtop-1.2.2/include/ini.h --- nvtop-1.1.0/include/ini.h 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/include/ini.h 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,157 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2020, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef INI_H +#define INI_H + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Same as ini_parse(), but takes a zero-terminated string with the INI data +instead of a file. Useful for parsing INI data from a network socket or +already in memory. */ +int ini_parse_string(const char* string, ini_handler handler, void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See https://github.com/benhoyt/inih/issues/21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Chars that begin a start-of-line comment. Per Python configparser, allow + both ; and # comments at the start of a line by default. */ +#ifndef INI_START_COMMENT_PREFIXES +#define INI_START_COMMENT_PREFIXES ";#" +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file (stack or heap). Note that + this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +/* Nonzero to allow heap line buffer to grow via realloc(), zero for a + fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is + zero. */ +#ifndef INI_ALLOW_REALLOC +#define INI_ALLOW_REALLOC 0 +#endif + +/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK + is zero. */ +#ifndef INI_INITIAL_ALLOC +#define INI_INITIAL_ALLOC 200 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Nonzero to call the handler at the start of each new section (with + name and value NULL). Default is to only call the handler on + each name=value pair. */ +#ifndef INI_CALL_HANDLER_ON_NEW_SECTION +#define INI_CALL_HANDLER_ON_NEW_SECTION 0 +#endif + +/* Nonzero to allow a name without a value (no '=' or ':' on the line) and + call the handler with value NULL in this case. Default is to treat + no-value lines as an error. */ +#ifndef INI_ALLOW_NO_VALUE +#define INI_ALLOW_NO_VALUE 0 +#endif + +/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory + allocation functions (INI_USE_STACK must also be 0). These functions must + have the same signatures as malloc/free/realloc and behave in a similar + way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ +#ifndef INI_CUSTOM_ALLOCATOR +#define INI_CUSTOM_ALLOCATOR 0 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* INI_H */ diff -Nru nvtop-1.1.0/include/nvtop/extract_gpuinfo_common.h nvtop-1.2.2/include/nvtop/extract_gpuinfo_common.h --- nvtop-1.1.0/include/nvtop/extract_gpuinfo_common.h 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/extract_gpuinfo_common.h 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,136 @@ +/* + * + * Copyright (C) 2021 Maxime Schmitt + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#ifndef EXTRACT_GPUINFO_COMMON_H__ +#define EXTRACT_GPUINFO_COMMON_H__ + +#include +#include +#include + +#define IS_VALID(x, y) ((y)[(x) / CHAR_BIT] & (1 << ((x) % CHAR_BIT))) +#define SET_VALID(x, y) ((y)[(x) / CHAR_BIT] |= (1 << ((x) % CHAR_BIT))) +#define RESET_VALID(x, y) ((y)[(x) / CHAR_BIT] &= ~(1 << ((x) % CHAR_BIT))) + +enum gpuinfo_static_info_valid { + gpuinfo_device_name_valid = 0, + gpuinfo_max_pcie_gen_valid, + gpuinfo_max_link_width_valid, + gpuinfo_temperature_shutdown_valid, + gpuinfo_temperature_slowdown_valid, + gpuinfo_static_info_count, +}; + +#define MAX_DEVICE_NAME 128 + +typedef struct gpuinfo_static_info_struct { + char device_name[MAX_DEVICE_NAME]; + unsigned max_pcie_gen; + unsigned max_pcie_link_width; + unsigned temperature_shutdown_threshold; + unsigned temperature_slowdown_threshold; + unsigned char valid[gpuinfo_static_info_count / CHAR_BIT + 1]; +} gpuinfo_static_info; + +enum gpuinfo_dynamic_info_valid { + gpuinfo_curr_gpu_clock_speed_valid = 0, + gpuinfo_max_gpu_clock_speed_valid, + gpuinfo_curr_mem_clock_speed_valid, + gpuinfo_max_mem_clock_speed_valid, + gpuinfo_gpu_util_rate_valid, + gpuinfo_mem_util_rate_valid, + gpuinfo_encoder_rate_valid, + gpuinfo_decoder_rate_valid, + gpuinfo_total_memory_valid, + gpuinfo_free_memory_valid, + gpuinfo_used_memory_valid, + gpuinfo_pcie_link_gen_valid, + gpuinfo_pcie_link_width_valid, + gpuinfo_pcie_rx_valid, + gpuinfo_pcie_tx_valid, + gpuinfo_fan_speed_valid, + gpuinfo_gpu_temp_valid, + gpuinfo_power_draw_valid, + gpuinfo_power_draw_max_valid, + gpuinfo_dynamic_info_count, +}; + +typedef struct gpuinfo_dynamic_info_struct { + unsigned int gpu_clock_speed; // Device clock speed in MHz + unsigned int gpu_clock_speed_max; // Maximum clock speed in MHz + unsigned int mem_clock_speed; // Device clock speed in MHz + unsigned int mem_clock_speed_max; // Maximum clock speed in MHz + unsigned int gpu_util_rate; // GPU utilization rate in % + unsigned int mem_util_rate; // MEM utilization rate in % + unsigned int encoder_rate; // Encoder utilization rate in % + unsigned int decoder_rate; // Decoder utilization rate in % + unsigned long long total_memory; // Total memory (bytes) + unsigned long long free_memory; // Unallocated memory (bytes) + unsigned long long used_memory; // Allocated memory (bytes) + unsigned int curr_pcie_link_gen; // PCIe link generation used + unsigned int curr_pcie_link_width; // PCIe line width used + unsigned int pcie_rx; // PCIe throughput in KB/s + unsigned int pcie_tx; // PCIe throughput in KB/s + unsigned int fan_speed; // Fan speed percentage + unsigned int gpu_temp; // GPU temperature °celsius + unsigned int power_draw; // Power usage in milliwatts + unsigned int power_draw_max; // Max power usage in milliwatts + unsigned char valid[gpuinfo_dynamic_info_count / CHAR_BIT + 1]; +} gpuinfo_dynamic_info; + +enum gpu_process_type { + gpu_process_graphical, + gpu_process_compute, + gpu_process_type_count, +}; + +enum gpuinfo_process_info_valid { + gpuinfo_process_cmdline_valid, + gpuinfo_process_user_name_valid, + gpuinfo_process_gpu_usage_valid, + gpuinfo_process_gpu_encoder_valid, + gpuinfo_process_gpu_decoder_valid, + gpuinfo_process_gpu_memory_usage_valid, + gpuinfo_process_gpu_memory_percentage_valid, + gpuinfo_process_cpu_usage_valid, + gpuinfo_process_cpu_memory_virt_valid, + gpuinfo_process_cpu_memory_res_valid, + gpuinfo_process_info_count +}; + +typedef struct gpu_process_struct { + enum gpu_process_type type; + pid_t pid; // Process ID + char *cmdline; // Process User Name + char *user_name; // Process User Name + unsigned gpu_usage; // Percentage of GPU used by the process + unsigned encode_usage; // Percentage of GPU encoder used by the process + unsigned decode_usage; // Percentage of GPU decoder used by the process + unsigned long long gpu_memory_usage; // Memory used by the process + unsigned gpu_memory_percentage; // Percentage of the total device memory + // consumed by the process + unsigned cpu_usage; + unsigned long cpu_memory_virt; + unsigned long cpu_memory_res; + unsigned char valid[gpuinfo_process_info_count / CHAR_BIT + 1]; +} gpu_process; + +#endif // EXTRACT_GPUINFO_COMMON_H__ diff -Nru nvtop-1.1.0/include/nvtop/extract_gpuinfo.h nvtop-1.2.2/include/nvtop/extract_gpuinfo.h --- nvtop-1.1.0/include/nvtop/extract_gpuinfo.h 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/extract_gpuinfo.h 2021-07-24 13:07:26.000000000 +0000 @@ -1,6 +1,6 @@ /* * - * Copyright (C) 2017 Maxime Schmitt + * Copyright (C) 2017-2021 Maxime Schmitt * * This file is part of Nvtop. * @@ -22,104 +22,42 @@ #ifndef EXTRACT_GPUINFO_H_ #define EXTRACT_GPUINFO_H_ -#include -#include -#include -#include -#include - -#define IS_VALID(x, y) ((y)[(x) / CHAR_BIT] & (1 << ((x) % CHAR_BIT))) -#define SET_VALID(x, y) ((y)[(x) / CHAR_BIT] |= (1 << ((x) % CHAR_BIT))) -#define RESET_VALID(x, y) ((y)[(x) / CHAR_BIT] &= ~(1 << ((x) % CHAR_BIT))) - -struct gpu_process { - intmax_t pid; // Process ID - char *process_name; // Process Name - char *user_name; // Process User Name - unsigned long long used_memory; // Memory used by process - size_t cpu_memory_virt; - size_t cpu_memory_res; - double cpu_usage; -}; +#include "nvtop/extract_gpuinfo_common.h" +#include "nvtop/extract_gpuinfo_nvidia.h" -enum dev_info_valid { - device_name_valid = 0, - gpu_clock_speed_valid, - gpu_clock_speed_max_valid, - mem_clock_speed_valid, - mem_clock_speed_max_valid, - gpu_util_rate_valid, - mem_util_rate_valid, - encoder_rate_valid, - encoder_sampling_valid, - decoder_rate_valid, - decoder_sampling_valid, - free_memory_valid, - total_memory_valid, - used_memory_valid, - cur_pcie_link_gen_valid, - max_pcie_link_gen_valid, - cur_pcie_link_width_valid, - max_pcie_link_width_valid, - pcie_rx_valid, - pcie_tx_valid, - fan_speed_valid, - gpu_temp_valid, - gpu_temp_slowdown_valid, - gpu_temp_shutdown_valid, - power_draw_valid, - power_draw_max_valid, - valid_max_val, -}; +#include -struct device_info { - nvmlDevice_t device_handle; // Used to query device - char device_name[NVML_DEVICE_NAME_BUFFER_SIZE]; // Device Name - unsigned int gpu_clock_speed; // Device clock speed in MHz - unsigned int gpu_clock_speed_max; // Maximum clock speed in MHz - unsigned int mem_clock_speed; // Device clock speed in MHz - unsigned int mem_clock_speed_max; // Maximum clock speed in MHz - unsigned int gpu_util_rate; // GPU utilization rate in % - unsigned int mem_util_rate; // MEM utilization rate in % - unsigned int encoder_rate; // Encoder utilization rate in % - unsigned int encoder_sampling; // Encoder sampling period in micro sec - unsigned int decoder_rate; // Decoder utilization rate in % - unsigned int decoder_sampling; // Decoder sampling period in micro sec - unsigned long long free_memory; // Unallocated memory (bytes) - unsigned long long total_memory; // Total memory (bytes) - unsigned long long used_memory; // Allocated memory (bytes) - unsigned int cur_pcie_link_gen; // PCIe link generation used - unsigned int max_pcie_link_gen; // PCIe link generation max - unsigned int cur_pcie_link_width; // PCIe line width used - unsigned int max_pcie_link_width; // PCIe line width max - unsigned int pcie_rx; // PCIe throughput in KB/s - unsigned int pcie_tx; // PCIe throughput in KB/s - unsigned int fan_speed; // Fan speed percentage - unsigned int gpu_temp; // GPU temperature °c - unsigned int gpu_temp_slowdown; // GPU temperature °c - unsigned int gpu_temp_shutdown; // GPU temperature °c - unsigned int power_draw; // Power usage in milliwatts - unsigned int power_draw_max; // Max power usage in milliwatts - unsigned int size_proc_buffers; // Number of Compute processes (Cuda) - unsigned int num_compute_procs; // Number of Compute processes (Cuda) - unsigned int num_graphical_procs; // Number of Graphical processes - struct gpu_process *graphic_procs; // Graphical process info - struct gpu_process *compute_procs; // Compute processes info - nvmlProcessInfo_t *process_infos; // Internal use - unsigned char valid[valid_max_val / CHAR_BIT + 1]; // Validity bits +enum gpuinfo_gputype { + gpuinfo_type_nvidia_proprietary, }; -bool init_gpu_info_extraction(void); - -bool shutdown_gpu_info_extraction(void); +typedef struct gpu_info_struct { + enum gpuinfo_gputype gpu_type; + union { + gpuinfo_nvidia_device_handle nvidia_gpuhandle; + }; + gpuinfo_static_info static_info; + gpuinfo_dynamic_info dynamic_info; + unsigned processes_count; + gpu_process *processes; + union { + gpuinfo_nvidia_internal_data nvidia_internal; + }; +} gpu_info; + +bool gpuinfo_init_info_extraction(uint64_t mask_nvidia, unsigned *devices_count, gpu_info **devices); + +bool gpuinfo_shutdown_info_extraction(unsigned device_count, + gpu_info *devices); + +bool gpuinfo_populate_static_infos(unsigned device_count, gpu_info *devices); -unsigned int initialize_device_info(struct device_info **dev_info, - size_t gpu_mask); +bool gpuinfo_refresh_dynamic_info(unsigned device_count, gpu_info *devices); -void update_device_infos(unsigned int num_devs, struct device_info *dev_info); +bool gpuinfo_refresh_processes(unsigned device_count, gpu_info *devices); -void clean_device_info(unsigned int num_devs, struct device_info *dev_info); +void gpuinfo_clean(unsigned device_count, gpu_info *devices); -void clean_pid_cache(void); +void gpuinfo_clear_cache(void); #endif // EXTRACT_GPUINFO_H_ diff -Nru nvtop-1.1.0/include/nvtop/extract_gpuinfo_nvidia.h nvtop-1.2.2/include/nvtop/extract_gpuinfo_nvidia.h --- nvtop-1.1.0/include/nvtop/extract_gpuinfo_nvidia.h 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/extract_gpuinfo_nvidia.h 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,56 @@ +/* + * + * Copyright (C) 2021 Maxime Schmitt + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#ifndef EXTRACT_GPUINFO_NVIDIA_H_ +#define EXTRACT_GPUINFO_NVIDIA_H_ + +#include "nvtop/extract_gpuinfo_common.h" + +#include +#include + +typedef struct nvmlDevice *nvmlDevice_t, *gpuinfo_nvidia_device_handle; + +typedef struct gpuinfo_nvidia_internal_data { + unsigned long long last_utilization_timestamp; +} gpuinfo_nvidia_internal_data; + +bool gpuinfo_nvidia_init(void); + +void gpuinfo_nvidia_shutdown(void); + +const char *gpuinfo_nvidia_last_error_string(void); + +bool gpuinfo_nvidia_get_device_handles( + gpuinfo_nvidia_device_handle **handle_array_ptr, unsigned *count, + uint64_t mask); + +void gpuinfo_nvidia_populate_static_info(gpuinfo_nvidia_device_handle device, + gpuinfo_static_info *static_info); + +void gpuinfo_nvidia_refresh_dynamic_info(gpuinfo_nvidia_device_handle device, + gpuinfo_dynamic_info *dynamic_info); + +void gpuinfo_nvidia_get_running_processes( + gpuinfo_nvidia_device_handle device, gpuinfo_nvidia_internal_data *internal, + unsigned *num_processes_recovered, gpu_process **processes_info); + +#endif // EXTRACT_GPUINFO_NVIDIA_H_ diff -Nru nvtop-1.1.0/include/nvtop/get_process_info.h nvtop-1.2.2/include/nvtop/get_process_info.h --- nvtop-1.1.0/include/nvtop/get_process_info.h 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/get_process_info.h 2021-07-24 13:07:26.000000000 +0000 @@ -1,6 +1,6 @@ /* * - * Copyright (C) 2017 Maxime Schmitt + * Copyright (C) 2017-2021 Maxime Schmitt * * This file is part of Nvtop. * @@ -24,6 +24,7 @@ #include #include +#include #include "nvtop/time.h" @@ -32,7 +33,7 @@ double total_kernel_time; // Seconds size_t virtual_memory; // Bytes size_t resident_memory; // Bytes - nvtop_time time; + nvtop_time timestamp; }; void get_username_from_pid(pid_t pid, char **buffer); diff -Nru nvtop-1.1.0/include/nvtop/interface_common.h nvtop-1.2.2/include/nvtop/interface_common.h --- nvtop-1.1.0/include/nvtop/interface_common.h 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/interface_common.h 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,57 @@ +/* + * + * Copyright (C) 2021 Maxime Schmitt + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#ifndef INTERFACE_COMMON_H__ +#define INTERFACE_COMMON_H__ + +enum plot_information { + plot_gpu_rate = 0, + plot_gpu_mem_rate, + plot_encoder_rate, + plot_decoder_rate, + plot_gpu_temperature, + plot_gpu_power_draw_rate, + plot_fan_speed, + plot_gpu_clock_rate, + plot_gpu_mem_clock_rate, + plot_information_count +}; + +typedef int plot_info_to_draw; + +enum process_field { + process_pid = 0, + process_user, + process_gpu_id, + process_type, + process_gpu_rate, + process_enc_rate, + process_dec_rate, + process_memory, + process_cpu_usage, + process_cpu_mem_usage, + process_command, + process_field_count, +}; + +typedef int process_field_displayed; + +#endif // INTERFACE_COMMON_H__ diff -Nru nvtop-1.1.0/include/nvtop/interface.h nvtop-1.2.2/include/nvtop/interface.h --- nvtop-1.1.0/include/nvtop/interface.h 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/interface.h 2021-07-24 13:07:26.000000000 +0000 @@ -1,6 +1,6 @@ /* * - * Copyright (C) 2017-2018 Maxime Schmitt + * Copyright (C) 2017-2021 Maxime Schmitt * * This file is part of Nvtop. * @@ -23,27 +23,31 @@ #define INTERFACE_H_ #include "nvtop/extract_gpuinfo.h" +#include "nvtop/interface_common.h" +#include "nvtop/interface_options.h" struct nvtop_interface; -void show_gpu_infos_ascii(unsigned int num_devices, - struct device_info *dev_info); - -struct nvtop_interface * -initialize_curses(unsigned int num_devices, unsigned int biggest_device_name, - bool use_color, bool use_fahrenheit, bool show_per_gpu_plot, - bool plot_old_left_recent_right, - double encode_decode_hide_time, unsigned collect_interval); +struct nvtop_interface *initialize_curses(unsigned num_devices, + unsigned largest_device_name, + nvtop_interface_option options); void clean_ncurses(struct nvtop_interface *interface); -void draw_gpu_info_ncurses(struct device_info *dev_info, +void draw_gpu_info_ncurses(unsigned devices_count, gpu_info *devices, struct nvtop_interface *interface); +void save_current_data_to_ring(unsigned devices_count, gpu_info *devices, + struct nvtop_interface *interface); + void update_window_size_to_terminal_size(struct nvtop_interface *inter); void interface_key(int keyId, struct nvtop_interface *inter); bool is_escape_for_quit(struct nvtop_interface *inter); +bool interface_freeze_processes(struct nvtop_interface *interface); + +int interface_update_interval(const struct nvtop_interface *interface); + #endif // INTERFACE_H_ diff -Nru nvtop-1.1.0/include/nvtop/interface_internal_common.h nvtop-1.2.2/include/nvtop/interface_internal_common.h --- nvtop-1.1.0/include/nvtop/interface_internal_common.h 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/interface_internal_common.h 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,156 @@ +/* + * + * Copyright (C) 2021 Maxime Schmitt + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#ifndef INTERFACE_INTERNAL_COMMON_H__ +#define INTERFACE_INTERNAL_COMMON_H__ + +#include "nvtop/interface_layout_selection.h" +#include "nvtop/interface_options.h" +#include "nvtop/interface_ring_buffer.h" +#include "nvtop/time.h" + +#include + +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +enum nvtop_option_window_state { + nvtop_option_state_hidden, + nvtop_option_state_kill, + nvtop_option_state_sort_by, +}; + +enum interface_color { + cyan_color = 1, + yellow_color, + magenta_color, + green_color, + red_color, + blue_color, +}; + +struct device_window { + WINDOW *name_win; // Name of the GPU + WINDOW *gpu_util_enc_dec; + WINDOW *gpu_util_no_enc_or_dec; + WINDOW *gpu_util_no_enc_and_dec; + WINDOW *mem_util_enc_dec; + WINDOW *mem_util_no_enc_or_dec; + WINDOW *mem_util_no_enc_and_dec; + WINDOW *encode_util; + WINDOW *decode_util; + WINDOW *fan_speed; + WINDOW *temperature; + WINDOW *power_info; + WINDOW *gpu_clock_info; + WINDOW *mem_clock_info; + WINDOW *pcie_info; + bool enc_was_visible; + bool dec_was_visible; + nvtop_time last_decode_seen; + nvtop_time last_encode_seen; +}; + +static const unsigned int option_window_size = 13; +struct option_window { + enum nvtop_option_window_state state; + enum nvtop_option_window_state previous_state; + unsigned int selected_row; + unsigned int offset; + WINDOW *option_win; +}; + +struct process_window { + unsigned offset; + unsigned offset_column; + WINDOW *process_win; + WINDOW *process_with_option_win; + unsigned selected_row; + pid_t selected_pid; + struct option_window option_window; +}; + +struct plot_window { + size_t num_data; + double *data; + WINDOW *win; + WINDOW *plot_window; + unsigned num_devices_to_plot; + unsigned devices_ids[4]; +}; + +enum setup_window_section { + setup_general_selected, + setup_header_selected, + setup_chart_selected, + setup_process_list_selected, + setup_window_selection_count +}; + +struct setup_window { + unsigned indentation_level; + enum setup_window_section selected_section; + bool visible; + WINDOW *clean_space; + WINDOW *setup; + WINDOW *single; + WINDOW *split[2]; + unsigned options_selected[2]; +}; + +// Keep gpu information every 1 second for 10 minutes +struct nvtop_interface { + nvtop_interface_option options; + unsigned devices_count; + struct device_window *devices_win; + struct process_window process; + WINDOW *shortcut_window; + unsigned num_plots; + struct plot_window *plots; + interface_ring_buffer saved_data_ring; + struct setup_window setup_win; +}; + +enum device_field { + device_name = 0, + device_fan_speed, + device_temperature, + device_power, + device_pcie, + device_clock, + device_field_count, +}; + +inline void set_attribute_between(WINDOW *win, int startY, int startX, int endX, + attr_t attr, short pair) { + int rows, cols; + getmaxyx(win, rows, cols); + (void)rows; + if (startX >= cols || endX < 0) + return; + startX = startX < 0 ? 0 : startX; + endX = endX > cols ? cols : endX; + int size = endX - startX; + mvwchgat(win, startY, startX, size, attr, pair, NULL); +} + +#endif // INTERFACE_INTERNAL_COMMON_H__ diff -Nru nvtop-1.1.0/include/nvtop/interface_layout_selection.h nvtop-1.2.2/include/nvtop/interface_layout_selection.h --- nvtop-1.1.0/include/nvtop/interface_layout_selection.h 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/interface_layout_selection.h 2021-07-24 13:07:26.000000000 +0000 @@ -1,28 +1,27 @@ #ifndef INTERFACE_LAYOUT_SELECTION_H__ #define INTERFACE_LAYOUT_SELECTION_H__ -#include - -struct layout_selection; +#include "nvtop/interface_common.h" +#include "nvtop/interface_options.h" -enum plot_type { - plot_gpu_max, - plot_gpu_solo, - plot_gpu_duo, -}; +#include struct window_position { unsigned posX, posY, sizeX, sizeY; }; -char *layout_as_string(struct layout_selection *layout); +// Should be fine +#define MAX_CHARTS 30 void compute_sizes_from_layout( - bool show_graphs, bool show_header, bool show_process, size_t num_devices, - unsigned num_info_per_device, unsigned device_header_rows, + unsigned devices_count, unsigned device_header_rows, unsigned device_header_cols, unsigned rows, unsigned cols, - struct window_position *device_position, - struct window_position *process_position, unsigned *num_plots, - struct window_position **plot_positions, enum plot_type *plot_types); + const plot_info_to_draw to_draw[devices_count], + process_field_displayed process_field_displayed, + struct window_position device_positions[devices_count], unsigned *num_plots, + struct window_position plot_positions[MAX_CHARTS], + unsigned map_device_to_plot[devices_count], + struct window_position *process_position, + struct window_position *setup_position); #endif // INTERFACE_LAYOUT_SELECTION_H__ diff -Nru nvtop-1.1.0/include/nvtop/interface_options.h nvtop-1.2.2/include/nvtop/interface_options.h --- nvtop-1.1.0/include/nvtop/interface_options.h 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/interface_options.h 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,134 @@ +/* + * + * Copyright (C) 2021 Maxime Schmitt + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#ifndef INTERFACE_OPTIONS_H__ +#define INTERFACE_OPTIONS_H__ + +#include "nvtop/interface_common.h" + +#include + +typedef struct nvtop_interface_option_struct { + bool plot_left_to_right; // true to reverse the plot refresh direction + // defines inactivity (0 use rate) before + // hiding it + bool temperature_in_fahrenheit; // Switch from celsius to fahrenheit + // temperature scale + bool use_color; // Name self explanatory + double encode_decode_hiding_timer; // Negative to always display, positive + plot_info_to_draw + *device_information_drawn; // Stores the information to drawn for each + // GPU (see enum plot_draw_information) + char *config_file_location; // Location of the config file + enum process_field + sort_processes_by; // Specify the field used to order the processes + bool sort_descending_order; // Sort in descenging order + int update_interval; // Interval between interface update in milliseconds + process_field_displayed + process_fields_displayed; // Which columns of the + // process list are displayed +} nvtop_interface_option; + +inline bool plot_isset_draw_info(enum plot_information check_info, + plot_info_to_draw to_draw) { + return (to_draw & (1 << check_info)) > 0; +} + +inline unsigned plot_count_draw_info(plot_info_to_draw to_draw) { + unsigned count = 0; + for (enum plot_information i = plot_gpu_rate; i < plot_information_count; + ++i) { + count += plot_isset_draw_info(i, to_draw); + } + return count; +} + +inline plot_info_to_draw plot_add_draw_info(enum plot_information set_info, + plot_info_to_draw to_draw) { + if (plot_count_draw_info(to_draw) < 4) + return to_draw | (1 << set_info); + else + return to_draw; +} + +inline plot_info_to_draw plot_remove_draw_info(enum plot_information reset_info, + plot_info_to_draw to_draw) { + return to_draw & (~(1 << reset_info)); +} + +inline plot_info_to_draw plot_default_draw_info(void) { + return (1 << plot_gpu_rate) | (1 << plot_gpu_mem_rate); +} + +void alloc_interface_options_internals(char *config_file_location, + unsigned num_devices, + nvtop_interface_option *options); + +bool load_interface_options_from_config_file(unsigned num_devices, + nvtop_interface_option *options); + +bool save_interface_options_to_config_file( + unsigned num_devices, const nvtop_interface_option *options); + +inline bool +process_is_field_displayed(enum process_field field, + process_field_displayed fields_displayed) { + return (fields_displayed & (1 << field)) > 0; +} + +inline process_field_displayed +process_remove_field_to_display(enum process_field field, + process_field_displayed fields_displayed) { + return fields_displayed & (~(1 << field)); +} + +inline process_field_displayed +process_add_field_to_display(enum process_field field, + process_field_displayed fields_displayed) { + return fields_displayed | (1 << field); +} + +inline process_field_displayed process_default_displayed_field(void) { + process_field_displayed to_display = 0; + for (enum process_field field = process_pid; field < process_field_count; + ++field) { + to_display = process_add_field_to_display(field, to_display); + } + to_display = process_remove_field_to_display(process_enc_rate, to_display); + to_display = process_remove_field_to_display(process_dec_rate, to_display); + return to_display; +} + +inline unsigned +process_field_displayed_count(process_field_displayed fields_displayed) { + unsigned displayed_count = 0; + for (enum process_field field = process_pid; field < process_field_count; + ++field) { + if (process_is_field_displayed(field, fields_displayed)) + displayed_count++; + } + return displayed_count; +} + +enum process_field +process_default_sort_by_from(process_field_displayed fields_displayed); + +#endif // INTERFACE_OPTIONS_H__ diff -Nru nvtop-1.1.0/include/nvtop/interface_ring_buffer.h nvtop-1.2.2/include/nvtop/interface_ring_buffer.h --- nvtop-1.1.0/include/nvtop/interface_ring_buffer.h 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/interface_ring_buffer.h 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,135 @@ +/* + * + * Copyright (C) 2021 Maxime Schmitt + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#ifndef INTERFACE_RING_BUFFER_H__ +#define INTERFACE_RING_BUFFER_H__ + +#include + +typedef struct interface_ring_buffer_st { + unsigned devices_count; + unsigned per_device_data_saved; + unsigned buffer_size; + void *ring_buffer[2]; +} interface_ring_buffer; + +#define INTERFACE_RING_BUFFER_DATA(ring_buffer_ptr, name) \ + unsigned(*name)[ring_buffer_ptr->per_device_data_saved] \ + [ring_buffer_ptr->buffer_size] = \ + (unsigned(*)[ring_buffer_ptr->per_device_data_saved] \ + [ring_buffer_ptr->buffer_size]) \ + ring_buffer_ptr->ring_buffer[1]; + +#define INTERFACE_RING_BUFFER_INDICES(ring_buffer_ptr, name) \ + unsigned(*name)[ring_buffer_ptr->per_device_data_saved][2] = \ + (unsigned(*)[ring_buffer_ptr->per_device_data_saved][2]) \ + ring_buffer_ptr->ring_buffer[0]; + +void interface_alloc_ring_buffer(unsigned devices_count, + unsigned per_device_data, unsigned buffer_size, + interface_ring_buffer *ring_buffer); + +void interface_free_ring_buffer(interface_ring_buffer *buffer); + +inline unsigned +interface_ring_buffer_data_stored(const interface_ring_buffer *buff, + unsigned device, unsigned which_data) { + INTERFACE_RING_BUFFER_INDICES(buff, indices); + unsigned start = indices[device][which_data][0]; + unsigned end = indices[device][which_data][1]; + unsigned length = end - start; + if (end < start) { // Has wrapped around the buffer + length += buff->buffer_size; + } + return length; +} + +inline unsigned interface_index_in_ring(const interface_ring_buffer *buff, + unsigned device, unsigned which_data, + unsigned index) { + assert(interface_ring_buffer_data_stored(buff, device, which_data) > index); + INTERFACE_RING_BUFFER_INDICES(buff, indices); + unsigned start = indices[device][which_data][0]; + unsigned location = start + index; + if (location >= buff->buffer_size) + location -= buff->buffer_size; + return location; +} + +inline unsigned interface_ring_buffer_get(const interface_ring_buffer *buff, + unsigned device, unsigned which_data, + unsigned index) { + INTERFACE_RING_BUFFER_DATA(buff, data); + unsigned index_in_ring = + interface_index_in_ring(buff, device, which_data, index); + return data[device][which_data][index_in_ring]; +} + +inline void interface_ring_buffer_push(interface_ring_buffer *buff, + unsigned device, unsigned which_data, + unsigned value) { + INTERFACE_RING_BUFFER_INDICES(buff, indices); + INTERFACE_RING_BUFFER_DATA(buff, data); + unsigned start = indices[device][which_data][0]; + unsigned end = indices[device][which_data][1]; + // If ring full, move start index + data[device][which_data][end] = value; + end++; + if (end == buff->buffer_size) + end -= buff->buffer_size; + if (end == start) { + start++; + if (start == buff->buffer_size) + start -= buff->buffer_size; + } + indices[device][which_data][0] = start; + indices[device][which_data][1] = end; +} + +inline void interface_ring_buffer_pop(interface_ring_buffer *buff, + unsigned device, unsigned which_data) { + INTERFACE_RING_BUFFER_INDICES(buff, indices); + unsigned start = indices[device][which_data][0]; + unsigned end = indices[device][which_data][1]; + if (start != end) { + start++; + if (start == buff->buffer_size) + start -= buff->buffer_size; + indices[device][which_data][0] = start; + } +} + +inline void interface_ring_buffer_empty_select(interface_ring_buffer *buff, + unsigned device, + unsigned which_data) { + INTERFACE_RING_BUFFER_INDICES(buff, indices); + indices[device][which_data][0] = 0; + indices[device][which_data][1] = 0; +} + +inline void interface_ring_buffer_empty(interface_ring_buffer *buff, + unsigned device) { + for (unsigned i = 0; i < buff->per_device_data_saved; ++i) { + interface_ring_buffer_empty_select(buff, device, i); + } +} + +#endif // INTERFACE_RING_BUFFER_H__ diff -Nru nvtop-1.1.0/include/nvtop/interface_setup_win.h nvtop-1.2.2/include/nvtop/interface_setup_win.h --- nvtop-1.1.0/include/nvtop/interface_setup_win.h 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/interface_setup_win.h 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,45 @@ +/* + * + * Copyright (C) 2021 Maxime Schmitt + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#ifndef INTERFACE_SETUP_WIN_H__ +#define INTERFACE_SETUP_WIN_H__ + +#include "nvtop/extract_gpuinfo.h" +#include "nvtop/interface_internal_common.h" +#include "nvtop/interface_layout_selection.h" + +void alloc_setup_window(struct window_position *position, + struct setup_window *setup_win); + +void free_setup_window(struct setup_window *setup_win); + +void show_setup_window(struct nvtop_interface *interface); + +void hide_setup_window(struct nvtop_interface *interface); + +void draw_setup_window(unsigned devices_count, gpu_info *devices, + struct nvtop_interface *interface); + +void draw_setup_window_shortcuts(struct nvtop_interface *interface); + +void handle_setup_win_keypress(int keyId, struct nvtop_interface *interface); + +#endif // INTERFACE_SETUP_WIN_H__ diff -Nru nvtop-1.1.0/include/nvtop/plot.h nvtop-1.2.2/include/nvtop/plot.h --- nvtop-1.1.0/include/nvtop/plot.h 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/include/nvtop/plot.h 2021-07-24 13:07:26.000000000 +0000 @@ -23,14 +23,14 @@ #define __PLOT_H_ #include +#include #include -void nvtop_line_plot(WINDOW *win, size_t num_data, const double *data, - double min, double max, unsigned num_plots, - const char *legend[]); +#define PLOT_MAX_LEGEND_SIZE 35 -void nvtop_bar_plot(WINDOW *win, size_t num_data, const double *data, - double min, double max); +void nvtop_line_plot(WINDOW *win, size_t num_data, const double *data, + unsigned num_plots, bool legend_left, + char legend[4][PLOT_MAX_LEGEND_SIZE]); void draw_rectangle(WINDOW *win, unsigned startX, unsigned startY, unsigned sizeX, unsigned sizeY); diff -Nru nvtop-1.1.0/include/uthash.h nvtop-1.2.2/include/uthash.h --- nvtop-1.1.0/include/uthash.h 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/include/uthash.h 2021-07-24 13:07:26.000000000 +0000 @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2018, Troy D. Hanson http://troydhanson.github.com/uthash/ +Copyright (c) 2003-2021, Troy D. Hanson http://troydhanson.github.io/uthash/ All rights reserved. Redistribution and use in source and binary forms, with or without @@ -24,12 +24,22 @@ #ifndef UTHASH_H #define UTHASH_H -#define UTHASH_VERSION 2.0.2 +#define UTHASH_VERSION 2.3.0 #include /* memcmp, memset, strlen */ #include /* ptrdiff_t */ #include /* exit */ +#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT +/* This codepath is provided for backward compatibility, but I plan to remove it. */ +#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT +#else +#include /* uint8_t, uint32_t */ +#endif + /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed @@ -62,23 +72,6 @@ } while (0) #endif -/* a number of the hash function use uint32_t which isn't defined on Pre VS2010 */ -#if defined(_WIN32) -#if defined(_MSC_VER) && _MSC_VER >= 1600 -#include -#elif defined(__WATCOMC__) || defined(__MINGW32__) || defined(__CYGWIN__) -#include -#else -typedef unsigned int uint32_t; -typedef unsigned char uint8_t; -#endif -#elif defined(__GNUC__) && !defined(__VXWORKS__) -#include -#else -typedef unsigned int uint32_t; -typedef unsigned char uint8_t; -#endif - #ifndef uthash_malloc #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #endif @@ -88,13 +81,18 @@ #ifndef uthash_bzero #define uthash_bzero(a,n) memset(a,'\0',n) #endif -#ifndef uthash_memcmp -#define uthash_memcmp(a,b,n) memcmp(a,b,n) -#endif #ifndef uthash_strlen #define uthash_strlen(s) strlen(s) #endif +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + #ifndef uthash_noexpand_fyi #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #endif @@ -136,7 +134,7 @@ /* calculate the element whose hash handle address is hhp */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) /* calculate the hash handle from element address elp */ -#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle *)(((char*)(elp)) + ((tbl)->hho))) +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) #define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ do { \ @@ -150,7 +148,7 @@ #define HASH_VALUE(keyptr,keylen,hashv) \ do { \ - HASH_FCN(keyptr, keylen, hashv); \ + HASH_FUNCTION(keyptr, keylen, hashv); \ } while (0) #define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ @@ -167,9 +165,12 @@ #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ - unsigned _hf_hashv; \ - HASH_VALUE(keyptr, keylen, _hf_hashv); \ - HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ } while (0) #ifdef HASH_BLOOM @@ -397,7 +398,7 @@ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ - (add)->hh.key = (char*) (keyptr); \ + (add)->hh.key = (const void*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ @@ -511,7 +512,8 @@ * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG -#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head,where) \ do { \ struct UT_hash_handle *_thh; \ @@ -578,13 +580,6 @@ #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif -/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ -#ifdef HASH_FUNCTION -#define HASH_FCN HASH_FUNCTION -#else -#define HASH_FCN HASH_JEN -#endif - /* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ #define HASH_BER(key,keylen,hashv) \ do { \ @@ -683,7 +678,8 @@ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ - case 1: _hj_i += _hj_key[0]; \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ } while (0) @@ -731,6 +727,8 @@ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ + break; \ + default: ; \ } \ \ /* Force "avalanching" of final 127 bits */ \ @@ -742,87 +740,6 @@ hashv += hashv >> 6; \ } while (0) -#ifdef HASH_USING_NO_STRICT_ALIASING -/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. - * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. - * MurmurHash uses the faster approach only on CPU's where we know it's safe. - * - * Note the preprocessor built-in defines can be emitted using: - * - * gcc -m64 -dM -E - < /dev/null (on gcc) - * cc -## a.c (where a.c is a simple test file) (Sun Studio) - */ -#if (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86)) -#define MUR_GETBLOCK(p,i) p[i] -#else /* non intel */ -#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 3UL) == 0UL) -#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 3UL) == 1UL) -#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 3UL) == 2UL) -#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 3UL) == 3UL) -#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) -#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) -#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) -#define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) -#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) -#else /* assume little endian non-intel */ -#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) -#define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) -#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) -#endif -#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ - (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ - (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ - MUR_ONE_THREE(p)))) -#endif -#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) -#define MUR_FMIX(_h) \ -do { \ - _h ^= _h >> 16; \ - _h *= 0x85ebca6bu; \ - _h ^= _h >> 13; \ - _h *= 0xc2b2ae35u; \ - _h ^= _h >> 16; \ -} while (0) - -#define HASH_MUR(key,keylen,hashv) \ -do { \ - const uint8_t *_mur_data = (const uint8_t*)(key); \ - const int _mur_nblocks = (int)(keylen) / 4; \ - uint32_t _mur_h1 = 0xf88D5353u; \ - uint32_t _mur_c1 = 0xcc9e2d51u; \ - uint32_t _mur_c2 = 0x1b873593u; \ - uint32_t _mur_k1 = 0; \ - const uint8_t *_mur_tail; \ - const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+(_mur_nblocks*4)); \ - int _mur_i; \ - for (_mur_i = -_mur_nblocks; _mur_i != 0; _mur_i++) { \ - _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ - _mur_k1 *= _mur_c1; \ - _mur_k1 = MUR_ROTL32(_mur_k1,15); \ - _mur_k1 *= _mur_c2; \ - \ - _mur_h1 ^= _mur_k1; \ - _mur_h1 = MUR_ROTL32(_mur_h1,13); \ - _mur_h1 = (_mur_h1*5U) + 0xe6546b64u; \ - } \ - _mur_tail = (const uint8_t*)(_mur_data + (_mur_nblocks*4)); \ - _mur_k1=0; \ - switch ((keylen) & 3U) { \ - case 0: break; \ - case 3: _mur_k1 ^= (uint32_t)_mur_tail[2] << 16; /* FALLTHROUGH */ \ - case 2: _mur_k1 ^= (uint32_t)_mur_tail[1] << 8; /* FALLTHROUGH */ \ - case 1: _mur_k1 ^= (uint32_t)_mur_tail[0]; \ - _mur_k1 *= _mur_c1; \ - _mur_k1 = MUR_ROTL32(_mur_k1,15); \ - _mur_k1 *= _mur_c2; \ - _mur_h1 ^= _mur_k1; \ - } \ - _mur_h1 ^= (uint32_t)(keylen); \ - MUR_FMIX(_mur_h1); \ - hashv = _mur_h1; \ -} while (0) -#endif /* HASH_USING_NO_STRICT_ALIASING */ - /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ do { \ @@ -833,7 +750,7 @@ } \ while ((out) != NULL) { \ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ - if (uthash_memcmp((out)->hh.key, keyptr, keylen_in) == 0) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ break; \ } \ } \ @@ -919,12 +836,12 @@ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ - 2UL * (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ if (!_he_new_buckets) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero(_he_new_buckets, \ - 2UL * (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ (tbl)->ideal_chain_maxlen = \ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ @@ -937,7 +854,9 @@ _he_newbkt = &(_he_new_buckets[_he_bkt]); \ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ (tbl)->nonideal_items++; \ - _he_newbkt->expand_mult = _he_newbkt->count / (tbl)->ideal_chain_maxlen; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ @@ -1070,7 +989,7 @@ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ - _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ @@ -1209,7 +1128,7 @@ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ - void *key; /* ptr to enclosing struct's key */ + const void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; diff -Nru nvtop-1.1.0/manpage/nvtop.in nvtop-1.2.2/manpage/nvtop.in --- nvtop-1.1.0/manpage/nvtop.in 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/manpage/nvtop.in 2021-07-24 13:07:26.000000000 +0000 @@ -1,7 +1,7 @@ .\" Manpage for nvtop .\" Contact maxime.schmitt91@gmail.com -.TH nvtop 1 "January 2019" "Version @nvtop_VERSION_MAJOR@.@nvtop_VERSION_MINOR@.@nvtop_VERSION_PATCH@" "nvtop command" +.TH nvtop 1 "May 2021" "Version @nvtop_VERSION_MAJOR@.@nvtop_VERSION_MINOR@.@nvtop_VERSION_PATCH@" "nvtop command" .SH NAME nvtop \- NVIDIA GPU top @@ -32,9 +32,6 @@ .BR \-C ", " \-\-no\-color Monochrome mode. .TP -.BR \-N ", " \-\-no\-cache -Disable the (process id, user name, command line) cache and query the system at each refresh. -.TP .BR \-f ", " \-\-freedom\-unit Use fahrenheit as temperature scale. .TP @@ -50,6 +47,22 @@ .BR \-v ", " \-\-version Print the version and exit. +.SH INTERACTIVE SETUP WINDOW +.TP +You can enter the setup utility by pressing \fBF2\fR to view and modify the following interface options: +.TP +.I General +This section deals with general interface options. \fBColor support\fR and \fBinterface update interval\fR can be modified. +.TP +.I Devices +This section deals with the devices display (top of the interface). You can \fBswitch the temperature scale to fahrenheit\fR and \fBset the encoder/decoder hiding timer\fR. +.TP +.I Chart +This section deals with the line plots (middle of the interface). You can \fBreverse the plot direction\fR and \fBselect which metric is being shown in the plots\fR. +.TP +.I Processes +This section deals with the process list (bottom of the interface). You can \fBselect the sort order\fR, \fBselect the metric by which to sort the processes by\fR and \fBselect which metric is displayed\fR. + .SH INTERACTIVE COMMANDS .TP The following commands are available while in nvtop: @@ -69,6 +82,13 @@ .BR - Sort decreasingly. .TP +.BR F2 +Enter the setup utility to modify the interface options. +.TP +.BR F12 +Save the current interface options to persistent storage. +See the \fBCONFIGURATION FILE\fR section. +.TP .BR F9 "Kill" process: Select a signal to send to the highlighted process. .TP @@ -78,10 +98,19 @@ .BR F10 ", " q ", " Esc Quit. -.SH Dynamic meters +.SH DYNAMIC METERS .TP When the video encoder (ENC) and decoder (DEC) of the GPU are in use, new percentage meters will appear next to the GPU utilization bar. They will disappear automatically after some time of inactivity (see option -E). +.SH CONFIGURATION FILE +.LP +The configuration file follows the \fIXDG Base Directory Specification\fR and is stored at \fI$XDG_CONFIG_HOME/nvtop/interface.ini\fR. The location defaults to \fI$HOME/.config/nvtop/interface.ini\fR if the XDG location is not defined. +.LP +Do not edit this file. The file is automatically created or updated upon toggling the interface saving key \fBF12\fR. +.LP +The configuration is loaded during program initialization. +If no configuration file is present, default options are used. + .SH MEMORY SIZES .TP Memory sizes in nvtop are displayed as multiples of 1024 bytes or 1 KiB. diff -Nru nvtop-1.1.0/README.markdown nvtop-1.2.2/README.markdown --- nvtop-1.1.0/README.markdown 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/README.markdown 2021-07-24 13:07:26.000000000 +0000 @@ -14,20 +14,38 @@ Table of Contents ----------------- -1. [NVTOP Options and Interactive Commands](#nvtop-options-and-interactive-commands) -1. [GPU Support](#gpu-support) -1. [Build](#build) - 1. [Ubuntu / Debian](#ubuntu--debian) - 1. [Fedora / RedHat / CentOS](#fedora--redhat--centos) - 1. [OpenSUSE](#opensuse) - 1. [Arch Linux](#arch-linux) - 1. [Gentoo](#gentoo) - 1. [NVTOP Build](#nvtop-build) -1. [Troubleshoot](#troubleshoot) -1. [License](#license) +- [NVTOP Options and Interactive Commands](#nvtop-options-and-interactive-commands) + - [Interactive Setup Window](#interactive-setup-window) + - [Saving Preferences](#saving-preferences) + - [NVTOP Manual and Command line Options](#nvtop-manual-and-command-line-options) +- [GPU Support](#gpu-support) +- [Build](#build) +- [Distribution Specific Installation Process](#distribution-specific-installation-process) + - [Ubuntu / Debian](#ubuntu--debian) + - [Ubuntu disco (19.04) / Debian buster (stable)](#ubuntu-disco-1904--debian-buster-stable) + - [Fedora / RedHat / CentOS](#fedora--redhat--centos) + - [OpenSUSE](#opensuse) + - [Arch Linux](#arch-linux) + - [Docker](#docker) +- [NVTOP Build](#nvtop-build) +- [Troubleshoot](#troubleshoot) +- [License](#license) NVTOP Options and Interactive Commands -------------------------------------- +### Interactive Setup Window + +NVTOP has a builtin setup utility that provides a way to specialize the interface to your needs. +Simply press ``F2`` and select the options that are the best for you. + +![NVTOP Setup Window](/screenshot/Nvtop-config.png) + +### Saving Preferences + +You can save the preferences set in the setup window by pressing ``F12``. +The preferences will be loaded the next time you run ``nvtop``. + +### NVTOP Manual and Command line Options NVTOP comes with a manpage! ```bash @@ -50,7 +68,7 @@ Build ----- -Two libraries are required: +Two libraries are required in order for NVTOP to display GPU information: * The *NVIDIA Management Library* (*NVML*) which comes with the GPU driver. * This queries the GPU for information. @@ -102,26 +120,38 @@ - ```bash sudo pacman -S nvtop ``` - + ### Gentoo - ```bash sudo layman -a guru && sudo emerge -av nvtop ``` +### Docker + +- NVIDIA drivers (same as above) + +- [nvidia-docker](https://github.com/NVIDIA/nvidia-docker) (See [Container Toolkit Installation Guide](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker)) + +- ```bash + git clone https://github.com/Syllo/nvtop.git + docker build --tag nvtop . + docker run --interactive --tty --rm --runtime=nvidia --gpus all --pid=host nvtop + ``` + ## NVTOP Build ```bash git clone https://github.com/Syllo/nvtop.git mkdir -p nvtop/build && cd nvtop/build cmake .. +make -# If it errors with "Could NOT find NVML (missing: NVML_INCLUDE_DIRS)" -# try the following command instead, otherwise skip to the build with make. -cmake .. -DNVML_RETRIEVE_HEADER_ONLINE=True +# Install globally on the system +sudo make install -make -make install # You may need sufficient permission for that (root) +# Alternatively, install without privileges at a location of your choosing +# make DESTDIR="/your/install/path" install ``` If you use **conda** as environment manager and encounter an error while building nvtop, try `conda deactivate` before invoking `cmake`. Binary files /tmp/tmpf82rc2tv/E8GCXC4OLF/nvtop-1.1.0/screenshot/Nvtop-config.png and /tmp/tmpf82rc2tv/JOiBjR6kcM/nvtop-1.2.2/screenshot/Nvtop-config.png differ Binary files /tmp/tmpf82rc2tv/E8GCXC4OLF/nvtop-1.1.0/screenshot/NVTOP_ex1.png and /tmp/tmpf82rc2tv/JOiBjR6kcM/nvtop-1.2.2/screenshot/NVTOP_ex1.png differ diff -Nru nvtop-1.1.0/src/CMakeLists.txt nvtop-1.2.2/src/CMakeLists.txt --- nvtop-1.1.0/src/CMakeLists.txt 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/src/CMakeLists.txt 2021-07-24 13:07:26.000000000 +0000 @@ -9,46 +9,26 @@ nvtop.c interface.c interface_layout_selection.c + interface_options.c + interface_setup_win.c + interface_ring_buffer.c get_process_info_linux.c extract_gpuinfo.c + extract_gpuinfo_nvidia.c time.c - plot.c) + plot.c + ini.c) target_include_directories(nvtop PRIVATE ${PROJECT_SOURCE_DIR}/include ${PROJECT_BINARY_DIR}/include) -set(CMAKE_REQUIRED_INCLUDES "${NVML_INCLUDE_DIRS};${PROJECT_BINARY_DIR}/include") -set(CMAKE_REQUIRED_LIBRARIES "${NVML_LIBRARIES}") -unset(COMPILE_RESULT CACHE) -check_c_source_compiles( - " -#include -#include - -int main() -{ - nvmlReturn_t ret = nvmlInit(); - unsigned devCount; - nvmlReturn_t ret2 = nvmlDeviceGetCount(&devCount); - nvmlDevice_t device; - nvmlReturn_t ret3 = nvmlDeviceGetHandleByIndex(0u, &device); - nvmlReturn_t ret4 = nvmlDeviceGetComputeRunningProcesses(device, NULL, NULL); - nvmlReturn_t ret5 = nvmlDeviceGetGraphicsRunningProcesses(device, NULL, NULL); - return EXIT_SUCCESS; -} - " COMPILE_RESULT) - -if(NOT COMPILE_RESULT) - message(WARNING "The downloaded \"nvml.h\" header does not match the installed library version, unprecise information may be displayed in nvtop as a result." - "\nYou can solve this by dowloading it from the src repository of https://github.com/NVIDIA/nvidia-settings using the tag that matches your driver version and placing the file in the build/include directory of nvtop.") - target_compile_definitions(nvtop PRIVATE NVML_NO_UNVERSIONED_FUNC_DEFS) -endif() -unset(COMPILE_RESULT CACHE) set_property(TARGET nvtop PROPERTY C_STANDARD 11) +target_compile_definitions(nvtop PRIVATE _GNU_SOURCE) + target_link_libraries(nvtop - PRIVATE nvml ncurses m) + PRIVATE ncurses m ${CMAKE_DL_LIBS}) install (TARGETS nvtop RUNTIME DESTINATION bin) diff -Nru nvtop-1.1.0/src/extract_gpuinfo.c nvtop-1.2.2/src/extract_gpuinfo.c --- nvtop-1.1.0/src/extract_gpuinfo.c 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/src/extract_gpuinfo.c 2021-07-24 13:07:26.000000000 +0000 @@ -1,6 +1,6 @@ /* * - * Copyright (C) 2017 Maxime Schmitt + * Copyright (C) 2017-2021 Maxime Schmitt * * This file is part of Nvtop. * @@ -20,513 +20,238 @@ */ #include +#include #include #include #include #include "nvtop/extract_gpuinfo.h" +#include "nvtop/extract_gpuinfo_common.h" +#include "nvtop/extract_gpuinfo_nvidia.h" #include "nvtop/get_process_info.h" +#include "nvtop/time.h" #include "uthash.h" -#define HASH_FIND_PID(head, pidfield, out) \ - HASH_FIND(hh, head, pidfield, sizeof(intmax_t), out) +#define HASH_FIND_PID(head, key_ptr, out_ptr) \ + HASH_FIND(hh, head, key_ptr, sizeof(*key_ptr), out_ptr) -#define HASH_ADD_PID(head, pidfield, add) \ - HASH_ADD(hh, head, pidfield, sizeof(intmax_t), add) +#define HASH_ADD_PID(head, in_ptr) \ + HASH_ADD(hh, head, pid, sizeof(pid_t), in_ptr) -#define HASH_REPLACE_PID(head, pidfield, replaced) \ - HASH_REPLACE(hh, head, pidfield, sizeof(intmax_t), replaced) +typedef struct process_info_cache_struct { + pid_t pid; + char *cmdline; + char *user_name; + double last_total_consumed_cpu_time; + nvtop_time last_measurement_timestamp; + UT_hash_handle hh; +} process_info_cache; + +process_info_cache *cached_process_info = NULL; +process_info_cache *updated_process_info = NULL; -static bool nvml_initialized = false; - -bool init_gpu_info_extraction(void) { - if (!nvml_initialized) { - nvmlReturn_t retval = nvmlInit(); - if (retval != NVML_SUCCESS) { - fprintf(stderr, "Impossible to initialize nvidia nvml : %s\n", - nvmlErrorString(retval)); +bool gpuinfo_init_info_extraction(uint64_t mask_nvidia, unsigned *devices_count, + gpu_info **devices) { + unsigned nvidia_devices_count = 0; + gpuinfo_nvidia_device_handle *nvidia_devices = NULL; + if (gpuinfo_nvidia_init()) { + bool retval = gpuinfo_nvidia_get_device_handles( + &nvidia_devices, &nvidia_devices_count, mask_nvidia); + if (!retval || (retval && nvidia_devices_count == 0)) { + gpuinfo_nvidia_shutdown(); + nvidia_devices_count = 0; + nvidia_devices = NULL; + } + } + unsigned total_devices = nvidia_devices_count; + if (!total_devices) { + *devices = NULL; + } else { + *devices = malloc(total_devices * sizeof(**devices)); + if (!*devices) { + perror("Cannot allocate memory: "); + free(nvidia_devices); return false; } - nvml_initialized = true; } + + for (unsigned i = 0; i < nvidia_devices_count; ++i) { + (*devices)[i].gpu_type = gpuinfo_type_nvidia_proprietary; + (*devices)[i].nvidia_gpuhandle = nvidia_devices[i]; + (*devices)[i].processes_count = 0; + (*devices)[i].processes = NULL; + (*devices)[i].nvidia_internal.last_utilization_timestamp = 0; + } + free(nvidia_devices); + *devices_count = total_devices; return true; } -bool shutdown_gpu_info_extraction(void) { - if (nvml_initialized) { - nvmlReturn_t retval = nvmlShutdown(); - if (retval != NVML_SUCCESS) { - fprintf(stderr, "Impossible to shutdown nvidia nvml : %s\n", - nvmlErrorString(retval)); +bool gpuinfo_shutdown_info_extraction(unsigned device_count, + gpu_info *devices) { + for (unsigned i = 0; i < device_count; ++i) { + free(devices[i].processes); + } + free(devices); + gpuinfo_nvidia_shutdown(); + gpuinfo_clear_cache(); + return true; +} + +bool gpuinfo_populate_static_infos(unsigned device_count, gpu_info *devices) { + for (unsigned i = 0; i < device_count; ++i) { + switch (devices[i].gpu_type) { + case gpuinfo_type_nvidia_proprietary: + gpuinfo_nvidia_populate_static_info(devices[i].nvidia_gpuhandle, + &devices[i].static_info); + break; + default: + fprintf(stderr, + "Unknown GPU type encountered during static initialization\n"); return false; } - nvml_initialized = false; } return true; } -/** - * Normaly those informations are not changing over time - */ -static void populate_static_device_infos(struct device_info *dev_info) { - - // GPU NAME - nvmlReturn_t retval = - nvmlDeviceGetName(dev_info->device_handle, dev_info->device_name, - NVML_DEVICE_NAME_BUFFER_SIZE); - SET_VALID(device_name_valid, dev_info->valid); - if (retval != NVML_SUCCESS) { - memcpy(dev_info->device_name, "UNKNOWN", strlen("UNKNOWN") + 1); - RESET_VALID(device_name_valid, dev_info->valid); - } - - // PCIe LINK GEN MAX - retval = nvmlDeviceGetMaxPcieLinkGeneration(dev_info->device_handle, - &dev_info->max_pcie_link_gen); - SET_VALID(max_pcie_link_gen_valid, dev_info->valid); - if (retval != NVML_SUCCESS) { - dev_info->max_pcie_link_gen = 0; - RESET_VALID(max_pcie_link_gen_valid, dev_info->valid); - } - // PCIe LINK WIDTH MAX - retval = nvmlDeviceGetMaxPcieLinkWidth(dev_info->device_handle, - &dev_info->max_pcie_link_width); - SET_VALID(max_pcie_link_width_valid, dev_info->valid); - if (retval != NVML_SUCCESS) { - dev_info->max_pcie_link_width = 0; - RESET_VALID(max_pcie_link_width_valid, dev_info->valid); - } - - // GPU TEMP SHUTDOWN - retval = nvmlDeviceGetTemperatureThreshold( - dev_info->device_handle, NVML_TEMPERATURE_THRESHOLD_SHUTDOWN, - &dev_info->gpu_temp_shutdown); - SET_VALID(gpu_temp_shutdown_valid, dev_info->valid); - if (retval != NVML_SUCCESS) { - dev_info->gpu_temp_shutdown = 0; - RESET_VALID(gpu_temp_shutdown_valid, dev_info->valid); - } - - // GPU TEMP SLOWDOWN - retval = nvmlDeviceGetTemperatureThreshold( - dev_info->device_handle, NVML_TEMPERATURE_THRESHOLD_SLOWDOWN, - &dev_info->gpu_temp_slowdown); - SET_VALID(gpu_temp_slowdown_valid, dev_info->valid); - if (retval != NVML_SUCCESS) { - dev_info->gpu_temp_slowdown = 0; - RESET_VALID(gpu_temp_slowdown_valid, dev_info->valid); +bool gpuinfo_refresh_dynamic_info(unsigned device_count, gpu_info *devices) { + for (unsigned i = 0; i < device_count; ++i) { + switch (devices[i].gpu_type) { + case gpuinfo_type_nvidia_proprietary: + gpuinfo_nvidia_refresh_dynamic_info(devices[i].nvidia_gpuhandle, + &devices[i].dynamic_info); + break; + default: + fprintf(stderr, + "Unknown GPU type encountered during static initialization\n"); + return false; + } } + return true; } -struct pid_infos { - intmax_t pid; - char *process_name; - char *user_name; - double cpu_elapsed_time; - nvtop_time last_measurement; - UT_hash_handle hh; -}; - -static struct pid_infos *saved_pid_infos = NULL; // Hash table saved pid info -static struct pid_infos *current_used_infos = NULL; // Hash table saved pid info - -static void populate_infos(intmax_t pid, struct pid_infos *infos) { - infos->pid = pid; - get_command_from_pid((pid_t)pid, &infos->process_name); - if (infos->process_name == NULL) { - infos->process_name = malloc(4 * sizeof(*infos->process_name)); - memcpy(infos->process_name, "N/A", 4); - } - get_username_from_pid((pid_t)pid, &infos->user_name); - if (infos->user_name == NULL) { - infos->user_name = malloc(4 * sizeof(*infos->user_name)); - memcpy(infos->user_name, "N/A", 4); - } - struct process_cpu_usage cpu_usage; - if (get_process_info((pid_t)pid, &cpu_usage)) { - infos->cpu_elapsed_time = - cpu_usage.total_kernel_time + cpu_usage.total_user_time; - infos->last_measurement = cpu_usage.time; - } else { - infos->cpu_elapsed_time = -1.; - } -} +static void gpuinfo_populate_process_infos(unsigned device_count, + gpu_info *devices) { + for (unsigned i = 0; i < device_count; ++i) { + for (unsigned j = 0; j < devices[i].processes_count; ++j) { + pid_t current_pid = devices[i].processes[j].pid; + process_info_cache *cached_pid_info; + + HASH_FIND_PID(cached_process_info, ¤t_pid, cached_pid_info); + if (!cached_pid_info) { + HASH_FIND_PID(updated_process_info, ¤t_pid, cached_pid_info); + if (!cached_pid_info) { + // Newly encountered pid + cached_pid_info = malloc(sizeof(*cached_pid_info)); + cached_pid_info->pid = current_pid; + get_username_from_pid(current_pid, &cached_pid_info->user_name); + get_command_from_pid(current_pid, &cached_pid_info->cmdline); + cached_pid_info->last_total_consumed_cpu_time = -1.; + HASH_ADD_PID(updated_process_info, cached_pid_info); + } + } else { + // Already encountered so delete from cached list to avoid freeing + // memory at the end of this function + HASH_DEL(cached_process_info, cached_pid_info); + HASH_ADD_PID(updated_process_info, cached_pid_info); + } -static void -update_gpu_process_from_process_info(unsigned int num_process, - nvmlProcessInfo_t *p_info, - struct gpu_process *gpu_proc_info) { - - for (unsigned int i = 0; i < num_process; ++i) { - gpu_proc_info[i].pid = p_info[i].pid; - struct pid_infos *infos; - HASH_FIND_PID(saved_pid_infos, &gpu_proc_info[i].pid, infos); - if (!infos) { - HASH_FIND_PID(current_used_infos, &gpu_proc_info[i].pid, infos); - if (!infos) { // getting information from the system - infos = malloc(sizeof(*infos)); - populate_infos(gpu_proc_info[i].pid, infos); - HASH_ADD_PID(current_used_infos, pid, infos); + if (cached_pid_info->cmdline) { + devices[i].processes[j].cmdline = cached_pid_info->cmdline; + SET_VALID(gpuinfo_process_cmdline_valid, devices[i].processes[j].valid); } - } else { - HASH_DEL(saved_pid_infos, infos); - HASH_ADD_PID(current_used_infos, pid, infos); - } - struct process_cpu_usage cpu_usage; - if (get_process_info((pid_t)p_info[i].pid, &cpu_usage)) { - if (infos->cpu_elapsed_time > -1.) { - gpu_proc_info[i].cpu_usage = - 100. * - (cpu_usage.total_user_time + cpu_usage.total_kernel_time - - infos->cpu_elapsed_time) / - nvtop_difftime(infos->last_measurement, cpu_usage.time); - } else { - gpu_proc_info[i].cpu_usage = 0.; + if (cached_pid_info->user_name) { + devices[i].processes[j].user_name = cached_pid_info->user_name; + SET_VALID(gpuinfo_process_user_name_valid, + devices[i].processes[j].valid); } - gpu_proc_info[i].cpu_memory_virt = cpu_usage.virtual_memory; - gpu_proc_info[i].cpu_memory_res = cpu_usage.resident_memory; - infos->last_measurement = cpu_usage.time; - infos->cpu_elapsed_time = - cpu_usage.total_user_time + cpu_usage.total_kernel_time; - } else { - gpu_proc_info[i].cpu_memory_virt = 0; - gpu_proc_info[i].cpu_memory_res = 0; - gpu_proc_info[i].cpu_usage = 0.; - infos->cpu_elapsed_time = -1.; - } - /*fprintf(stderr, "PID %d cpu usage %f virt %zu res %zu\n", - * (int)p_info[i].pid,*/ - /*gpu_proc_info[i].cpu_usage, gpu_proc_info[i].cpu_memory_virt,*/ - /*gpu_proc_info[i].cpu_memory_res);*/ - gpu_proc_info[i].process_name = infos->process_name; - gpu_proc_info[i].user_name = infos->user_name; - gpu_proc_info[i].used_memory = p_info[i].usedGpuMemory; - } -} -static void update_graphical_process(struct device_info *dinfo) { - nvmlReturn_t retval; + struct process_cpu_usage cpu_usage; + if (get_process_info(current_pid, &cpu_usage)) { + if (cached_pid_info->last_total_consumed_cpu_time > -1.) { + double usage_percent = + round(100. * + (cpu_usage.total_user_time + cpu_usage.total_kernel_time - + cached_pid_info->last_total_consumed_cpu_time) / + nvtop_difftime(cached_pid_info->last_measurement_timestamp, + cpu_usage.timestamp)); + devices[i].processes[j].cpu_usage = (unsigned)usage_percent; + } else { + devices[i].processes[j].cpu_usage = 0; + } + SET_VALID(gpuinfo_process_cpu_usage_valid, + devices[i].processes[j].valid); + cached_pid_info->last_measurement_timestamp = cpu_usage.timestamp; + cached_pid_info->last_total_consumed_cpu_time = + cpu_usage.total_kernel_time + cpu_usage.total_user_time; + devices[i].processes[j].cpu_memory_res = cpu_usage.resident_memory; + SET_VALID(gpuinfo_process_cpu_memory_res_valid, + devices[i].processes[j].valid); + devices[i].processes[j].cpu_memory_virt = cpu_usage.virtual_memory; + SET_VALID(gpuinfo_process_cpu_memory_virt_valid, + devices[i].processes[j].valid); + } else { + cached_pid_info->last_total_consumed_cpu_time = -1; + } - unsigned int array_size; -retry_querry_graphical: - array_size = dinfo->size_proc_buffers; - unsigned int prev_array_size = array_size; - retval = nvmlDeviceGetGraphicsRunningProcesses( - dinfo->device_handle, &array_size, dinfo->process_infos); - if (retval != NVML_SUCCESS) { - if (retval == NVML_ERROR_INSUFFICIENT_SIZE) { - unsigned int new_size = prev_array_size * 2 > array_size * 2 - ? prev_array_size * 2 - : array_size * 2; - dinfo->size_proc_buffers = new_size; - dinfo->graphic_procs = realloc(dinfo->graphic_procs, - new_size * sizeof(*dinfo->graphic_procs)); - dinfo->compute_procs = realloc(dinfo->compute_procs, - new_size * sizeof(*dinfo->compute_procs)); - dinfo->process_infos = realloc(dinfo->process_infos, - new_size * sizeof(*dinfo->process_infos)); - goto retry_querry_graphical; - } else { - dinfo->num_graphical_procs = 0; + // Process memory usage percent of total device memory + if (IS_VALID(gpuinfo_total_memory_valid, devices[i].dynamic_info.valid) && + IS_VALID(gpuinfo_process_gpu_memory_usage_valid, + devices[i].processes[j].valid)) { + float percentage = + roundf(100.f * (float)devices[i].processes[j].gpu_memory_usage / + (float)devices[i].dynamic_info.total_memory); + devices[i].processes[j].gpu_memory_percentage = (unsigned)percentage; + assert(devices[i].processes[j].gpu_memory_percentage <= 100); + SET_VALID(gpuinfo_process_gpu_memory_percentage_valid, + devices[i].processes[j].valid); + } } - } else { - dinfo->num_graphical_procs = array_size; } - update_gpu_process_from_process_info( - dinfo->num_graphical_procs, dinfo->process_infos, dinfo->graphic_procs); -} - -static void update_compute_process(struct device_info *dinfo) { - nvmlReturn_t retval; - - unsigned int array_size; -retry_querry_compute: - array_size = dinfo->size_proc_buffers; - unsigned int prev_array_size = array_size; - retval = nvmlDeviceGetComputeRunningProcesses( - dinfo->device_handle, &array_size, dinfo->process_infos); - if (retval != NVML_SUCCESS) { - if (retval == NVML_ERROR_INSUFFICIENT_SIZE) { - unsigned int new_size = prev_array_size * 2 > array_size * 2 - ? prev_array_size * 2 - : array_size * 2; - dinfo->size_proc_buffers = new_size; - dinfo->graphic_procs = realloc(dinfo->graphic_procs, - new_size * sizeof(*dinfo->graphic_procs)); - dinfo->compute_procs = realloc(dinfo->compute_procs, - new_size * sizeof(*dinfo->compute_procs)); - dinfo->process_infos = realloc(dinfo->process_infos, - new_size * sizeof(*dinfo->process_infos)); - goto retry_querry_compute; - } else { - dinfo->num_compute_procs = 0; + process_info_cache *pid_not_encountered, *tmp; + HASH_ITER(hh, cached_process_info, pid_not_encountered, tmp) { + HASH_DEL(cached_process_info, pid_not_encountered); + free(pid_not_encountered->cmdline); + free(pid_not_encountered->user_name); + free(pid_not_encountered); + } + cached_process_info = updated_process_info; + updated_process_info = NULL; +} + +bool gpuinfo_refresh_processes(unsigned device_count, gpu_info *devices) { + for (unsigned i = 0; i < device_count; ++i) { + switch (devices[i].gpu_type) { + case gpuinfo_type_nvidia_proprietary: { + unsigned processes_count = 0; + gpu_process *processes = NULL; + gpuinfo_nvidia_get_running_processes(devices[i].nvidia_gpuhandle, + &devices[i].nvidia_internal, + &processes_count, &processes); + free(devices[i].processes); + devices[i].processes = processes; + devices[i].processes_count = processes_count; + } break; + default: + fprintf(stderr, + "Unknown GPU type encountered during static initialization\n"); + return false; } - } else { - dinfo->num_compute_procs = array_size; } - update_gpu_process_from_process_info( - dinfo->num_compute_procs, dinfo->process_infos, dinfo->compute_procs); -} - -void update_device_infos(unsigned int num_devs, struct device_info *dev_info) { - for (unsigned int i = 0; i < num_devs; ++i) { - struct device_info *curr_dev_info = &dev_info[i]; - - // GPU CLK - nvmlReturn_t retval = nvmlDeviceGetClockInfo( - curr_dev_info->device_handle, NVML_CLOCK_GRAPHICS, - &curr_dev_info->gpu_clock_speed); - SET_VALID(gpu_clock_speed_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->gpu_clock_speed = 0; - RESET_VALID(gpu_clock_speed_valid, curr_dev_info->valid); - } - - // MEM CLK - retval = - nvmlDeviceGetClockInfo(curr_dev_info->device_handle, NVML_CLOCK_MEM, - &curr_dev_info->mem_clock_speed); - SET_VALID(mem_clock_speed_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->mem_clock_speed = 0; - RESET_VALID(mem_clock_speed_valid, curr_dev_info->valid); - } - - // GPU CLK MAX - retval = nvmlDeviceGetMaxClockInfo(curr_dev_info->device_handle, - NVML_CLOCK_GRAPHICS, - &curr_dev_info->gpu_clock_speed_max); - SET_VALID(gpu_clock_speed_max_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->gpu_clock_speed_max = 0; - RESET_VALID(gpu_clock_speed_max_valid, curr_dev_info->valid); - } - - // MEM CLK MAX - retval = - nvmlDeviceGetMaxClockInfo(curr_dev_info->device_handle, NVML_CLOCK_MEM, - &curr_dev_info->mem_clock_speed_max); - SET_VALID(mem_clock_speed_max_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->mem_clock_speed_max = 0; - RESET_VALID(mem_clock_speed_max_valid, curr_dev_info->valid); - } - - // GPU / MEM UTIL RATE - nvmlUtilization_t util_rate; - retval = - nvmlDeviceGetUtilizationRates(curr_dev_info->device_handle, &util_rate); - if (retval != NVML_SUCCESS) { - curr_dev_info->gpu_util_rate = 0; - curr_dev_info->mem_util_rate = 0; - RESET_VALID(gpu_util_rate_valid, curr_dev_info->valid); - RESET_VALID(mem_util_rate_valid, curr_dev_info->valid); - } else { - curr_dev_info->gpu_util_rate = util_rate.gpu; - curr_dev_info->mem_util_rate = util_rate.memory; - SET_VALID(gpu_util_rate_valid, curr_dev_info->valid); - SET_VALID(mem_util_rate_valid, curr_dev_info->valid); - } - - // FREE / TOTAL / USED MEMORY - nvmlMemory_t meminfo; - retval = nvmlDeviceGetMemoryInfo(curr_dev_info->device_handle, &meminfo); - if (retval != NVML_SUCCESS) { - curr_dev_info->free_memory = 0; - curr_dev_info->total_memory = 0; - curr_dev_info->used_memory = 0; - RESET_VALID(free_memory_valid, curr_dev_info->valid); - RESET_VALID(total_memory_valid, curr_dev_info->valid); - RESET_VALID(used_memory_valid, curr_dev_info->valid); - } else { - curr_dev_info->free_memory = meminfo.free; - curr_dev_info->total_memory = meminfo.total; - curr_dev_info->used_memory = meminfo.used; - SET_VALID(free_memory_valid, curr_dev_info->valid); - SET_VALID(total_memory_valid, curr_dev_info->valid); - SET_VALID(used_memory_valid, curr_dev_info->valid); - } - - // PCIe LINK GEN - retval = nvmlDeviceGetCurrPcieLinkGeneration( - curr_dev_info->device_handle, &curr_dev_info->cur_pcie_link_gen); - SET_VALID(cur_pcie_link_gen_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->cur_pcie_link_gen = 0; - RESET_VALID(cur_pcie_link_gen_valid, curr_dev_info->valid); - } - - // PCIe LINK WIDTH - retval = nvmlDeviceGetCurrPcieLinkWidth( - curr_dev_info->device_handle, &curr_dev_info->cur_pcie_link_width); - SET_VALID(cur_pcie_link_width_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->cur_pcie_link_width = 0; - RESET_VALID(cur_pcie_link_width_valid, curr_dev_info->valid); - } - - // PCIe TX THROUGHPUT - retval = nvmlDeviceGetPcieThroughput(curr_dev_info->device_handle, - NVML_PCIE_UTIL_TX_BYTES, - &curr_dev_info->pcie_tx); - SET_VALID(pcie_tx_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->pcie_tx = 0; - RESET_VALID(pcie_tx_valid, curr_dev_info->valid); - } - - // PCIe RX THROUGHPUT - retval = nvmlDeviceGetPcieThroughput(curr_dev_info->device_handle, - NVML_PCIE_UTIL_RX_BYTES, - &curr_dev_info->pcie_rx); - SET_VALID(pcie_rx_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->pcie_rx = 0; - RESET_VALID(pcie_rx_valid, curr_dev_info->valid); - } - // FAN SPEED - retval = nvmlDeviceGetFanSpeed(curr_dev_info->device_handle, - &curr_dev_info->fan_speed); - SET_VALID(fan_speed_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->fan_speed = 0; - RESET_VALID(fan_speed_valid, curr_dev_info->valid); - } - - // GPU TEMP - retval = nvmlDeviceGetTemperature(curr_dev_info->device_handle, - NVML_TEMPERATURE_GPU, - &curr_dev_info->gpu_temp); - SET_VALID(gpu_temp_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->gpu_temp = 0; - RESET_VALID(gpu_temp_valid, curr_dev_info->valid); - } - - // POWER DRAW - retval = nvmlDeviceGetPowerUsage(curr_dev_info->device_handle, - &curr_dev_info->power_draw); - SET_VALID(power_draw_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->power_draw = 0; - RESET_VALID(power_draw_valid, curr_dev_info->valid); - } - - // POWER MAX - retval = nvmlDeviceGetEnforcedPowerLimit(curr_dev_info->device_handle, - &curr_dev_info->power_draw_max); - SET_VALID(power_draw_max_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->power_draw_max = 0; - RESET_VALID(power_draw_max_valid, curr_dev_info->valid); - } - - // Encoder infos - retval = nvmlDeviceGetEncoderUtilization(curr_dev_info->device_handle, - &curr_dev_info->encoder_rate, - &curr_dev_info->encoder_sampling); - SET_VALID(encoder_rate_valid, curr_dev_info->valid); - SET_VALID(encoder_sampling_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->encoder_rate = 0; - curr_dev_info->encoder_sampling = 0; - RESET_VALID(encoder_rate_valid, curr_dev_info->valid); - RESET_VALID(encoder_sampling_valid, curr_dev_info->valid); - } - - // Decoder infos - retval = nvmlDeviceGetDecoderUtilization(curr_dev_info->device_handle, - &curr_dev_info->decoder_rate, - &curr_dev_info->decoder_sampling); - SET_VALID(decoder_rate_valid, curr_dev_info->valid); - SET_VALID(decoder_sampling_valid, curr_dev_info->valid); - if (retval != NVML_SUCCESS) { - curr_dev_info->decoder_rate = 0; - curr_dev_info->decoder_sampling = 0; - RESET_VALID(decoder_rate_valid, curr_dev_info->valid); - RESET_VALID(decoder_sampling_valid, curr_dev_info->valid); - } + gpuinfo_populate_process_infos(device_count, devices); - // Process informations - update_graphical_process(curr_dev_info); - update_compute_process(curr_dev_info); - - } // Loop over devices - - // Now delete the (pid,username,command) cache entries that are not in use - // anymore - struct pid_infos *old_info, *tmp; - HASH_ITER(hh, saved_pid_infos, old_info, tmp) { - HASH_DEL(saved_pid_infos, old_info); - free(old_info->process_name); - free(old_info->user_name); - free(old_info); - } - saved_pid_infos = current_used_infos; - current_used_infos = NULL; + return true; } -unsigned int initialize_device_info(struct device_info **dev_info, - size_t gpu_mask) { - unsigned int num_devices; - nvmlReturn_t retval = nvmlDeviceGetCount(&num_devices); - if (retval != NVML_SUCCESS) { - fprintf(stderr, "Impossible to get the number of devices : %s\n", - nvmlErrorString(retval)); - return 0; - } - *dev_info = malloc(num_devices * sizeof(**dev_info)); - struct device_info *devs = *dev_info; - unsigned int num_queriable = 0; - for (unsigned int i = 0; i < num_devices; ++i) { - retval = nvmlDeviceGetHandleByIndex(i, &devs[num_queriable].device_handle); - if (i < CHAR_BIT * sizeof(gpu_mask) && (gpu_mask & (1 << i)) == 0) - continue; - if (retval != NVML_SUCCESS) { - if (retval == NVML_ERROR_NO_PERMISSION) { - continue; - } else { - fprintf(stderr, "Impossible to get handle for device number %u : %s\n", - i, nvmlErrorString(retval)); - free(*dev_info); - return 0; - } - } else { - populate_static_device_infos(&devs[num_queriable]); -#define DEF_PROC_NUM 25 - devs[num_queriable].size_proc_buffers = DEF_PROC_NUM; - devs[num_queriable].compute_procs = - malloc(DEF_PROC_NUM * sizeof(*devs[num_queriable].compute_procs)); - devs[num_queriable].graphic_procs = - malloc(DEF_PROC_NUM * sizeof(*devs[num_queriable].graphic_procs)); - devs[num_queriable].process_infos = - malloc(DEF_PROC_NUM * sizeof(*devs[num_queriable].process_infos)); -#undef DEF_PROC_NUM - num_queriable += 1; +void gpuinfo_clear_cache(void) { + if (cached_process_info) { + process_info_cache *pid_cached, *tmp; + HASH_ITER(hh, cached_process_info, pid_cached, tmp) { + HASH_DEL(cached_process_info, pid_cached); + free(pid_cached->cmdline); + free(pid_cached->user_name); + free(pid_cached); } } - return num_queriable; -} - -void clean_pid_cache(void) { - struct pid_infos *old_info, *tmp; - // The current_used_infos should be NULL here - assert(current_used_infos == NULL); - HASH_ITER(hh, saved_pid_infos, old_info, tmp) { - HASH_DEL(saved_pid_infos, old_info); - free(old_info->process_name); - free(old_info->user_name); - free(old_info); - } -} - -void clean_device_info(unsigned int num_devs, struct device_info *dev_info) { - for (unsigned int i = 0; i < num_devs; ++i) { - free(dev_info[i].graphic_procs); - free(dev_info[i].compute_procs); - free(dev_info[i].process_infos); - } - free(dev_info); - clean_pid_cache(); } diff -Nru nvtop-1.1.0/src/extract_gpuinfo_nvidia.c nvtop-1.2.2/src/extract_gpuinfo_nvidia.c --- nvtop-1.1.0/src/extract_gpuinfo_nvidia.c 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/src/extract_gpuinfo_nvidia.c 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,760 @@ +/* + * + * Copyright (C) 2021 Maxime Schmitt + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#include "nvtop/extract_gpuinfo_nvidia.h" +#include "nvtop/extract_gpuinfo_common.h" + +#include +#include +#include +#include + +#define NVML_SUCCESS 0 +#define NVML_ERROR_INSUFFICIENT_SIZE 7 + +typedef int nvmlReturn_t; // store the enum as int + +// Init and shutdown + +static nvmlReturn_t (*nvmlInit)(void); + +static nvmlReturn_t (*nvmlShutdown)(void); + +// Static information and helper functions + +static nvmlReturn_t (*nvmlDeviceGetCount)(unsigned int *deviceCount); + +static nvmlReturn_t (*nvmlDeviceGetHandleByIndex)(unsigned int index, + nvmlDevice_t *device); + +static const char *(*nvmlErrorString)(nvmlReturn_t); + +static nvmlReturn_t (*nvmlDeviceGetName)(nvmlDevice_t device, char *name, + unsigned int length); + +static nvmlReturn_t (*nvmlDeviceGetMaxPcieLinkGeneration)( + nvmlDevice_t device, unsigned int *maxLinkGen); + +static nvmlReturn_t (*nvmlDeviceGetMaxPcieLinkWidth)( + nvmlDevice_t device, unsigned int *maxLinkWidth); + +typedef enum { + NVML_TEMPERATURE_THRESHOLD_SHUTDOWN = 0, + NVML_TEMPERATURE_THRESHOLD_SLOWDOWN = 1, +} nvmlTemperatureThresholds_t; + +static nvmlReturn_t (*nvmlDeviceGetTemperatureThreshold)( + nvmlDevice_t device, nvmlTemperatureThresholds_t thresholdType, + unsigned int *temp); + +// Dynamic information extraction + +typedef enum { + NVML_CLOCK_GRAPHICS = 0, + NVML_CLOCK_SM = 1, + NVML_CLOCK_MEM = 2, + NVML_CLOCK_VIDEO = 3, +} nvmlClockType_t; + +static nvmlReturn_t (*nvmlDeviceGetClockInfo)(nvmlDevice_t device, + nvmlClockType_t type, + unsigned int *clock); + +static nvmlReturn_t (*nvmlDeviceGetMaxClockInfo)(nvmlDevice_t device, + nvmlClockType_t type, + unsigned int *clock); + +typedef struct { + unsigned int gpu; + unsigned int memory; +} nvmlUtilization_t; + +static nvmlReturn_t (*nvmlDeviceGetUtilizationRates)( + nvmlDevice_t device, nvmlUtilization_t *utilization); + +typedef struct { + unsigned long long total; + unsigned long long free; + unsigned long long used; +} nvmlMemory_t; + +static nvmlReturn_t (*nvmlDeviceGetMemoryInfo)(nvmlDevice_t device, + nvmlMemory_t *memory); + +static nvmlReturn_t (*nvmlDeviceGetCurrPcieLinkGeneration)( + nvmlDevice_t device, unsigned int *currLinkGen); + +static nvmlReturn_t (*nvmlDeviceGetCurrPcieLinkWidth)( + nvmlDevice_t device, unsigned int *currLinkWidth); + +typedef enum { + NVML_PCIE_UTIL_TX_BYTES = 0, + NVML_PCIE_UTIL_RX_BYTES = 1, +} nvmlPcieUtilCounter_t; + +static nvmlReturn_t (*nvmlDeviceGetPcieThroughput)( + nvmlDevice_t device, nvmlPcieUtilCounter_t counter, unsigned int *value); + +static nvmlReturn_t (*nvmlDeviceGetFanSpeed)(nvmlDevice_t device, + unsigned int *speed); + +typedef enum { + NVML_TEMPERATURE_GPU = 0, +} nvmlTemperatureSensors_t; + +static nvmlReturn_t (*nvmlDeviceGetTemperature)( + nvmlDevice_t device, nvmlTemperatureSensors_t sensorType, + unsigned int *temp); + +static nvmlReturn_t (*nvmlDeviceGetPowerUsage)(nvmlDevice_t device, + unsigned int *power); + +static nvmlReturn_t (*nvmlDeviceGetEnforcedPowerLimit)(nvmlDevice_t device, + unsigned int *limit); + +static nvmlReturn_t (*nvmlDeviceGetEncoderUtilization)( + nvmlDevice_t device, unsigned int *utilization, + unsigned int *samplingPeriodUs); + +static nvmlReturn_t (*nvmlDeviceGetDecoderUtilization)( + nvmlDevice_t device, unsigned int *utilization, + unsigned int *samplingPeriodUs); + +// Processes running on GPU + +typedef struct { + unsigned int pid; + unsigned long long usedGpuMemory; + // unsigned int gpuInstanceId; // not supported by older NVIDIA drivers + // unsigned int computeInstanceId; // not supported by older NVIDIA drivers +} nvmlProcessInfo_t; + +static nvmlReturn_t (*nvmlDeviceGetGraphicsRunningProcesses)( + nvmlDevice_t device, unsigned int *infoCount, nvmlProcessInfo_t *infos); + +static nvmlReturn_t (*nvmlDeviceGetComputeRunningProcesses)( + nvmlDevice_t device, unsigned int *infoCount, nvmlProcessInfo_t *infos); + +static void *libnvidia_ml_handle; + +static nvmlReturn_t last_nvml_return_status = NVML_SUCCESS; +static char didnt_call_gpuinfo_init[] = + "The NVIDIA extraction has not been initialized, please call " + "gpuinfo_nvidia_init\n"; +static const char *local_error_string = didnt_call_gpuinfo_init; + +// Processes GPU Utilization + +typedef struct { + unsigned int pid; + unsigned long long timeStamp; + unsigned int smUtil; + unsigned int memUtil; + unsigned int encUtil; + unsigned int decUtil; +} nvmlProcessUtilizationSample_t; + +nvmlReturn_t (*nvmlDeviceGetProcessUtilization)( + nvmlDevice_t device, nvmlProcessUtilizationSample_t *utilization, + unsigned int *processSamplesCount, unsigned long long lastSeenTimeStamp); + +/* + * + * This function loads the libnvidia-ml.so shared object, initializes the + * required function pointers and calls the nvidia library initialization + * function. Returns true if everything has been initialized successfully. If + * false is returned, the cause of the error can be retrieved by calling the + * function gpuinfo_nvidia_last_error_string. + * + */ +bool gpuinfo_nvidia_init(void) { + + libnvidia_ml_handle = dlopen("libnvidia-ml.so", RTLD_LAZY); + if (!libnvidia_ml_handle) + libnvidia_ml_handle = dlopen("libnvidia-ml.so.1", RTLD_LAZY); + if (!libnvidia_ml_handle) { + local_error_string = dlerror(); + return false; + } + + // Default to last version + nvmlInit = (__typeof__(nvmlInit))dlsym(libnvidia_ml_handle, "nvmlInit_v2"); + if (!nvmlInit) + nvmlInit = (__typeof__(nvmlInit))dlsym(libnvidia_ml_handle, "nvmlInit"); + if (!nvmlInit) + goto init_error_clean_exit; + + nvmlShutdown = + (__typeof__(nvmlShutdown))dlsym(libnvidia_ml_handle, "nvmlShutdown"); + if (!nvmlShutdown) + goto init_error_clean_exit; + + // Default to last version if available + nvmlDeviceGetCount = (__typeof__(nvmlDeviceGetCount))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetCount_v2"); + if (!nvmlDeviceGetCount) + nvmlDeviceGetCount = (__typeof__(nvmlDeviceGetCount))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetCount"); + if (!nvmlDeviceGetCount) + goto init_error_clean_exit; + + nvmlDeviceGetHandleByIndex = (__typeof__(nvmlDeviceGetHandleByIndex))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetHandleByIndex_v2"); + if (!nvmlDeviceGetHandleByIndex) + nvmlDeviceGetHandleByIndex = (__typeof__(nvmlDeviceGetHandleByIndex))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetHandleByIndex"); + if (!nvmlDeviceGetHandleByIndex) + goto init_error_clean_exit; + + nvmlErrorString = (__typeof__(nvmlErrorString))dlsym(libnvidia_ml_handle, + "nvmlErrorString"); + if (!nvmlErrorString) + goto init_error_clean_exit; + + nvmlDeviceGetName = (__typeof__(nvmlDeviceGetName))dlsym(libnvidia_ml_handle, + "nvmlDeviceGetName"); + if (!nvmlDeviceGetName) + goto init_error_clean_exit; + + nvmlDeviceGetMaxPcieLinkGeneration = + (__typeof__(nvmlDeviceGetMaxPcieLinkGeneration))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetMaxPcieLinkGeneration"); + if (!nvmlDeviceGetMaxPcieLinkGeneration) + goto init_error_clean_exit; + + nvmlDeviceGetMaxPcieLinkWidth = + (__typeof__(nvmlDeviceGetMaxPcieLinkWidth))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetMaxPcieLinkWidth"); + if (!nvmlDeviceGetMaxPcieLinkWidth) + goto init_error_clean_exit; + + nvmlDeviceGetTemperatureThreshold = + (__typeof__(nvmlDeviceGetTemperatureThreshold))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetTemperatureThreshold"); + if (!nvmlDeviceGetTemperatureThreshold) + goto init_error_clean_exit; + + nvmlDeviceGetClockInfo = (__typeof__(nvmlDeviceGetClockInfo))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetClockInfo"); + if (!nvmlDeviceGetClockInfo) + goto init_error_clean_exit; + + nvmlDeviceGetMaxClockInfo = (__typeof__(nvmlDeviceGetMaxClockInfo))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetMaxClockInfo"); + if (!nvmlDeviceGetMaxClockInfo) + goto init_error_clean_exit; + + nvmlDeviceGetUtilizationRates = + (__typeof__(nvmlDeviceGetUtilizationRates))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetUtilizationRates"); + if (!nvmlDeviceGetUtilizationRates) + goto init_error_clean_exit; + + nvmlDeviceGetMemoryInfo = (__typeof__(nvmlDeviceGetMemoryInfo))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetMemoryInfo"); + if (!nvmlDeviceGetMemoryInfo) + goto init_error_clean_exit; + + nvmlDeviceGetCurrPcieLinkGeneration = + (__typeof__(nvmlDeviceGetCurrPcieLinkGeneration))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetCurrPcieLinkGeneration"); + if (!nvmlDeviceGetCurrPcieLinkGeneration) + goto init_error_clean_exit; + + nvmlDeviceGetCurrPcieLinkWidth = + (__typeof__(nvmlDeviceGetCurrPcieLinkWidth))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetCurrPcieLinkWidth"); + if (!nvmlDeviceGetCurrPcieLinkWidth) + goto init_error_clean_exit; + + nvmlDeviceGetPcieThroughput = (__typeof__(nvmlDeviceGetPcieThroughput))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetPcieThroughput"); + if (!nvmlDeviceGetPcieThroughput) + goto init_error_clean_exit; + + nvmlDeviceGetFanSpeed = (__typeof__(nvmlDeviceGetFanSpeed))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetFanSpeed"); + if (!nvmlDeviceGetFanSpeed) + goto init_error_clean_exit; + + nvmlDeviceGetTemperature = (__typeof__(nvmlDeviceGetTemperature))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetTemperature"); + if (!nvmlDeviceGetTemperature) + goto init_error_clean_exit; + + nvmlDeviceGetPowerUsage = (__typeof__(nvmlDeviceGetPowerUsage))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetPowerUsage"); + if (!nvmlDeviceGetPowerUsage) + goto init_error_clean_exit; + + nvmlDeviceGetEnforcedPowerLimit = + (__typeof__(nvmlDeviceGetEnforcedPowerLimit))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetEnforcedPowerLimit"); + if (!nvmlDeviceGetEnforcedPowerLimit) + goto init_error_clean_exit; + + nvmlDeviceGetEncoderUtilization = + (__typeof__(nvmlDeviceGetEncoderUtilization))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetEncoderUtilization"); + if (!nvmlDeviceGetEncoderUtilization) + goto init_error_clean_exit; + + nvmlDeviceGetDecoderUtilization = + (__typeof__(nvmlDeviceGetDecoderUtilization))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetDecoderUtilization"); + if (!nvmlDeviceGetDecoderUtilization) + goto init_error_clean_exit; + + nvmlDeviceGetGraphicsRunningProcesses = + (__typeof__(nvmlDeviceGetGraphicsRunningProcesses))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetGraphicsRunningProcesses"); + if (!nvmlDeviceGetGraphicsRunningProcesses) + goto init_error_clean_exit; + + nvmlDeviceGetComputeRunningProcesses = + (__typeof__(nvmlDeviceGetComputeRunningProcesses))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetComputeRunningProcesses"); + if (!nvmlDeviceGetComputeRunningProcesses) + goto init_error_clean_exit; + + // This one might not be available + nvmlDeviceGetProcessUtilization = + (__typeof__(nvmlDeviceGetProcessUtilization))dlsym( + libnvidia_ml_handle, "nvmlDeviceGetProcessUtilization"); + + last_nvml_return_status = nvmlInit(); + if (last_nvml_return_status != NVML_SUCCESS) { + return false; + } + local_error_string = NULL; + + return true; + +init_error_clean_exit: + dlclose(libnvidia_ml_handle); + libnvidia_ml_handle = NULL; + return false; +} + +void gpuinfo_nvidia_shutdown(void) { + if (libnvidia_ml_handle) { + nvmlShutdown(); + dlclose(libnvidia_ml_handle); + libnvidia_ml_handle = NULL; + local_error_string = didnt_call_gpuinfo_init; + } +} + +const char *gpuinfo_nvidia_last_error_string(void) { + if (local_error_string) { + return local_error_string; + } else if (libnvidia_ml_handle && nvmlErrorString) { + return nvmlErrorString(last_nvml_return_status); + } else { + return "An unanticipated error occurred while accessing NVIDIA GPU " + "information\n"; + } +} + +bool gpuinfo_nvidia_get_device_handles( + gpuinfo_nvidia_device_handle **handle_array_ptr, unsigned *count, + uint64_t mask) { + + if (!libnvidia_ml_handle) + return false; + + unsigned num_devices; + last_nvml_return_status = nvmlDeviceGetCount(&num_devices); + if (last_nvml_return_status != NVML_SUCCESS) + return false; + + *handle_array_ptr = malloc(num_devices * sizeof(**handle_array_ptr)); + if (!*handle_array_ptr) { + local_error_string = strerror(errno); + return false; + } + *count = 0; + for (unsigned int i = 0; i < num_devices; ++i) { + if (i < CHAR_BIT * sizeof(mask) && (mask & (1 << i)) == 0) + continue; + last_nvml_return_status = + nvmlDeviceGetHandleByIndex(i, &(*handle_array_ptr)[*count]); + if (last_nvml_return_status == NVML_SUCCESS) + *count += 1; + } + return true; +} + +void gpuinfo_nvidia_populate_static_info(gpuinfo_nvidia_device_handle device, + gpuinfo_static_info *static_info) { + last_nvml_return_status = + nvmlDeviceGetName(device, static_info->device_name, MAX_DEVICE_NAME); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_device_name_valid, static_info->valid); + else + RESET_VALID(gpuinfo_device_name_valid, static_info->valid); + + last_nvml_return_status = + nvmlDeviceGetMaxPcieLinkGeneration(device, &static_info->max_pcie_gen); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_max_pcie_gen_valid, static_info->valid); + else + RESET_VALID(gpuinfo_max_pcie_gen_valid, static_info->valid); + + last_nvml_return_status = + nvmlDeviceGetMaxPcieLinkWidth(device, &static_info->max_pcie_link_width); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_max_link_width_valid, static_info->valid); + else + RESET_VALID(gpuinfo_max_link_width_valid, static_info->valid); + + last_nvml_return_status = nvmlDeviceGetTemperatureThreshold( + device, NVML_TEMPERATURE_THRESHOLD_SHUTDOWN, + &static_info->temperature_shutdown_threshold); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_temperature_shutdown_valid, static_info->valid); + else + RESET_VALID(gpuinfo_temperature_shutdown_valid, static_info->valid); + + last_nvml_return_status = nvmlDeviceGetTemperatureThreshold( + device, NVML_TEMPERATURE_THRESHOLD_SLOWDOWN, + &static_info->temperature_slowdown_threshold); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_temperature_slowdown_valid, static_info->valid); + else + RESET_VALID(gpuinfo_temperature_slowdown_valid, static_info->valid); +} + +void gpuinfo_nvidia_refresh_dynamic_info(gpuinfo_nvidia_device_handle device, + gpuinfo_dynamic_info *dynamic_info) { + + bool graphics_clock_valid = false; + unsigned graphics_clock; + bool sm_clock_valid = false; + unsigned sm_clock; + nvmlClockType_t getMaxClockFrom = NVML_CLOCK_GRAPHICS; + + // GPU current speed + // Maximum between SM and Graphical + last_nvml_return_status = + nvmlDeviceGetClockInfo(device, NVML_CLOCK_GRAPHICS, &graphics_clock); + graphics_clock_valid = last_nvml_return_status == NVML_SUCCESS; + + last_nvml_return_status = + nvmlDeviceGetClockInfo(device, NVML_CLOCK_SM, &sm_clock); + sm_clock_valid = last_nvml_return_status == NVML_SUCCESS; + + if (graphics_clock_valid && sm_clock_valid && graphics_clock < sm_clock) { + getMaxClockFrom = NVML_CLOCK_SM; + } else if (!graphics_clock_valid && sm_clock_valid) { + getMaxClockFrom = NVML_CLOCK_SM; + } + + RESET_VALID(gpuinfo_curr_gpu_clock_speed_valid, dynamic_info->valid); + if (getMaxClockFrom == NVML_CLOCK_GRAPHICS && graphics_clock_valid) { + dynamic_info->gpu_clock_speed = graphics_clock; + SET_VALID(gpuinfo_curr_gpu_clock_speed_valid, dynamic_info->valid); + } + if (getMaxClockFrom == NVML_CLOCK_SM && sm_clock_valid) { + dynamic_info->gpu_clock_speed = sm_clock; + SET_VALID(gpuinfo_curr_gpu_clock_speed_valid, dynamic_info->valid); + } + + // GPU max speed + last_nvml_return_status = nvmlDeviceGetMaxClockInfo( + device, getMaxClockFrom, &dynamic_info->gpu_clock_speed_max); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_max_gpu_clock_speed_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_max_gpu_clock_speed_valid, dynamic_info->valid); + + // Memory current speed + last_nvml_return_status = nvmlDeviceGetClockInfo( + device, NVML_CLOCK_MEM, &dynamic_info->mem_clock_speed); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_curr_mem_clock_speed_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_curr_mem_clock_speed_valid, dynamic_info->valid); + + // Memory max speed + last_nvml_return_status = nvmlDeviceGetMaxClockInfo( + device, NVML_CLOCK_MEM, &dynamic_info->mem_clock_speed_max); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_max_mem_clock_speed_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_max_mem_clock_speed_valid, dynamic_info->valid); + + // CPU and Memory utilization rates + nvmlUtilization_t utilization_percentages; + last_nvml_return_status = + nvmlDeviceGetUtilizationRates(device, &utilization_percentages); + if (last_nvml_return_status == NVML_SUCCESS) { + dynamic_info->gpu_util_rate = utilization_percentages.gpu; + SET_VALID(gpuinfo_gpu_util_rate_valid, dynamic_info->valid); + } else { + RESET_VALID(gpuinfo_gpu_util_rate_valid, dynamic_info->valid); + } + + // Encoder utilization rate + unsigned ignored_period; + last_nvml_return_status = nvmlDeviceGetEncoderUtilization( + device, &dynamic_info->encoder_rate, &ignored_period); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_encoder_rate_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_encoder_rate_valid, dynamic_info->valid); + + // Decoder utilization rate + last_nvml_return_status = nvmlDeviceGetDecoderUtilization( + device, &dynamic_info->decoder_rate, &ignored_period); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_decoder_rate_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_decoder_rate_valid, dynamic_info->valid); + + // Device memory info (total,used,free) + nvmlMemory_t memory_info; + last_nvml_return_status = nvmlDeviceGetMemoryInfo(device, &memory_info); + if (last_nvml_return_status == NVML_SUCCESS) { + dynamic_info->total_memory = memory_info.total; + dynamic_info->used_memory = memory_info.used; + dynamic_info->free_memory = memory_info.free; + dynamic_info->mem_util_rate = memory_info.used * 100 / memory_info.total; + SET_VALID(gpuinfo_total_memory_valid, dynamic_info->valid); + SET_VALID(gpuinfo_used_memory_valid, dynamic_info->valid); + SET_VALID(gpuinfo_free_memory_valid, dynamic_info->valid); + SET_VALID(gpuinfo_mem_util_rate_valid, dynamic_info->valid); + } else { + RESET_VALID(gpuinfo_total_memory_valid, dynamic_info->valid); + RESET_VALID(gpuinfo_used_memory_valid, dynamic_info->valid); + RESET_VALID(gpuinfo_free_memory_valid, dynamic_info->valid); + RESET_VALID(gpuinfo_mem_util_rate_valid, dynamic_info->valid); + } + + // Pcie generation used by the device + last_nvml_return_status = nvmlDeviceGetCurrPcieLinkGeneration( + device, &dynamic_info->curr_pcie_link_gen); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_pcie_link_gen_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_pcie_link_gen_valid, dynamic_info->valid); + + // Pcie width used by the device + last_nvml_return_status = nvmlDeviceGetCurrPcieLinkWidth( + device, &dynamic_info->curr_pcie_link_width); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_pcie_link_width_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_pcie_link_width_valid, dynamic_info->valid); + + // Pcie reception throughput + last_nvml_return_status = nvmlDeviceGetPcieThroughput( + device, NVML_PCIE_UTIL_RX_BYTES, &dynamic_info->pcie_rx); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_pcie_rx_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_pcie_rx_valid, dynamic_info->valid); + + // Pcie transmission throughput + last_nvml_return_status = nvmlDeviceGetPcieThroughput( + device, NVML_PCIE_UTIL_TX_BYTES, &dynamic_info->pcie_tx); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_pcie_tx_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_pcie_tx_valid, dynamic_info->valid); + + // Fan speed + last_nvml_return_status = + nvmlDeviceGetFanSpeed(device, &dynamic_info->fan_speed); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_fan_speed_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_fan_speed_valid, dynamic_info->valid); + + // GPU temperature + last_nvml_return_status = nvmlDeviceGetTemperature( + device, NVML_TEMPERATURE_GPU, &dynamic_info->gpu_temp); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_gpu_temp_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_gpu_temp_valid, dynamic_info->valid); + + // Device power usage + last_nvml_return_status = + nvmlDeviceGetPowerUsage(device, &dynamic_info->power_draw); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_power_draw_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_power_draw_valid, dynamic_info->valid); + + // Maximum enforced power usage + last_nvml_return_status = + nvmlDeviceGetEnforcedPowerLimit(device, &dynamic_info->power_draw_max); + if (last_nvml_return_status == NVML_SUCCESS) + SET_VALID(gpuinfo_power_draw_max_valid, dynamic_info->valid); + else + RESET_VALID(gpuinfo_power_draw_max_valid, dynamic_info->valid); +} + +static void gpuinfo_nvidia_get_process_utilization( + gpuinfo_nvidia_device_handle device, gpuinfo_nvidia_internal_data *internal, + unsigned num_processes_recovered, + gpu_process processes[num_processes_recovered]) { + if (num_processes_recovered && nvmlDeviceGetProcessUtilization) { + unsigned samples_count = 0; + nvmlReturn_t retval = nvmlDeviceGetProcessUtilization( + device, NULL, &samples_count, internal->last_utilization_timestamp); + if (retval != NVML_ERROR_INSUFFICIENT_SIZE) + return; + nvmlProcessUtilizationSample_t *samples = + malloc(samples_count * sizeof(*samples)); + retval = nvmlDeviceGetProcessUtilization( + device, samples, &samples_count, internal->last_utilization_timestamp); + if (retval != NVML_SUCCESS) { + free(samples); + return; + } + unsigned long long newest_timestamp_candidate = + internal->last_utilization_timestamp; + for (unsigned i = 0; i < samples_count; ++i) { + bool process_matched = false; + for (unsigned j = 0; !process_matched && j < num_processes_recovered; + ++j) { + // Filter out samples due to inconsistency in the results returned by + // the function nvmlDeviceGetProcessUtilization (see bug #110 on + // Github). Check for a valid running process returned by + // nvmlDeviceGetComputeRunningProcesses or + // nvmlDeviceGetGraphicsRunningProcesses, filter out inconsistent + // utilization value greater than 100% and filter out timestamp results + // that are less recent than what we were asking for + if ((pid_t)samples[i].pid == processes[j].pid && + samples[i].smUtil <= 100 && samples[i].encUtil <= 100 && + samples[i].decUtil <= 100 && + samples[i].timeStamp > internal->last_utilization_timestamp) { + // Collect the largest valid timestamp for this device to filter out + // the samples during the next call to the function + // nvmlDeviceGetProcessUtilization + if (samples[i].timeStamp > newest_timestamp_candidate) + newest_timestamp_candidate = samples[i].timeStamp; + + processes[j].gpu_usage = samples[i].smUtil; + SET_VALID(gpuinfo_process_gpu_usage_valid, processes[j].valid); + processes[j].encode_usage = samples[i].encUtil; + SET_VALID(gpuinfo_process_gpu_encoder_valid, processes[j].valid); + processes[j].decode_usage = samples[i].decUtil; + SET_VALID(gpuinfo_process_gpu_decoder_valid, processes[j].valid); + process_matched = true; + } + } + } + internal->last_utilization_timestamp = newest_timestamp_candidate; + free(samples); + } +} + +#define DEFAULT_PROCESS_ARRAY_SIZE 64 + +void gpuinfo_nvidia_get_running_processes( + gpuinfo_nvidia_device_handle device, gpuinfo_nvidia_internal_data *internal, + unsigned *num_processes_recovered, gpu_process **processes_info) { + *num_processes_recovered = 0; + size_t array_size = DEFAULT_PROCESS_ARRAY_SIZE; + nvmlProcessInfo_t *retrieved_infos = + malloc(array_size * sizeof(*retrieved_infos)); + if (!retrieved_infos) { + perror("Could not allocate memory: "); + exit(EXIT_FAILURE); + } + unsigned graphical_count = 0, compute_count = 0, recovered_count; +retry_query_graphical: + recovered_count = array_size; + last_nvml_return_status = nvmlDeviceGetGraphicsRunningProcesses( + device, &recovered_count, retrieved_infos); + if (last_nvml_return_status == NVML_ERROR_INSUFFICIENT_SIZE) { + array_size += array_size; + retrieved_infos = + realloc(retrieved_infos, array_size * sizeof(*retrieved_infos)); + if (!retrieved_infos) { + perror("Could not re-allocate memory: "); + exit(EXIT_FAILURE); + } + goto retry_query_graphical; + } + if (last_nvml_return_status == NVML_SUCCESS) { + graphical_count = recovered_count; + } +retry_query_compute: + recovered_count = array_size - graphical_count; + last_nvml_return_status = nvmlDeviceGetComputeRunningProcesses( + device, &recovered_count, retrieved_infos + graphical_count); + if (last_nvml_return_status == NVML_ERROR_INSUFFICIENT_SIZE) { + array_size += array_size; + retrieved_infos = + realloc(retrieved_infos, array_size * sizeof(*retrieved_infos)); + if (!retrieved_infos) { + perror("Could not re-allocate memory: "); + exit(EXIT_FAILURE); + } + goto retry_query_compute; + } + if (last_nvml_return_status == NVML_SUCCESS) { + compute_count = recovered_count; + } + + *num_processes_recovered = graphical_count + compute_count; + if (*num_processes_recovered > 0) { + *processes_info = + malloc(*num_processes_recovered * sizeof(**processes_info)); + if (!*processes_info) { + perror("Could not allocate memory: "); + exit(EXIT_FAILURE); + } + for (unsigned i = 0; i < graphical_count + compute_count; ++i) { + if (i < graphical_count) + (*processes_info)[i].type = gpu_process_graphical; + else + (*processes_info)[i].type = gpu_process_compute; + (*processes_info)[i].pid = retrieved_infos[i].pid; + (*processes_info)[i].gpu_memory_usage = retrieved_infos[i].usedGpuMemory; + SET_VALID(gpuinfo_process_gpu_memory_usage_valid, + (*processes_info)[i].valid); + RESET_VALID(gpuinfo_process_cmdline_valid, (*processes_info)[i].valid); + RESET_VALID(gpuinfo_process_user_name_valid, (*processes_info)[i].valid); + RESET_VALID(gpuinfo_process_gpu_usage_valid, (*processes_info)[i].valid); + RESET_VALID(gpuinfo_process_gpu_encoder_valid, + (*processes_info)[i].valid); + RESET_VALID(gpuinfo_process_gpu_decoder_valid, + (*processes_info)[i].valid); + RESET_VALID(gpuinfo_process_gpu_memory_percentage_valid, + (*processes_info)[i].valid); + RESET_VALID(gpuinfo_process_cpu_usage_valid, (*processes_info)[i].valid); + RESET_VALID(gpuinfo_process_cpu_memory_virt_valid, + (*processes_info)[i].valid); + RESET_VALID(gpuinfo_process_cpu_memory_res_valid, + (*processes_info)[i].valid); + } + } else { + *processes_info = NULL; + } + free(retrieved_infos); + gpuinfo_nvidia_get_process_utilization( + device, internal, *num_processes_recovered, *processes_info); +} diff -Nru nvtop-1.1.0/src/get_process_info_linux.c nvtop-1.2.2/src/get_process_info_linux.c --- nvtop-1.1.0/src/get_process_info_linux.c 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/src/get_process_info_linux.c 2021-07-24 13:07:26.000000000 +0000 @@ -1,6 +1,6 @@ /* * - * Copyright (C) 2017 Maxime Schmitt + * Copyright (C) 2017-2021 Maxime Schmitt * * This file is part of Nvtop. * @@ -76,6 +76,7 @@ size_t size_buffer = command_line_increment; *buffer = malloc(size_buffer); char *current_buffer = *buffer; + current_buffer[0] = '\0'; size_t total_read = 0; do { @@ -190,7 +191,7 @@ if (!stat_file) { return false; } - nvtop_get_current_time(&usage->time); + nvtop_get_current_time(&usage->timestamp); unsigned long total_user_time; // in clock_ticks unsigned long total_kernel_time; // in clock_ticks unsigned long virtual_memory; // In bytes diff -Nru nvtop-1.1.0/src/ini.c nvtop-1.2.2/src/ini.c --- nvtop-1.1.0/src/ini.c 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/src/ini.c 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,298 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2020, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#if INI_CUSTOM_ALLOCATOR +#include +void* ini_malloc(size_t size); +void ini_free(void* ptr); +void* ini_realloc(void* ptr, size_t size); +#else +#include +#define ini_malloc malloc +#define ini_free free +#define ini_realloc realloc +#endif +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to NUL at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Similar to strncpy, but ensures dest (size bytes) is + NUL-terminated, and doesn't pad with NULs. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ + size_t i; + for (i = 0; i < size - 1 && src[i]; i++) + dest[i] = src[i]; + dest[i] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; + int max_line = INI_MAX_LINE; +#else + char* line; + size_t max_line = INI_INITIAL_ALLOC; +#endif +#if INI_ALLOW_REALLOC && !INI_USE_STACK + char* new_line; + size_t offset; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)ini_malloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, (int)max_line, stream) != NULL) { +#if INI_ALLOW_REALLOC && !INI_USE_STACK + offset = strlen(line); + while (offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) + max_line = INI_MAX_LINE; + new_line = ini_realloc(line, max_line); + if (!new_line) { + ini_free(line); + return -2; + } + line = new_line; + if (reader(line + offset, (int)(max_line - offset), stream) == NULL) + break; + if (max_line >= INI_MAX_LINE) + break; + offset += strlen(line + offset); + } +#endif + + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (strchr(INI_START_COMMENT_PREFIXES, *start)) { + /* Start-of-line comment */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; +#if INI_CALL_HANDLER_ON_NEW_SECTION + if (!HANDLER(user, section, NULL, NULL) && !error) + error = lineno; +#endif + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ +#if INI_ALLOW_NO_VALUE + *end = '\0'; + name = rstrip(start); + if (!HANDLER(user, section, name, NULL) && !error) + error = lineno; +#else + error = lineno; +#endif + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + ini_free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; + } + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = strlen(string); + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); +} diff -Nru nvtop-1.1.0/src/interface.c nvtop-1.2.2/src/interface.c --- nvtop-1.1.0/src/interface.c 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/src/interface.c 2021-07-24 13:07:26.000000000 +0000 @@ -1,6 +1,6 @@ /* * - * Copyright (C) 2017-2018 Maxime Schmitt + * Copyright (C) 2017-2021 Maxime Schmitt * * This file is part of Nvtop. * @@ -20,7 +20,14 @@ */ #include "nvtop/interface.h" +#include "nvtop/extract_gpuinfo.h" +#include "nvtop/extract_gpuinfo_common.h" +#include "nvtop/interface_common.h" +#include "nvtop/interface_internal_common.h" #include "nvtop/interface_layout_selection.h" +#include "nvtop/interface_options.h" +#include "nvtop/interface_ring_buffer.h" +#include "nvtop/interface_setup_win.h" #include "nvtop/plot.h" #include "nvtop/time.h" @@ -33,153 +40,23 @@ #include #include -#define max(a, b) ((a) > (b) ? (a) : (b)) -#define min(a, b) ((a) < (b) ? (a) : (b)) -#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) - -enum process_field { - process_pid = 0, - process_user, - process_gpu_id, - process_type, - process_memory, - process_cpu_usage, - process_cpu_mem_usage, - process_command, - process_end, -}; - -enum nvtop_option_window_state { - nvtop_option_state_hidden, - nvtop_option_state_kill, - nvtop_option_state_sort_by, -}; - -enum interface_color { - cyan_color = 1, - yellow_color, - magenta_color, - green_color, - red_color, - blue_color, -}; - -struct device_window { - char *device_name; - WINDOW *name_win; // Name of the GPU - WINDOW *gpu_util_enc_dec; - WINDOW *gpu_util_no_enc_or_dec; - WINDOW *gpu_util_no_enc_and_dec; - WINDOW *mem_util_enc_dec; - WINDOW *mem_util_no_enc_or_dec; - WINDOW *mem_util_no_enc_and_dec; - WINDOW *encode_util; - WINDOW *decode_util; - WINDOW *fan_speed; - WINDOW *temperature; - WINDOW *power_info; - WINDOW *gpu_clock_info; - WINDOW *mem_clock_info; - WINDOW *pcie_info; - nvtop_time last_decode_seen; - nvtop_time last_encode_seen; -}; - -struct all_gpu_processes { - intmax_t pid; - char *process_name; - char *user_name; - unsigned long long used_memory; - double mem_percentage; - double cpu_percent; - size_t cpu_memory; - unsigned int gpu_id; - bool is_graphical; -}; - -static const unsigned int option_window_size = 13; -struct option_window { - enum nvtop_option_window_state state; - unsigned int selected_row; - unsigned int offset; - WINDOW *option_win; - WINDOW *option_selection_window; -}; - -struct process_window { - unsigned int size_biggest_name; - unsigned int size_processes_buffer; - unsigned int num_processes; - unsigned int offset; - unsigned int offset_column; - struct all_gpu_processes *all_process; - WINDOW *process_win; - WINDOW *process_with_option_win; - unsigned int selected_row; - enum process_field sort_criterion; - bool sort_asc; - struct option_window option_window; -}; - -struct plot_window { - enum plot_type type; - /*nvtop_time max_data_retain_time;*/ - /*nvtop_time time_between_data_collection;*/ - /*nvtop_time last_time_collected;*/ - size_t num_data; - double *data; - WINDOW *win; - WINDOW *plot_window; - size_t gpu_ids[2]; -}; - -// Keep gpu information every 1 second for 10 minutes -struct retained_data { - unsigned collect_interval; - size_t size_data_buffer; - size_t num_collected_data; - unsigned *gpu_util; - unsigned *mem_util; - nvtop_time last_collect; -}; - -struct nvtop_interface { - size_t num_devices; - struct device_window *devices_win; - struct process_window process; - unsigned num_plots; - struct plot_window *plots; - bool use_fahrenheit; - bool show_plot; - bool plot_old_left_recent_right; - double encode_decode_hide_time; - struct retained_data past_data; -}; - -enum device_field { - device_name = 0, - device_fan_speed, - device_temperature, - device_power, - device_pcie, - device_clock, - device_count, -}; - -static unsigned int sizeof_device_field[] = { +static unsigned int sizeof_device_field[device_field_count] = { [device_name] = 11, [device_fan_speed] = 8, [device_temperature] = 10, [device_power] = 15, [device_clock] = 11, [device_pcie] = 46, }; -static unsigned int sizeof_process_field[] = { - [process_pid] = 7, [process_user] = 4, [process_gpu_id] = 3, - [process_type] = 7, +static unsigned int sizeof_process_field[process_field_count] = { + [process_pid] = 7, [process_user] = 4, + [process_gpu_id] = 3, [process_type] = 7, + [process_gpu_rate] = 4, [process_enc_rate] = 4, + [process_dec_rate] = 4, [process_memory] = 14, // 9 for mem 5 for % - [process_cpu_usage] = 6, [process_cpu_mem_usage] = 9, [process_command] = 0, + [process_cpu_usage] = 6, [process_cpu_mem_usage] = 9, + [process_command] = 0, }; -static void alloc_device_window(unsigned int device_id, unsigned int start_row, - unsigned int start_col, unsigned int totalcol, +static void alloc_device_window(unsigned int start_row, unsigned int start_col, + unsigned int totalcol, struct device_window *dwin) { const unsigned int spacer = 1; @@ -190,13 +67,6 @@ newwin(1, sizeof_device_field[device_name], start_row, start_col); if (dwin->name_win == NULL) goto alloc_error; - if (dwin->device_name != NULL) { - wattron(dwin->name_win, COLOR_PAIR(cyan_color)); - mvwprintw(dwin->name_win, 0, 0, "Device %-2u", device_id); - wattroff(dwin->name_win, COLOR_PAIR(cyan_color)); - wprintw(dwin->name_win, "[%s]", dwin->device_name); - wnoutrefresh(dwin->name_win); - } dwin->pcie_info = newwin(1, sizeof_device_field[device_pcie], start_row, start_col + spacer + sizeof_device_field[device_name]); @@ -303,6 +173,8 @@ start_col + spacer + size_gpu + size_encode + 1); if (dwin->mem_util_no_enc_and_dec == NULL) goto alloc_error; + dwin->enc_was_visible = false; + dwin->dec_was_visible = false; return; alloc_error: @@ -332,20 +204,26 @@ static void alloc_process_with_option(struct nvtop_interface *interface, unsigned posX, unsigned posY, unsigned sizeX, unsigned sizeY) { - if (sizeY > 0) { - - interface->process.size_processes_buffer = sizeY > 50 ? sizeY : 50; - interface->process.process_win = newwin(sizeY, sizeX, posY, posX); interface->process.process_with_option_win = newwin( sizeY, sizeX - option_window_size, posY, posX + option_window_size); - } else { - interface->process.option_window.state = nvtop_option_state_hidden; interface->process.process_win = NULL; interface->process.process_with_option_win = NULL; } + interface->process.selected_row = 0; + interface->process.selected_pid = -1; + interface->process.offset_column = 0; + interface->process.offset = 0; + + interface->process.option_window.option_win = + newwin(sizeY, option_window_size, posY, posX); + + interface->process.option_window.state = nvtop_option_state_hidden; + interface->process.option_window.previous_state = nvtop_option_state_sort_by; + interface->process.option_window.offset = 0; + interface->process.option_window.selected_row = 0; } static void initialize_gpu_mem_plot(struct plot_window *plot, @@ -354,56 +232,36 @@ unsigned cols = position->sizeX; cols -= 5; rows -= 2; - cols = cols / 2 * 2; plot->plot_window = newwin(rows, cols, position->posY + 1, position->posX + 4); draw_rectangle(plot->win, 3, 0, cols + 2, rows + 2); - mvwprintw(plot->win, 1 + rows * 3 / 4, 0, "25%%"); - mvwprintw(plot->win, 1 + rows / 4, 0, "75%%"); - mvwprintw(plot->win, 1 + rows / 2, 0, "50%%"); + mvwprintw(plot->win, 1 + rows * 3 / 4, 0, " 25"); + mvwprintw(plot->win, 1 + rows / 4, 0, " 75"); + mvwprintw(plot->win, 1 + rows / 2, 0, " 50"); mvwprintw(plot->win, 1, 0, "100"); - mvwprintw(plot->win, rows, 0, " 0%%"); + mvwprintw(plot->win, rows, 0, " 0"); plot->data = calloc(cols, sizeof(*plot->data)); plot->num_data = cols; + wnoutrefresh(plot->win); } -static void alloc_plot_window(struct nvtop_interface *interface, +static void alloc_plot_window(unsigned devices_count, struct window_position *plot_positions, - enum plot_type plot_type) { - if (!plot_positions) { + unsigned map_device_to_plot[devices_count], + struct nvtop_interface *interface) { + if (!interface->num_plots) { interface->plots = NULL; return; } interface->plots = malloc(interface->num_plots * sizeof(*interface->plots)); - unsigned num_device_to_attribute; - unsigned max_device_per_plot; - switch (plot_type) { - case plot_gpu_solo: - num_device_to_attribute = interface->num_devices; - max_device_per_plot = 1; - break; - case plot_gpu_duo: - num_device_to_attribute = interface->num_devices; - max_device_per_plot = 2; - break; - case plot_gpu_max: - num_device_to_attribute = 1; - max_device_per_plot = 1; - break; - } - unsigned current_gpu_id = 0; for (size_t i = 0; i < interface->num_plots; ++i) { - interface->plots[i].gpu_ids[0] = current_gpu_id++; - if (num_device_to_attribute > 1 && max_device_per_plot > 1) { - interface->plots[i].type = plot_gpu_duo; - num_device_to_attribute -= 2; - interface->plots[i].gpu_ids[1] = current_gpu_id++; - } else { - if (plot_type == plot_gpu_max) - interface->plots[i].type = plot_gpu_max; - else - interface->plots[i].type = plot_gpu_solo; - num_device_to_attribute -= 1; + interface->plots[i].num_devices_to_plot = 0; + for (unsigned dev_id = 0; dev_id < devices_count; ++dev_id) { + if (map_device_to_plot[dev_id] == i) { + interface->plots[i] + .devices_ids[interface->plots[i].num_devices_to_plot] = dev_id; + interface->plots[i].num_devices_to_plot++; + } } interface->plots[i].win = newwin(plot_positions[i].sizeY, plot_positions[i].sizeX, @@ -426,70 +284,54 @@ int rows, cols; getmaxyx(stdscr, rows, cols); - unsigned int num_devices = dwin->num_devices; + unsigned int devices_count = dwin->devices_count; - struct window_position device_positions[num_devices]; + struct window_position device_positions[devices_count]; + unsigned map_device_to_plot[devices_count]; struct window_position process_position; - struct window_position *plot_positions = NULL; + struct window_position plot_positions[MAX_CHARTS]; + struct window_position setup_position; - enum plot_type plot_type; - compute_sizes_from_layout(dwin->show_plot, true, true, num_devices, 2, - 3, device_length(), rows - 1, cols, - device_positions, &process_position, - &dwin->num_plots, &plot_positions, &plot_type); + compute_sizes_from_layout(devices_count, 3, device_length(), rows - 1, cols, + dwin->options.device_information_drawn, + dwin->options.process_fields_displayed, + device_positions, &dwin->num_plots, plot_positions, + map_device_to_plot, &process_position, + &setup_position); - alloc_plot_window(dwin, plot_positions, plot_type); - free(plot_positions); + alloc_plot_window(devices_count, plot_positions, map_device_to_plot, dwin); - for (unsigned int i = 0; i < num_devices; ++i) { - alloc_device_window(i, device_positions[i].posY, device_positions[i].posX, + for (unsigned int i = 0; i < devices_count; ++i) { + alloc_device_window(device_positions[i].posY, device_positions[i].posX, device_positions[i].sizeX, &dwin->devices_win[i]); } alloc_process_with_option(dwin, process_position.posX, process_position.posY, process_position.sizeX, process_position.sizeY); - dwin->process.option_window.option_win = - newwin(process_position.sizeY, option_window_size, process_position.posY, - process_position.posX); - dwin->process.option_window.option_selection_window = - newwin(1, cols, rows - 1, 0); + dwin->shortcut_window = newwin(1, cols, rows - 1, 0); + + alloc_setup_window(&setup_position, &dwin->setup_win); } static void delete_all_windows(struct nvtop_interface *dwin) { - for (unsigned int i = 0; i < dwin->num_devices; ++i) { + for (unsigned int i = 0; i < dwin->devices_count; ++i) { free_device_windows(&dwin->devices_win[i]); } delwin(dwin->process.process_win); delwin(dwin->process.process_with_option_win); dwin->process.process_win = NULL; dwin->process.process_with_option_win = NULL; - delwin(dwin->process.option_window.option_selection_window); + delwin(dwin->shortcut_window); delwin(dwin->process.option_window.option_win); for (size_t i = 0; i < dwin->num_plots; ++i) { delwin(dwin->plots[i].win); free(dwin->plots[i].data); } + free_setup_window(&dwin->setup_win); free(dwin->plots); } -void show_gpu_infos_ascii(unsigned int num_devices, - struct device_info *dev_info) { - - for (unsigned int i = 0; i < num_devices; ++i) { - printf("GPU %u: %s @ (%uMHz,%uMHz)," - " Util. (%u%% , %u%%)," - " FAN %u%%," - " TEMP %u°c," - " POWER %uW / %uW\n", - i, dev_info[i].device_name, dev_info[i].gpu_clock_speed, - dev_info[i].mem_clock_speed, dev_info[i].gpu_util_rate, - dev_info[i].mem_util_rate, dev_info[i].fan_speed, - dev_info[i].gpu_temp, dev_info[i].power_draw / 1000, - dev_info[i].power_draw_max / 1000); - } -} - static void initialize_colors(void) { start_color(); short background_color; @@ -509,89 +351,67 @@ init_pair(magenta_color, COLOR_MAGENTA, background_color); } -struct nvtop_interface * -initialize_curses(unsigned int num_devices, unsigned int biggest_device_name, - bool use_color, bool use_fahrenheit, bool show_per_gpu_plot, - bool plot_old_left_recent_right, - double encode_decode_hide_time, unsigned collect_interval) { +struct nvtop_interface *initialize_curses(unsigned devices_count, + unsigned largest_device_name, + nvtop_interface_option options) { struct nvtop_interface *interface = calloc(1, sizeof(*interface)); - interface->devices_win = calloc(num_devices, sizeof(*interface->devices_win)); - interface->num_devices = num_devices; - sizeof_device_field[device_name] = biggest_device_name + 11; + interface->options = options; + interface->devices_win = + calloc(devices_count, sizeof(*interface->devices_win)); + interface->devices_count = devices_count; + sizeof_device_field[device_name] = largest_device_name + 11; initscr(); - if (use_color && has_colors() == TRUE) { + refresh(); + if (interface->options.use_color && has_colors() == TRUE) { initialize_colors(); } cbreak(); noecho(); keypad(stdscr, TRUE); curs_set(0); - interface->encode_decode_hide_time = - encode_decode_hide_time < 0. ? 1e20 : encode_decode_hide_time; - interface->show_plot = show_per_gpu_plot; - interface->use_fahrenheit = use_fahrenheit; - interface->process.offset = 0; - interface->process.offset_column = 0; - interface->process.option_window.offset = 0; - interface->process.option_window.state = nvtop_option_state_hidden; - interface->process.selected_row = 0; - interface->process.sort_criterion = process_memory; - interface->process.sort_asc = false; - interface->process.size_processes_buffer = 50; - interface->plot_old_left_recent_right = plot_old_left_recent_right; - interface->process.all_process = - malloc(interface->process.size_processes_buffer * - sizeof(*interface->process.all_process)); - // Hide decode and encode if not active for more than some given seconds - nvtop_time time_now, some_time_in_past; - if (encode_decode_hide_time > 0.) + + // Hide decode and encode if not active for some time + if (interface->options.encode_decode_hiding_timer > 0.) { + nvtop_time time_now, some_time_in_past; some_time_in_past = nvtop_hmns_to_time( - 0, (unsigned int)(encode_decode_hide_time / 60.) + 1, 0); - else - some_time_in_past = nvtop_hmns_to_time(0, 0, 0); - nvtop_get_current_time(&time_now); - some_time_in_past = nvtop_substract_time(time_now, some_time_in_past); - interface->past_data.size_data_buffer = 10 * 60 * 1000; - interface->past_data.num_collected_data = 0; - interface->past_data.collect_interval = - collect_interval > 1000 ? collect_interval : 1000; - nvtop_get_current_time(&interface->past_data.last_collect); - - for (size_t i = 0; i < num_devices; ++i) { - interface->devices_win[i].last_encode_seen = some_time_in_past; - interface->devices_win[i].last_decode_seen = some_time_in_past; - } - interface->past_data.gpu_util = malloc( - sizeof(unsigned[num_devices][interface->past_data.size_data_buffer])); - interface->past_data.mem_util = malloc( - sizeof(unsigned[num_devices][interface->past_data.size_data_buffer])); + 0, + (unsigned int)(interface->options.encode_decode_hiding_timer / 60.) + 1, + 0); + + nvtop_get_current_time(&time_now); + some_time_in_past = nvtop_substract_time(time_now, some_time_in_past); + for (size_t i = 0; i < devices_count; ++i) { + interface->devices_win[i].last_encode_seen = some_time_in_past; + interface->devices_win[i].last_decode_seen = some_time_in_past; + } + } + + interface_alloc_ring_buffer(devices_count, 4, 10 * 60 * 1000, + &interface->saved_data_ring); initialize_all_windows(interface); - refresh(); return interface; } void clean_ncurses(struct nvtop_interface *interface) { endwin(); delete_all_windows(interface); - for (unsigned int i = 0; i < interface->num_devices; ++i) - free(interface->devices_win[i].device_name); + free(interface->options.device_information_drawn); + free(interface->options.config_file_location); free(interface->devices_win); - free(interface->process.all_process); - free(interface->past_data.gpu_util); - free(interface->past_data.mem_util); + interface_free_ring_buffer(&interface->saved_data_ring); free(interface); } -static void draw_bare_percentage(WINDOW *win, const char *prelude, - unsigned int new_percentage, - const char inside_braces_right[1024]) { +static void draw_percentage_meter(WINDOW *win, const char *prelude, + unsigned int new_percentage, + const char inside_braces_right[1024]) { int rows, cols; getmaxyx(win, rows, cols); (void)rows; size_t size_prelude = strlen(prelude); - wattron(win, COLOR_PAIR(cyan_color)); + wcolor_set(win, cyan_color, NULL); mvwprintw(win, 0, 0, "%s", prelude); - wattroff(win, COLOR_PAIR(cyan_color)); + wstandend(win); waddch(win, '['); int curx, cury; curx = getcurx(win); @@ -619,21 +439,25 @@ temp_convert = temp; else temp_convert = (unsigned)(32 + nearbyint(temp * 1.8)); - mvwprintw(win, 0, 0, "TEMP %3u", temp_convert); - waddch(win, ACS_DEGREE); - if (celsius) - waddch(win, 'C'); - else - waddch(win, 'F'); + wcolor_set(win, cyan_color, NULL); + mvwprintw(win, 0, 0, "TEMP"); + if (temp >= temp_slowdown - 5) { if (temp >= temp_slowdown) - mvwchgat(win, 0, 5, 3, 0, red_color, NULL); + wcolor_set(win, red_color, NULL); else - mvwchgat(win, 0, 5, 3, 0, yellow_color, NULL); + wcolor_set(win, yellow_color, NULL); } else { - mvwchgat(win, 0, 5, 3, 0, green_color, NULL); + wcolor_set(win, green_color, NULL); } - mvwchgat(win, 0, 0, 4, 0, cyan_color, NULL); + wprintw(win, " %3u", temp_convert); + wstandend(win); + + waddch(win, ACS_DEGREE); + if (celsius) + waddch(win, 'C'); + else + waddch(win, 'F'); wnoutrefresh(win); } @@ -660,100 +484,106 @@ wnoutrefresh(w); } -static void draw_devices(struct device_info *dev_info, +static void draw_devices(unsigned devices_count, gpu_info *devices, struct nvtop_interface *interface) { - unsigned int num_devices = interface->num_devices; - for (unsigned int i = 0; i < num_devices; ++i) { + for (unsigned i = 0; i < devices_count; ++i) { struct device_window *dev = &interface->devices_win[i]; - struct device_info *dinfo = &dev_info[i]; - if (dev->device_name == NULL) { - size_t namelen = strlen(dinfo->device_name) + 1; - dev->device_name = malloc(namelen); - memcpy(dev->device_name, dinfo->device_name, namelen); - wattron(dev->name_win, COLOR_PAIR(cyan_color)); - mvwprintw(dev->name_win, 0, 0, "Device %-2u", i); - wattroff(dev->name_win, COLOR_PAIR(cyan_color)); - wprintw(dev->name_win, "[%s]", dev->device_name); + + wcolor_set(dev->name_win, cyan_color, NULL); + mvwprintw(dev->name_win, 0, 0, "Device %-2u", i); + wstandend(dev->name_win); + if (IS_VALID(gpuinfo_device_name_valid, devices[i].static_info.valid)) { + wprintw(dev->name_win, "[%s]", devices[i].static_info.device_name); + wnoutrefresh(dev->name_win); + } else { + wprintw(dev->name_win, "[N/A]"); wnoutrefresh(dev->name_win); } + char buff[1024]; nvtop_time tnow; nvtop_get_current_time(&tnow); - static bool has_encode_in_last_min = true; - if (IS_VALID(encoder_rate_valid, dinfo->valid)) { - if (dinfo->encoder_rate > 0) { - if (!has_encode_in_last_min) { - werase_and_wnoutrefresh(dev->gpu_util_no_enc_and_dec); - werase_and_wnoutrefresh(dev->mem_util_no_enc_and_dec); - werase_and_wnoutrefresh(dev->gpu_util_no_enc_or_dec); - werase_and_wnoutrefresh(dev->mem_util_no_enc_or_dec); - } - has_encode_in_last_min = true; + bool is_encode_displayed = true; + if (IS_VALID(gpuinfo_encoder_rate_valid, devices[i].dynamic_info.valid)) { + if (devices[i].dynamic_info.encoder_rate > 0) { + is_encode_displayed = true; dev->last_encode_seen = tnow; - } else { - if (nvtop_difftime(dev->last_encode_seen, tnow) > - interface->encode_decode_hide_time) { - if (has_encode_in_last_min) { - werase_and_wnoutrefresh(dev->gpu_util_enc_dec); - werase_and_wnoutrefresh(dev->mem_util_enc_dec); + if (!dev->enc_was_visible) { + dev->enc_was_visible = true; + if (!dev->dec_was_visible) { + werase_and_wnoutrefresh(dev->gpu_util_no_enc_and_dec); + werase_and_wnoutrefresh(dev->mem_util_no_enc_and_dec); + } else { werase_and_wnoutrefresh(dev->gpu_util_no_enc_or_dec); werase_and_wnoutrefresh(dev->mem_util_no_enc_or_dec); } - has_encode_in_last_min = false; + } + } else { + if (interface->options.encode_decode_hiding_timer > 0. && + nvtop_difftime(dev->last_encode_seen, tnow) > + interface->options.encode_decode_hiding_timer) { + is_encode_displayed = false; + if (dev->enc_was_visible) { + dev->enc_was_visible = false; + if (dev->dec_was_visible) { + werase_and_wnoutrefresh(dev->gpu_util_enc_dec); + werase_and_wnoutrefresh(dev->mem_util_enc_dec); + } else { + werase_and_wnoutrefresh(dev->gpu_util_no_enc_or_dec); + werase_and_wnoutrefresh(dev->mem_util_no_enc_or_dec); + } + } } } } else { - has_encode_in_last_min = false; - werase_and_wnoutrefresh(dev->gpu_util_no_enc_or_dec); - werase_and_wnoutrefresh(dev->mem_util_no_enc_or_dec); - werase_and_wnoutrefresh(dev->gpu_util_enc_dec); - werase_and_wnoutrefresh(dev->mem_util_enc_dec); - werase_and_wnoutrefresh(dev->gpu_util_no_enc_and_dec); - werase_and_wnoutrefresh(dev->mem_util_no_enc_and_dec); - } - static bool has_decode_in_last_min = true; - if (IS_VALID(decoder_rate_valid, dinfo->valid)) { - if (dinfo->decoder_rate > 0) { - if (!has_decode_in_last_min) { - werase_and_wnoutrefresh(dev->gpu_util_no_enc_and_dec); - werase_and_wnoutrefresh(dev->mem_util_no_enc_and_dec); - werase_and_wnoutrefresh(dev->gpu_util_no_enc_or_dec); - werase_and_wnoutrefresh(dev->mem_util_no_enc_or_dec); - } - has_decode_in_last_min = true; + is_encode_displayed = false; + } + bool is_decode_displayed = true; + if (IS_VALID(gpuinfo_decoder_rate_valid, devices[i].dynamic_info.valid)) { + if (devices[i].dynamic_info.decoder_rate > 0) { + is_decode_displayed = true; dev->last_decode_seen = tnow; - } else { - if (nvtop_difftime(dev->last_decode_seen, tnow) > - interface->encode_decode_hide_time) { - if (has_decode_in_last_min) { - werase_and_wnoutrefresh(dev->gpu_util_enc_dec); - werase_and_wnoutrefresh(dev->mem_util_enc_dec); + if (!dev->dec_was_visible) { + dev->dec_was_visible = true; + if (!dev->enc_was_visible) { + werase_and_wnoutrefresh(dev->gpu_util_no_enc_and_dec); + werase_and_wnoutrefresh(dev->mem_util_no_enc_and_dec); + } else { werase_and_wnoutrefresh(dev->gpu_util_no_enc_or_dec); werase_and_wnoutrefresh(dev->mem_util_no_enc_or_dec); } - has_decode_in_last_min = false; + } + } else { + if (interface->options.encode_decode_hiding_timer > 0. && + nvtop_difftime(dev->last_decode_seen, tnow) > + interface->options.encode_decode_hiding_timer) { + is_decode_displayed = false; + if (dev->dec_was_visible) { + dev->dec_was_visible = false; + if (dev->enc_was_visible) { + werase_and_wnoutrefresh(dev->gpu_util_enc_dec); + werase_and_wnoutrefresh(dev->mem_util_enc_dec); + } else { + werase_and_wnoutrefresh(dev->gpu_util_no_enc_or_dec); + werase_and_wnoutrefresh(dev->mem_util_no_enc_or_dec); + } + } } } } else { - has_decode_in_last_min = false; - werase_and_wnoutrefresh(dev->gpu_util_no_enc_or_dec); - werase_and_wnoutrefresh(dev->mem_util_no_enc_or_dec); - werase_and_wnoutrefresh(dev->gpu_util_enc_dec); - werase_and_wnoutrefresh(dev->mem_util_enc_dec); - werase_and_wnoutrefresh(dev->gpu_util_no_enc_and_dec); - werase_and_wnoutrefresh(dev->mem_util_no_enc_and_dec); + is_decode_displayed = false; } WINDOW *gpu_util_win; WINDOW *mem_util_win; WINDOW *encode_win = dev->encode_util; WINDOW *decode_win = dev->decode_util; - if (has_encode_in_last_min && has_decode_in_last_min) { + if (is_encode_displayed && is_decode_displayed) { gpu_util_win = dev->gpu_util_enc_dec; mem_util_win = dev->mem_util_enc_dec; } else { - if (has_encode_in_last_min || has_decode_in_last_min) { - if (has_encode_in_last_min) + if (is_encode_displayed || is_decode_displayed) { + if (is_encode_displayed) encode_win = dev->decode_util; gpu_util_win = dev->gpu_util_no_enc_or_dec; mem_util_win = dev->mem_util_no_enc_or_dec; @@ -762,54 +592,60 @@ mem_util_win = dev->mem_util_no_enc_and_dec; } } - if (has_encode_in_last_min) { - if (IS_VALID(encoder_rate_valid, dinfo->valid)) { - snprintf(buff, 1024, "%u%%", dinfo->encoder_rate); - draw_bare_percentage(encode_win, "ENC", dinfo->encoder_rate, buff); + if (is_encode_displayed) { + if (IS_VALID(gpuinfo_encoder_rate_valid, devices[i].dynamic_info.valid)) { + snprintf(buff, 1024, "%u%%", devices[i].dynamic_info.encoder_rate); + draw_percentage_meter(encode_win, "ENC", + devices[i].dynamic_info.encoder_rate, buff); } } - if (has_decode_in_last_min) { - if (IS_VALID(decoder_rate_valid, dinfo->valid)) { - snprintf(buff, 1024, "%u%%", dinfo->decoder_rate); - draw_bare_percentage(decode_win, "DEC", dinfo->decoder_rate, buff); + if (is_decode_displayed) { + if (IS_VALID(gpuinfo_decoder_rate_valid, devices[i].dynamic_info.valid)) { + snprintf(buff, 1024, "%u%%", devices[i].dynamic_info.decoder_rate); + draw_percentage_meter(decode_win, "DEC", + devices[i].dynamic_info.decoder_rate, buff); } } - if (IS_VALID(gpu_util_rate_valid, dinfo->valid)) { - snprintf(buff, 1024, "%u%%", dinfo->gpu_util_rate); - draw_bare_percentage(gpu_util_win, "GPU", dinfo->gpu_util_rate, buff); + if (IS_VALID(gpuinfo_gpu_util_rate_valid, devices[i].dynamic_info.valid)) { + snprintf(buff, 1024, "%u%%", devices[i].dynamic_info.gpu_util_rate); + draw_percentage_meter(gpu_util_win, "GPU", + devices[i].dynamic_info.gpu_util_rate, buff); } else { snprintf(buff, 1024, "N/A"); - draw_bare_percentage(gpu_util_win, "GPU", 0, buff); + draw_percentage_meter(gpu_util_win, "GPU", 0, buff); } - if (IS_VALID(total_memory_valid, dinfo->valid) && - IS_VALID(used_memory_valid, dinfo->valid)) { - double total_mem = dinfo->total_memory; - double used_mem = dinfo->used_memory; + if (IS_VALID(gpuinfo_total_memory_valid, devices[i].dynamic_info.valid) && + IS_VALID(gpuinfo_used_memory_valid, devices[i].dynamic_info.valid)) { + double total_mem = devices[i].dynamic_info.total_memory; + double used_mem = devices[i].dynamic_info.used_memory; + double total_prefixed = total_mem, used_prefixed = used_mem; size_t prefix_off; - for (prefix_off = 0; prefix_off < 5 && total_mem >= 1000; ++prefix_off) { - total_mem /= 1024; - used_mem /= 1024; + for (prefix_off = 0; prefix_off < 5 && total_prefixed >= 1000.; + ++prefix_off) { + total_prefixed /= 1024.; + used_prefixed /= 1024.; } - snprintf(buff, 1024, "%.3f%s/%.3f%s", used_mem, memory_prefix[prefix_off], - total_mem, memory_prefix[prefix_off]); - draw_bare_percentage(mem_util_win, "MEM", - (unsigned int)(100. * dinfo->used_memory / - (double)dinfo->total_memory), - buff); + snprintf(buff, 1024, "%.3f%s/%.3f%s", used_prefixed, + memory_prefix[prefix_off], total_prefixed, + memory_prefix[prefix_off]); + draw_percentage_meter(mem_util_win, "MEM", + (unsigned int)(100. * used_mem / total_mem), buff); } else { snprintf(buff, 1024, "N/A"); - draw_bare_percentage(mem_util_win, "MEM", 0, buff); + draw_percentage_meter(mem_util_win, "MEM", 0, buff); } - if (IS_VALID(gpu_temp_valid, dinfo->valid)) { - if (!IS_VALID(gpu_temp_slowdown_valid, dinfo->valid)) - dinfo->gpu_temp_slowdown = 0; - draw_temp_color(dev->temperature, dinfo->gpu_temp, - dinfo->gpu_temp_slowdown, !interface->use_fahrenheit); + if (IS_VALID(gpuinfo_gpu_temp_valid, devices[i].dynamic_info.valid)) { + if (!IS_VALID(gpuinfo_temperature_slowdown_valid, + devices[i].static_info.valid)) + devices[i].static_info.temperature_slowdown_threshold = 0; + draw_temp_color(dev->temperature, devices[i].dynamic_info.gpu_temp, + devices[i].static_info.temperature_slowdown_threshold, + !interface->options.temperature_in_fahrenheit); } else { mvwprintw(dev->temperature, 0, 0, "TEMP N/A"); waddch(dev->temperature, ACS_DEGREE); - if (interface->use_fahrenheit) + if (interface->options.temperature_in_fahrenheit) waddch(dev->temperature, 'F'); else waddch(dev->temperature, 'C'); @@ -817,8 +653,9 @@ } // FAN - if (IS_VALID(fan_speed_valid, dinfo->valid)) - mvwprintw(dev->fan_speed, 0, 0, "FAN %3u%%", dinfo->fan_speed); + if (IS_VALID(gpuinfo_fan_speed_valid, devices[i].dynamic_info.valid)) + mvwprintw(dev->fan_speed, 0, 0, "FAN %3u%%", + devices[i].dynamic_info.fan_speed); else mvwprintw(dev->fan_speed, 0, 0, "FAN N/A%%"); mvwchgat(dev->fan_speed, 0, 0, 3, 0, cyan_color, NULL); @@ -826,8 +663,10 @@ // GPU CLOCK werase(dev->gpu_clock_info); - if (IS_VALID(gpu_clock_speed_valid, dinfo->valid)) - mvwprintw(dev->gpu_clock_info, 0, 0, "GPU %uMHz", dinfo->gpu_clock_speed); + if (IS_VALID(gpuinfo_curr_gpu_clock_speed_valid, + devices[i].dynamic_info.valid)) + mvwprintw(dev->gpu_clock_info, 0, 0, "GPU %uMHz", + devices[i].dynamic_info.gpu_clock_speed); else mvwprintw(dev->gpu_clock_info, 0, 0, "GPU N/A MHz"); @@ -836,8 +675,10 @@ // MEM CLOCK werase(dev->mem_clock_info); - if (IS_VALID(mem_clock_speed_valid, dinfo->valid)) - mvwprintw(dev->mem_clock_info, 0, 0, "MEM %uMHz", dinfo->mem_clock_speed); + if (IS_VALID(gpuinfo_curr_mem_clock_speed_valid, + devices[i].dynamic_info.valid)) + mvwprintw(dev->mem_clock_info, 0, 0, "MEM %uMHz", + devices[i].dynamic_info.mem_clock_speed); else mvwprintw(dev->mem_clock_info, 0, 0, "MEM N/A MHz"); mvwchgat(dev->mem_clock_info, 0, 0, 3, 0, cyan_color, NULL); @@ -845,16 +686,23 @@ // POWER werase(dev->power_info); - if (IS_VALID(power_draw_valid, dinfo->valid) && - IS_VALID(power_draw_max_valid, dinfo->valid)) + if (IS_VALID(gpuinfo_power_draw_valid, devices[i].dynamic_info.valid) && + IS_VALID(gpuinfo_power_draw_max_valid, devices[i].dynamic_info.valid)) mvwprintw(dev->power_info, 0, 0, "POW %3u / %3u W", - dinfo->power_draw / 1000, dinfo->power_draw_max / 1000); - else if (IS_VALID(power_draw_valid, dinfo->valid) && - !IS_VALID(power_draw_max_valid, dinfo->valid)) - mvwprintw(dev->power_info, 0, 0, "POW %3u W", dinfo->power_draw / 1000); - else if (!IS_VALID(power_draw_valid, dinfo->valid) && - IS_VALID(power_draw_max_valid, dinfo->valid)) - mvwprintw(dev->power_info, 0, 0, "POW N/A / %3u W", dinfo->power_draw_max / 1000); + devices[i].dynamic_info.power_draw / 1000, + devices[i].dynamic_info.power_draw_max / 1000); + else if (IS_VALID(gpuinfo_power_draw_valid, + devices[i].dynamic_info.valid) && + !IS_VALID(gpuinfo_power_draw_max_valid, + devices[i].dynamic_info.valid)) + mvwprintw(dev->power_info, 0, 0, "POW %3u W", + devices[i].dynamic_info.power_draw / 1000); + else if (!IS_VALID(gpuinfo_power_draw_valid, + devices[i].dynamic_info.valid) && + IS_VALID(gpuinfo_power_draw_max_valid, + devices[i].dynamic_info.valid)) + mvwprintw(dev->power_info, 0, 0, "POW N/A / %3u W", + devices[i].dynamic_info.power_draw_max / 1000); else mvwprintw(dev->power_info, 0, 0, "POW N/A W"); mvwchgat(dev->power_info, 0, 0, 3, 0, cyan_color, NULL); @@ -862,31 +710,31 @@ // PICe throughput werase(dev->pcie_info); - wattron(dev->pcie_info, COLOR_PAIR(cyan_color)); + wcolor_set(dev->pcie_info, cyan_color, NULL); mvwprintw(dev->pcie_info, 0, 0, "PCIe "); - wattroff(dev->pcie_info, COLOR_PAIR(cyan_color)); - wattron(dev->pcie_info, COLOR_PAIR(magenta_color)); + wcolor_set(dev->pcie_info, magenta_color, NULL); wprintw(dev->pcie_info, "GEN "); - wattroff(dev->pcie_info, COLOR_PAIR(magenta_color)); - if (IS_VALID(cur_pcie_link_gen_valid, dinfo->valid) && - IS_VALID(cur_pcie_link_width_valid, dinfo->valid)) - wprintw(dev->pcie_info, "%u@%2ux", dinfo->cur_pcie_link_gen, - dinfo->cur_pcie_link_width); + wstandend(dev->pcie_info); + if (IS_VALID(gpuinfo_pcie_link_gen_valid, devices[i].dynamic_info.valid) && + IS_VALID(gpuinfo_pcie_link_width_valid, devices[i].dynamic_info.valid)) + wprintw(dev->pcie_info, "%u@%2ux", + devices[i].dynamic_info.curr_pcie_link_gen, + devices[i].dynamic_info.curr_pcie_link_width); else wprintw(dev->pcie_info, "N/A"); - wattron(dev->pcie_info, COLOR_PAIR(magenta_color)); + wcolor_set(dev->pcie_info, magenta_color, NULL); wprintw(dev->pcie_info, " RX: "); - wattroff(dev->pcie_info, COLOR_PAIR(magenta_color)); - if (IS_VALID(pcie_rx_valid, dinfo->valid)) - print_pcie_at_scale(dev->pcie_info, dinfo->pcie_rx); + wstandend(dev->pcie_info); + if (IS_VALID(gpuinfo_pcie_rx_valid, devices[i].dynamic_info.valid)) + print_pcie_at_scale(dev->pcie_info, devices[i].dynamic_info.pcie_rx); else wprintw(dev->pcie_info, "N/A"); - wattron(dev->pcie_info, COLOR_PAIR(magenta_color)); + wcolor_set(dev->pcie_info, magenta_color, NULL); wprintw(dev->pcie_info, " TX: "); - wattroff(dev->pcie_info, COLOR_PAIR(magenta_color)); - if (IS_VALID(pcie_tx_valid, dinfo->valid)) - print_pcie_at_scale(dev->pcie_info, dinfo->pcie_tx); + wstandend(dev->pcie_info); + if (IS_VALID(gpuinfo_pcie_tx_valid, devices[i].dynamic_info.valid)) + print_pcie_at_scale(dev->pcie_info, devices[i].dynamic_info.pcie_tx); else wprintw(dev->pcie_info, "N/A"); @@ -894,85 +742,50 @@ } } -static inline unsigned int max_val(unsigned int a, unsigned int b) { - return a > b ? a : b; -} - -static unsigned int -copy_processes_for_processing(unsigned int num_devices, - struct device_info *dev_info, - struct nvtop_interface *interface) { +typedef struct { + unsigned processes_count; + struct gpuid_and_process { + unsigned gpu_id; + gpu_process *process; + } * processes; +} all_processes; + +static all_processes all_processes_array(unsigned devices_count, + gpu_info *devices) { + + unsigned total_processes_count = 0; + for (unsigned i = 0; i < devices_count; ++i) { + total_processes_count += devices[i].processes_count; + } + all_processes merged_devices_processes; + merged_devices_processes.processes_count = total_processes_count; + if (total_processes_count) { + merged_devices_processes.processes = malloc( + total_processes_count * sizeof(*merged_devices_processes.processes)); + if (!merged_devices_processes.processes) { + perror("Cannot allocate memory: "); + exit(EXIT_FAILURE); + } + } else { + merged_devices_processes.processes = NULL; + } - unsigned int maximum_name_length = 0; - unsigned int total_processes = 0; - for (unsigned int i = 0; i < num_devices; ++i) { - total_processes += - dev_info[i].num_compute_procs + dev_info[i].num_graphical_procs; - } - while (total_processes > interface->process.size_processes_buffer) { - interface->process.size_processes_buffer *= 2; - interface->process.all_process = - realloc(interface->process.all_process, - interface->process.size_processes_buffer * - sizeof(*interface->process.all_process)); - } - unsigned int offset = 0; - for (unsigned int i = 0; i < num_devices; ++i) { - for (unsigned int j = 0; j < dev_info[i].num_compute_procs; ++j) { - interface->process.all_process[offset + j].is_graphical = false; - interface->process.all_process[offset + j].gpu_id = i; - interface->process.all_process[offset + j].pid = - dev_info[i].compute_procs[j].pid; - interface->process.all_process[offset + j].process_name = - dev_info[i].compute_procs[j].process_name; - interface->process.all_process[offset + j].user_name = - dev_info[i].compute_procs[j].user_name; - interface->process.all_process[offset + j].used_memory = - dev_info[i].compute_procs[j].used_memory; - maximum_name_length = max_val( - maximum_name_length, strlen(dev_info[i].compute_procs[j].user_name)); - interface->process.all_process[offset + j].mem_percentage = - interface->process.all_process[offset + j].used_memory * 100. / - (double)dev_info[i].total_memory; - interface->process.all_process[offset + j].cpu_percent = - dev_info[i].compute_procs[j].cpu_usage; - interface->process.all_process[offset + j].cpu_memory = - dev_info[i].compute_procs[j].cpu_memory_res; - } - offset += dev_info[i].num_compute_procs; - for (unsigned int j = 0; j < dev_info[i].num_graphical_procs; ++j) { - interface->process.all_process[offset + j].is_graphical = true; - interface->process.all_process[offset + j].gpu_id = i; - interface->process.all_process[offset + j].pid = - dev_info[i].graphic_procs[j].pid; - interface->process.all_process[offset + j].process_name = - dev_info[i].graphic_procs[j].process_name; - interface->process.all_process[offset + j].user_name = - dev_info[i].graphic_procs[j].user_name; - interface->process.all_process[offset + j].used_memory = - dev_info[i].graphic_procs[j].used_memory; - maximum_name_length = max_val( - maximum_name_length, strlen(dev_info[i].graphic_procs[j].user_name)); - interface->process.all_process[offset + j].mem_percentage = - interface->process.all_process[offset + j].used_memory * 100. / - (double)dev_info[i].total_memory; - interface->process.all_process[offset + j].cpu_percent = - dev_info[i].graphic_procs[j].cpu_usage; - interface->process.all_process[offset + j].cpu_memory = - dev_info[i].graphic_procs[j].cpu_memory_res; + size_t offset = 0; + for (unsigned int i = 0; i < devices_count; ++i) { + for (unsigned int j = 0; j < devices[i].processes_count; ++j) { + merged_devices_processes.processes[offset].gpu_id = i; + merged_devices_processes.processes[offset++].process = + &devices[i].processes[j]; } - offset += dev_info[i].num_graphical_procs; } - interface->process.size_biggest_name = - max_val(interface->process.size_biggest_name, maximum_name_length); - return total_processes; + return merged_devices_processes; } static int compare_pid_desc(const void *pp1, const void *pp2) { - const struct all_gpu_processes *p1 = (const struct all_gpu_processes *)pp1; - const struct all_gpu_processes *p2 = (const struct all_gpu_processes *)pp2; - return -p1->pid + p2->pid; + const struct gpuid_and_process *p1 = (const struct gpuid_and_process *)pp1; + const struct gpuid_and_process *p2 = (const struct gpuid_and_process *)pp2; + return p1->process->pid >= p2->process->pid ? -1 : 1; } static int compare_pid_asc(const void *pp1, const void *pp2) { @@ -980,9 +793,13 @@ } static int compare_username_desc(const void *pp1, const void *pp2) { - const struct all_gpu_processes *p1 = (const struct all_gpu_processes *)pp1; - const struct all_gpu_processes *p2 = (const struct all_gpu_processes *)pp2; - return -strcmp(p1->user_name, p2->user_name); + const struct gpuid_and_process *p1 = (const struct gpuid_and_process *)pp1; + const struct gpuid_and_process *p2 = (const struct gpuid_and_process *)pp2; + if (IS_VALID(gpuinfo_process_user_name_valid, p1->process->valid) && + IS_VALID(gpuinfo_process_user_name_valid, p2->process->valid)) + return -strcmp(p1->process->user_name, p2->process->user_name); + else + return 0; } static int compare_username_asc(const void *pp1, const void *pp2) { @@ -990,9 +807,13 @@ } static int compare_process_name_desc(const void *pp1, const void *pp2) { - const struct all_gpu_processes *p1 = (const struct all_gpu_processes *)pp1; - const struct all_gpu_processes *p2 = (const struct all_gpu_processes *)pp2; - return -strncmp(p1->process_name, p2->process_name, sizeof(p1->user_name)); + const struct gpuid_and_process *p1 = (const struct gpuid_and_process *)pp1; + const struct gpuid_and_process *p2 = (const struct gpuid_and_process *)pp2; + if (IS_VALID(gpuinfo_process_cmdline_valid, p1->process->valid) && + IS_VALID(gpuinfo_process_cmdline_valid, p2->process->valid)) + return -strcmp(p1->process->cmdline, p2->process->cmdline); + else + return 0; } static int compare_process_name_asc(const void *pp1, const void *pp2) { @@ -1000,9 +821,14 @@ } static int compare_mem_usage_desc(const void *pp1, const void *pp2) { - const struct all_gpu_processes *p1 = (const struct all_gpu_processes *)pp1; - const struct all_gpu_processes *p2 = (const struct all_gpu_processes *)pp2; - return -p1->used_memory + p2->used_memory; + const struct gpuid_and_process *p1 = (const struct gpuid_and_process *)pp1; + const struct gpuid_and_process *p2 = (const struct gpuid_and_process *)pp2; + if (IS_VALID(gpuinfo_process_gpu_memory_usage_valid, p1->process->valid) && + IS_VALID(gpuinfo_process_gpu_memory_usage_valid, p2->process->valid)) + return p1->process->gpu_memory_usage >= p2->process->gpu_memory_usage ? -1 + : 1; + else + return 0; } static int compare_mem_usage_asc(const void *pp1, const void *pp2) { @@ -1010,9 +836,13 @@ } static int compare_cpu_usage_desc(const void *pp1, const void *pp2) { - const struct all_gpu_processes *p1 = (const struct all_gpu_processes *)pp1; - const struct all_gpu_processes *p2 = (const struct all_gpu_processes *)pp2; - return p1->cpu_percent >= p2->cpu_percent ? -1 : 1; + const struct gpuid_and_process *p1 = (const struct gpuid_and_process *)pp1; + const struct gpuid_and_process *p2 = (const struct gpuid_and_process *)pp2; + if (IS_VALID(gpuinfo_process_cpu_usage_valid, p1->process->valid) && + IS_VALID(gpuinfo_process_cpu_usage_valid, p2->process->valid)) + return p1->process->cpu_usage >= p2->process->cpu_usage ? -1 : 1; + else + return 0; } static int compare_cpu_usage_asc(const void *pp1, const void *pp2) { @@ -1020,9 +850,13 @@ } static int compare_cpu_mem_usage_desc(const void *pp1, const void *pp2) { - const struct all_gpu_processes *p1 = (const struct all_gpu_processes *)pp1; - const struct all_gpu_processes *p2 = (const struct all_gpu_processes *)pp2; - return -(int)p1->cpu_memory + (int)p2->cpu_memory; + const struct gpuid_and_process *p1 = (const struct gpuid_and_process *)pp1; + const struct gpuid_and_process *p2 = (const struct gpuid_and_process *)pp2; + if (IS_VALID(gpuinfo_process_cpu_memory_res_valid, p1->process->valid) && + IS_VALID(gpuinfo_process_cpu_memory_res_valid, p2->process->valid)) + return p1->process->cpu_memory_res >= p2->process->cpu_memory_res ? -1 : 1; + else + return 0; } static int compare_cpu_mem_usage_asc(const void *pp1, const void *pp2) { @@ -1030,9 +864,9 @@ } static int compare_gpu_desc(const void *pp1, const void *pp2) { - const struct all_gpu_processes *p1 = (const struct all_gpu_processes *)pp1; - const struct all_gpu_processes *p2 = (const struct all_gpu_processes *)pp2; - return -p1->gpu_id + p2->gpu_id; + const struct gpuid_and_process *p1 = (const struct gpuid_and_process *)pp1; + const struct gpuid_and_process *p2 = (const struct gpuid_and_process *)pp2; + return p1->gpu_id >= p2->gpu_id ? -1 : 1; } static int compare_gpu_asc(const void *pp1, const void *pp2) { @@ -1040,18 +874,84 @@ } static int compare_process_type_desc(const void *pp1, const void *pp2) { - const struct all_gpu_processes *p1 = (const struct all_gpu_processes *)pp1; - const struct all_gpu_processes *p2 = (const struct all_gpu_processes *)pp2; - return p1->is_graphical != p2->is_graphical; + const struct gpuid_and_process *p1 = (const struct gpuid_and_process *)pp1; + const struct gpuid_and_process *p2 = (const struct gpuid_and_process *)pp2; + return (p1->process->type == gpu_process_graphical) != + (p2->process->type == gpu_process_graphical); } static int compare_process_type_asc(const void *pp1, const void *pp2) { return -compare_process_name_desc(pp1, pp2); } -static void sort_process(struct all_gpu_processes *proc, - unsigned int total_process, - enum process_field criterion, bool asc_sort) { +static int compare_process_gpu_rate_desc(const void *pp1, const void *pp2) { + const struct gpuid_and_process *p1 = (const struct gpuid_and_process *)pp1; + const struct gpuid_and_process *p2 = (const struct gpuid_and_process *)pp2; + if (IS_VALID(gpuinfo_process_gpu_usage_valid, p1->process->valid) && + IS_VALID(gpuinfo_process_gpu_usage_valid, p2->process->valid)) { + return p1->process->gpu_usage > p2->process->gpu_usage ? -1 : 1; + } else { + if (IS_VALID(gpuinfo_process_gpu_usage_valid, p1->process->valid)) { + return p1->process->gpu_usage > 0 ? -1 : 0; + } else if (IS_VALID(gpuinfo_process_gpu_usage_valid, p2->process->valid)) { + return p2->process->gpu_usage > 0 ? 1 : 0; + } else { + return 0; + } + } +} + +static int compare_process_gpu_rate_asc(const void *pp1, const void *pp2) { + return -compare_process_gpu_rate_desc(pp1, pp2); +} + +static int compare_process_enc_rate_desc(const void *pp1, const void *pp2) { + const struct gpuid_and_process *p1 = (const struct gpuid_and_process *)pp1; + const struct gpuid_and_process *p2 = (const struct gpuid_and_process *)pp2; + if (IS_VALID(gpuinfo_process_gpu_encoder_valid, p1->process->valid) && + IS_VALID(gpuinfo_process_gpu_encoder_valid, p2->process->valid)) { + return p1->process->encode_usage >= p2->process->encode_usage ? -1 : 1; + } else { + if (IS_VALID(gpuinfo_process_gpu_encoder_valid, p1->process->valid)) { + return p1->process->encode_usage > 0 ? -1 : 0; + } else if (IS_VALID(gpuinfo_process_gpu_encoder_valid, + p2->process->valid)) { + return p2->process->encode_usage > 0 ? 1 : 0; + } else { + return 0; + } + } +} + +static int compare_process_enc_rate_asc(const void *pp1, const void *pp2) { + return -compare_process_enc_rate_desc(pp1, pp2); +} + +static int compare_process_dec_rate_desc(const void *pp1, const void *pp2) { + const struct gpuid_and_process *p1 = (const struct gpuid_and_process *)pp1; + const struct gpuid_and_process *p2 = (const struct gpuid_and_process *)pp2; + if (IS_VALID(gpuinfo_process_gpu_decoder_valid, p1->process->valid) && + IS_VALID(gpuinfo_process_gpu_decoder_valid, p2->process->valid)) { + return p1->process->decode_usage >= p2->process->decode_usage ? -1 : 1; + } else { + if (IS_VALID(gpuinfo_process_gpu_decoder_valid, p1->process->valid)) { + return p1->process->decode_usage > 0 ? -1 : 0; + } else if (IS_VALID(gpuinfo_process_gpu_decoder_valid, + p2->process->valid)) { + return p2->process->decode_usage > 0 ? 1 : 0; + } else { + return 0; + } + } +} +static int compare_process_dec_rate_asc(const void *pp1, const void *pp2) { + return -compare_process_dec_rate_desc(pp1, pp2); +} + +static void sort_process(all_processes all_procs, enum process_field criterion, + bool asc_sort) { + if (all_procs.processes_count == 0 || !all_procs.processes) + return; int (*sort_fun)(const void *, const void *); switch (criterion) { case process_pid: @@ -1102,21 +1002,44 @@ else sort_fun = compare_cpu_mem_usage_desc; break; - case process_end: - default: + case process_gpu_rate: + fprintf(stderr, "Comparing gpu rates\n"); + if (asc_sort) + sort_fun = compare_process_gpu_rate_asc; + else + sort_fun = compare_process_gpu_rate_desc; + break; + case process_enc_rate: + if (asc_sort) + sort_fun = compare_process_enc_rate_asc; + else + sort_fun = compare_process_enc_rate_desc; + break; + case process_dec_rate: + if (asc_sort) + sort_fun = compare_process_dec_rate_asc; + else + sort_fun = compare_process_dec_rate_desc; + break; + case process_field_count: return; } - qsort(proc, total_process, sizeof(*proc), sort_fun); + qsort(all_procs.processes, all_procs.processes_count, + sizeof(*all_procs.processes), sort_fun); } -static const char *columnName[] = { - "PID", "USER", "GPU", "TYPE", "GPU MEM", "CPU", "HOST MEM", "Command", +static const char *columnName[process_field_count] = { + "PID", "USER", "DEV", "TYPE", "GPU", "ENC", + "DEC", "GPU MEM", "CPU", "HOST MEM", "Command", }; static void update_selected_offset_with_window_size( unsigned int *selected_row, unsigned int *offset, unsigned int row_available_to_draw, unsigned int num_to_draw) { + if (!num_to_draw) + return; + if (*selected_row > num_to_draw - 1) *selected_row = num_to_draw - 1; @@ -1133,34 +1056,23 @@ #define process_buffer_line_size 8192 static char process_print_buffer[process_buffer_line_size]; -static void set_attribute_between(WINDOW *win, int startY, int startX, int endX, - attr_t attr, short pair) { - int rows, cols; - getmaxyx(win, rows, cols); - (void)rows; - if (startX >= cols || endX < 0) - return; - startX = startX < 0 ? 0 : startX; - endX = endX > cols ? cols : endX; - int size = endX - startX; - mvwchgat(win, startY, startX, size, attr, pair, NULL); -} - -static void print_processes_on_screen(unsigned int num_process, - struct process_window *process) { +static void +print_processes_on_screen(all_processes all_procs, + struct process_window *process, + enum process_field sort_criterion, + process_field_displayed fields_to_display) { WINDOW *win = process->option_window.state == nvtop_option_state_hidden ? process->process_win : process->process_with_option_win; - struct all_gpu_processes *proc = process->all_process; - - werase(win); + struct gpuid_and_process *processes = all_procs.processes; unsigned int rows, cols; getmaxyx(win, rows, cols); rows -= 1; update_selected_offset_with_window_size(&process->selected_row, - &process->offset, rows, num_process); + &process->offset, rows, + all_procs.processes_count); if (process->offset_column + cols >= process_buffer_line_size) process->offset_column = process_buffer_line_size - cols - 1; @@ -1178,192 +1090,288 @@ int printed = 0; int column_sort_start = 0, column_sort_end = sizeof_process_field[0]; memset(process_print_buffer, 0, sizeof(process_print_buffer)); - for (unsigned int i = 0; i < process_end; ++i) { - if (i == process->sort_criterion) { + for (enum process_field i = process_pid; i < process_field_count; ++i) { + if (i == sort_criterion) { column_sort_start = printed; column_sort_end = i == process_command ? process_buffer_line_size - 4 : column_sort_start + sizeof_process_field[i]; } - printed += snprintf(&process_print_buffer[printed], - process_buffer_line_size - printed, "%*s ", - sizeof_process_field[i], columnName[i]); + if (process_is_field_displayed(i, fields_to_display)) + printed += snprintf(&process_print_buffer[printed], + process_buffer_line_size - printed, "%*s ", + sizeof_process_field[i], columnName[i]); } mvwprintw(win, 0, 0, "%.*s", cols, &process_print_buffer[process->offset_column]); + wclrtoeol(win); mvwchgat(win, 0, 0, -1, A_STANDOUT, green_color, NULL); set_attribute_between(win, 0, column_sort_start - (int)process->offset_column, column_sort_end - (int)process->offset_column, A_STANDOUT, cyan_color); - int start_type = 0; - for (unsigned int i = 0; i < process_type; ++i) { - start_type += sizeof_process_field[i] + 1; - } - int end_type = start_type + sizeof_process_field[process_type]; - - for (unsigned int i = start_at_process; i < end_at_process && i < num_process; - ++i) { + int start_col_process_type = 0; + for (enum process_field i = process_pid; i < process_type; ++i) { + if (process_is_field_displayed(i, fields_to_display)) + start_col_process_type += sizeof_process_field[i] + 1; + } + int end_col_process_type = + start_col_process_type + sizeof_process_field[process_type]; + + static unsigned printed_last_call = 0; + unsigned last_line_printed = 0; + for (unsigned int i = start_at_process; + i < end_at_process && i < all_procs.processes_count; ++i) { memset(process_print_buffer, 0, sizeof(process_print_buffer)); - size_t size = snprintf(pid_str, sizeof_process_field[process_pid] + 1, - "%" PRIdMAX, proc[i].pid); - if (size == sizeof_process_field[process_pid] + 1) - pid_str[sizeof_process_field[process_pid]] = '\0'; - size = snprintf(guid_str, sizeof_process_field[process_gpu_id] + 1, "%u", - proc[i].gpu_id); - if (size == sizeof_process_field[process_gpu_id] + 1) - pid_str[sizeof_process_field[process_gpu_id]] = '\0'; - printed = - snprintf(process_print_buffer, process_buffer_line_size, "%*s %*s %*s", - sizeof_process_field[process_pid], pid_str, - sizeof_process_field[process_user], proc[i].user_name, - sizeof_process_field[process_gpu_id], guid_str); - if (proc[i].is_graphical) { + + printed = 0; + if (process_is_field_displayed(process_pid, fields_to_display)) { + size_t size = snprintf(pid_str, sizeof_process_field[process_pid] + 1, + "%" PRIdMAX, (intmax_t)processes[i].process->pid); + if (size == sizeof_process_field[process_pid] + 1) + pid_str[sizeof_process_field[process_pid]] = '\0'; + printed += + snprintf(&process_print_buffer[printed], process_buffer_line_size, + "%*s ", sizeof_process_field[process_pid], pid_str); + } + + if (process_is_field_displayed(process_user, fields_to_display)) { + const char *username; + if (IS_VALID(gpuinfo_process_user_name_valid, + processes[i].process->valid)) { + username = processes[i].process->user_name; + } else { + username = "N/A"; + } + printed += snprintf(&process_print_buffer[printed], - process_buffer_line_size - printed, " %*s ", - sizeof_process_field[process_type], "Graphic"); - } else { + process_buffer_line_size - printed, "%*s ", + sizeof_process_field[process_user], username); + } + + if (process_is_field_displayed(process_gpu_id, fields_to_display)) { + size_t size = snprintf(guid_str, sizeof_process_field[process_gpu_id] + 1, + "%u", processes[i].gpu_id); + if (size >= sizeof_process_field[process_gpu_id] + 1) + pid_str[sizeof_process_field[process_gpu_id]] = '\0'; printed += snprintf(&process_print_buffer[printed], - process_buffer_line_size - printed, " %*s ", - sizeof_process_field[process_type], "Compute"); + process_buffer_line_size - printed, "%*s ", + sizeof_process_field[process_gpu_id], guid_str); + } + + if (process_is_field_displayed(process_type, fields_to_display)) { + if (processes[i].process->type == gpu_process_graphical) { + printed += snprintf(&process_print_buffer[printed], + process_buffer_line_size - printed, "%*s ", + sizeof_process_field[process_type], "Graphic"); + } else { + printed += snprintf(&process_print_buffer[printed], + process_buffer_line_size - printed, "%*s ", + sizeof_process_field[process_type], "Compute"); + } + } + + if (process_is_field_displayed(process_gpu_rate, fields_to_display)) { + unsigned gpu_usage = 0; + if (IS_VALID(gpuinfo_process_gpu_usage_valid, + processes[i].process->valid)) { + gpu_usage = processes[i].process->gpu_usage; + } + printed += + snprintf(&process_print_buffer[printed], + process_buffer_line_size - printed, "%3u%% ", gpu_usage); } - snprintf(memory, 10, "%6uMiB", (unsigned)(proc[i].used_memory / 1048576)); - snprintf(memory + 9, sizeof_process_field[process_memory] - 7, " %3.0f%%", - proc[i].mem_percentage); - printed += snprintf(&process_print_buffer[printed], - process_buffer_line_size - printed, "%*s ", - sizeof_process_field[process_memory], memory); - snprintf(cpu_percent, sizeof_process_field[process_cpu_usage] + 1, "%.f%%", - proc[i].cpu_percent); - printed += snprintf(&process_print_buffer[printed], - process_buffer_line_size - printed, "%*s ", - sizeof_process_field[process_cpu_usage], cpu_percent); - snprintf(cpu_mem, sizeof_process_field[process_cpu_mem_usage] + 1, "%zuMiB", - proc[i].cpu_memory / 1048576); - printed += snprintf(&process_print_buffer[printed], - process_buffer_line_size - printed, "%*s ", - sizeof_process_field[process_cpu_mem_usage], cpu_mem); - snprintf(&process_print_buffer[printed], process_buffer_line_size - printed, - "%.*s", process_buffer_line_size - printed, proc[i].process_name); + + if (process_is_field_displayed(process_enc_rate, fields_to_display)) { + unsigned encoder_rate = 0; + if (IS_VALID(gpuinfo_process_gpu_encoder_valid, + processes[i].process->valid)) { + encoder_rate = processes[i].process->encode_usage; + } + printed += + snprintf(&process_print_buffer[printed], + process_buffer_line_size - printed, "%3u%% ", encoder_rate); + } + + if (process_is_field_displayed(process_dec_rate, fields_to_display)) { + unsigned decode_rate = 0; + if (IS_VALID(gpuinfo_process_gpu_decoder_valid, + processes[i].process->valid)) { + decode_rate = processes[i].process->decode_usage; + } + printed += + snprintf(&process_print_buffer[printed], + process_buffer_line_size - printed, "%3u%% ", decode_rate); + } + + if (process_is_field_displayed(process_memory, fields_to_display)) { + if (IS_VALID(gpuinfo_process_gpu_memory_usage_valid, + processes[i].process->valid)) { + if (IS_VALID(gpuinfo_process_gpu_memory_percentage_valid, + processes[i].process->valid)) { + snprintf( + memory, 9 + 1, "%6uMiB", + (unsigned)(processes[i].process->gpu_memory_usage / 1048576)); + snprintf(memory + 9, sizeof_process_field[process_memory] - 9 + 1, + " %3u%%", processes[i].process->gpu_memory_percentage); + } else { + snprintf( + memory, sizeof_process_field[process_memory], "%6uMiB", + (unsigned)(processes[i].process->gpu_memory_usage / 1048576)); + } + } else { + memory[0] = '\0'; + } + printed += snprintf(&process_print_buffer[printed], + process_buffer_line_size - printed, "%*s ", + sizeof_process_field[process_memory], memory); + } + + if (process_is_field_displayed(process_cpu_usage, fields_to_display)) { + if (IS_VALID(gpuinfo_process_cpu_usage_valid, + processes[i].process->valid)) + snprintf(cpu_percent, sizeof_process_field[process_cpu_usage] + 1, + "%u%%", processes[i].process->cpu_usage); + else + snprintf(cpu_percent, sizeof_process_field[process_cpu_usage] + 1, + " N/A"); + printed += snprintf(&process_print_buffer[printed], + process_buffer_line_size - printed, "%*s ", + sizeof_process_field[process_cpu_usage], cpu_percent); + } + + if (process_is_field_displayed(process_cpu_mem_usage, fields_to_display)) { + if (IS_VALID(gpuinfo_process_cpu_memory_res_valid, + processes[i].process->valid)) + snprintf(cpu_mem, sizeof_process_field[process_cpu_mem_usage] + 1, + "%zuMiB", processes[i].process->cpu_memory_res / 1048576); + else + snprintf(cpu_mem, sizeof_process_field[process_cpu_mem_usage] + 1, + "N/A"); + printed += snprintf(&process_print_buffer[printed], + process_buffer_line_size - printed, "%*s ", + sizeof_process_field[process_cpu_mem_usage], cpu_mem); + } + + if (process_is_field_displayed(process_command, fields_to_display)) { + if (IS_VALID(gpuinfo_process_cmdline_valid, processes[i].process->valid)) + printed += snprintf(&process_print_buffer[printed], + process_buffer_line_size - printed, "%.*s", + process_buffer_line_size - printed, + processes[i].process->cmdline); + } + unsigned int write_at = i - start_at_process + 1; mvwprintw(win, write_at, 0, "%.*s", cols, &process_print_buffer[process->offset_column]); + unsigned row, col; + getyx(win, row, col); + if (row == write_at) + wclrtoeol(win); + last_line_printed = write_at; if (i == special_row) { mvwchgat(win, write_at, 0, -1, A_STANDOUT, cyan_color, NULL); } else { - if (proc[i].is_graphical) { - set_attribute_between( - win, write_at, start_type - (int)process->offset_column, - end_type - (int)process->offset_column, 0, yellow_color); - } else { - set_attribute_between( - win, write_at, start_type - (int)process->offset_column, - end_type - (int)process->offset_column, 0, magenta_color); + if (process_is_field_displayed(process_type, fields_to_display)) { + if (processes[i].process->type == gpu_process_graphical) { + set_attribute_between( + win, write_at, + start_col_process_type - (int)process->offset_column, + end_col_process_type - (int)process->offset_column, 0, + yellow_color); + } else { + set_attribute_between( + win, write_at, + start_col_process_type - (int)process->offset_column, + end_col_process_type - (int)process->offset_column, 0, + magenta_color); + } } } } + if (printed_last_call > last_line_printed) { + for (unsigned i = last_line_printed + 1; + i <= rows && i <= printed_last_call; ++i) { + wmove(win, i, 0); + wclrtoeol(win); + } + } + printed_last_call = last_line_printed; wnoutrefresh(win); } -static void draw_processes(struct device_info *dev_info, - struct nvtop_interface *interface) { +static void update_process_option_win(struct nvtop_interface *interface); +static void draw_processes(unsigned devices_count, gpu_info *devices, + struct nvtop_interface *interface) { if (interface->process.process_win == NULL) return; - unsigned int num_devices = interface->num_devices; - unsigned int total_processes = 0; - if (interface->process.option_window.state == nvtop_option_state_hidden) { - total_processes = - copy_processes_for_processing(num_devices, dev_info, interface); - interface->process.num_processes = total_processes; - sort_process(interface->process.all_process, total_processes, - interface->process.sort_criterion, - interface->process.sort_asc); + + if (interface->process.option_window.state != + interface->process.option_window.previous_state) { + werase(interface->process.option_window.option_win); + wclear(interface->process.process_win); + wclear(interface->process.process_with_option_win); + wnoutrefresh(interface->process.option_window.option_win); + } + if (interface->process.option_window.state != nvtop_option_state_hidden) + update_process_option_win(interface); + + all_processes all_procs = all_processes_array(devices_count, devices); + sort_process(all_procs, interface->options.sort_processes_by, + !interface->options.sort_descending_order); + + if (all_procs.processes_count > 0) { + if (interface->process.selected_row >= all_procs.processes_count) + interface->process.selected_row = all_procs.processes_count - 1; + interface->process.selected_pid = + all_procs.processes[interface->process.selected_row].process->pid; } else { - total_processes = interface->process.num_processes; + interface->process.selected_row = 0; + interface->process.selected_pid = -1; } - sizeof_process_field[process_user] = interface->process.size_biggest_name; + unsigned largest_username = 4; + for (unsigned i = 0; i < all_procs.processes_count; ++i) { + if (IS_VALID(gpuinfo_process_user_name_valid, + all_procs.processes[i].process->valid)) { + unsigned length = strlen(all_procs.processes[i].process->user_name); + if (length > largest_username) + largest_username = length; + } + } + sizeof_process_field[process_user] = largest_username; - print_processes_on_screen(total_processes, &interface->process); + print_processes_on_screen(all_procs, &interface->process, + interface->options.sort_processes_by, + interface->options.process_fields_displayed); + free(all_procs.processes); } static const char *signalNames[] = { - "Cancel", - "SIGHUP", - "SIGINT", - "SIGQUIT", - "SIGILL", - "SIGTRAP", - "SIGABRT", - "SIGBUS", - "SIGFPE", - "SIGKILL", - "SIGUSR1", - "SIGSEGV", - "SIGUSR2", - "SIGPIPE", - "SIGALRM", - "SIGTERM", - "SIGCHLD", - "SIGCONT", - "SIGSTOP", - "SIGTSTP", - "SIGTTIN", - "SIGTTOU", - "SIGURG", - "SIGXCPU", - "SIGXFSZ", - "SIGVTALRM", - "SIGPROF", - "SIGWINCH", - "SIGIO", - "SIGPWR", - "SIGSYS", + "Cancel", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", + "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", + "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGCHLD", "SIGCONT", + "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", + "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", "SIGPWR", + "SIGSYS", }; static const int signalValues[ARRAY_SIZE(signalNames)] = { - -1, - SIGHUP, - SIGINT, - SIGQUIT, - SIGILL, - SIGTRAP, - SIGABRT, - SIGBUS, - SIGFPE, - SIGKILL, - SIGUSR1, - SIGSEGV, - SIGUSR2, - SIGPIPE, - SIGALRM, - SIGTERM, - SIGCHLD, - SIGCONT, - SIGSTOP, - SIGTSTP, - SIGTTIN, - SIGTTOU, - SIGURG, - SIGXCPU, - SIGXFSZ, - SIGVTALRM, - SIGPROF, - SIGWINCH, - SIGIO, - SIGPWR, - SIGSYS, + -1, SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, + SIGFPE, SIGKILL, SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, + SIGCHLD, SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, + SIGXFSZ, SIGVTALRM, SIGPROF, SIGWINCH, SIGIO, SIGPWR, SIGSYS, }; static const size_t nvtop_num_signals = ARRAY_SIZE(signalNames) - 1; static void draw_kill_option(struct nvtop_interface *interface) { WINDOW *win = interface->process.option_window.option_win; - wattron(win, COLOR_PAIR(green_color) | A_REVERSE); + wattr_set(win, A_REVERSE, green_color, NULL); mvwprintw(win, 0, 0, "Send signal:"); - wattroff(win, COLOR_PAIR(green_color) | A_REVERSE); + wstandend(win); wprintw(win, " "); int rows, cols; getmaxyx(win, rows, cols); @@ -1374,7 +1382,7 @@ for (size_t i = start_at_option; i < end_at_option && i <= nvtop_num_signals; ++i) { if (i == interface->process.option_window.selected_row) { - wattron(win, COLOR_PAIR(cyan_color) | A_STANDOUT); + wattr_set(win, A_STANDOUT, cyan_color, NULL); } wprintw(win, "%*d %s", 2, i, signalNames[i]); getyx(win, rows, cols); @@ -1382,7 +1390,7 @@ for (unsigned int j = cols; j < option_window_size; ++j) wprintw(win, " "); if (i == interface->process.option_window.selected_row) { - wattroff(win, COLOR_PAIR(cyan_color) | A_STANDOUT); + wstandend(win); mvwprintw(win, rows, option_window_size - 1, " "); } } @@ -1391,21 +1399,21 @@ static void draw_sort_option(struct nvtop_interface *interface) { WINDOW *win = interface->process.option_window.option_win; - wattron(win, COLOR_PAIR(green_color) | A_REVERSE); + wattr_set(win, A_REVERSE, green_color, NULL); mvwprintw(win, 0, 0, "Sort by "); - wattroff(win, COLOR_PAIR(green_color) | A_REVERSE); + wstandend(win); wprintw(win, " "); int rows, cols; if (interface->process.option_window.offset == 0) { if (interface->process.option_window.selected_row == 0) { - wattron(win, COLOR_PAIR(cyan_color) | A_STANDOUT); + wattr_set(win, A_STANDOUT, cyan_color, NULL); } wprintw(win, "Cancel"); getyx(win, rows, cols); for (unsigned int j = cols; j < option_window_size; ++j) wprintw(win, " "); if (interface->process.option_window.selected_row == 0) { - wattroff(win, COLOR_PAIR(cyan_color) | A_STANDOUT); + wstandend(win); mvwprintw(win, rows, option_window_size - 1, " "); } } @@ -1418,23 +1426,32 @@ ? start_at_option + rows - 2 : start_at_option + rows - 1; - for (size_t i = start_at_option; i < end_at_option && i < process_end; ++i) { - if (i + 1 == interface->process.option_window.selected_row) { - wattron(win, COLOR_PAIR(cyan_color) | A_STANDOUT); - } - wprintw(win, "%s", columnName[i]); - getyx(win, rows, cols); - for (unsigned int j = cols; j < option_window_size; ++j) - wprintw(win, " "); - if (i + 1 == interface->process.option_window.selected_row) { - wattroff(win, COLOR_PAIR(cyan_color) | A_STANDOUT); - mvwprintw(win, rows, option_window_size - 1, " "); + unsigned option_index = 0; + for (enum process_field field = process_pid; field < process_field_count; + ++field) { + if (process_is_field_displayed( + field, interface->options.process_fields_displayed)) { + if (option_index >= start_at_option && option_index < end_at_option) { + if (option_index + 1 == interface->process.option_window.selected_row) { + wattr_set(win, A_STANDOUT, cyan_color, NULL); + } + wprintw(win, "%s", columnName[field]); + getyx(win, rows, cols); + for (unsigned int j = cols; j < option_window_size; ++j) + wprintw(win, " "); + + if (option_index + 1 == interface->process.option_window.selected_row) { + wstandend(win); + mvwprintw(win, rows, option_window_size - 1, " "); + } + } + option_index++; } } wnoutrefresh(win); } -static void draw_options(struct nvtop_interface *interface) { +static void update_process_option_win(struct nvtop_interface *interface) { unsigned int rows, cols; getmaxyx(interface->process.option_window.option_win, rows, cols); rows -= 1; @@ -1445,7 +1462,9 @@ num_options = nvtop_num_signals + 1; // Option + Cancel break; case nvtop_option_state_sort_by: - num_options = process_end + 1; // Option + Cancel + num_options = process_field_displayed_count( + interface->options.process_fields_displayed) + + 1; // Option + Cancel break; case nvtop_option_state_hidden: default: @@ -1470,14 +1489,10 @@ } static const char *option_selection_hidden[] = { - "Sort", - "Kill", - "Quit", + "Setup", "Sort", "Kill", "Quit", "Save Config", }; static const char *option_selection_hidden_num[] = { - "6", - "9", - "10", + "2", "6", "9", "10", "12", }; static const char *option_selection_sort[][2] = { @@ -1494,220 +1509,274 @@ static const unsigned int option_selection_width = 8; -static void draw_option_selection(struct nvtop_interface *interface) { - WINDOW *win = interface->process.option_window.option_selection_window; +static void draw_process_shortcuts(struct nvtop_interface *interface) { + if (interface->process.option_window.state == + interface->process.option_window.previous_state) + return; + WINDOW *win = interface->shortcut_window; + enum nvtop_option_window_state current_state = + interface->process.option_window.state; wmove(win, 0, 0); - switch (interface->process.option_window.state) { + switch (current_state) { case nvtop_option_state_hidden: - for (size_t i = 0; i < 3; ++i) { - wprintw(win, "F%s", option_selection_hidden_num[i]); - wattron(win, COLOR_PAIR(cyan_color) | A_STANDOUT); - wprintw(win, "%s", option_selection_hidden[i]); - for (size_t j = strlen(option_selection_hidden[i]); - j < option_selection_width; ++j) - wprintw(win, " "); - wattroff(win, COLOR_PAIR(cyan_color) | A_STANDOUT); + for (size_t i = 0; i < ARRAY_SIZE(option_selection_hidden); ++i) { + if (process_field_displayed_count( + interface->options.process_fields_displayed) > 0 || + (i != 1 && i != 2)) { + wprintw(win, "F%s", option_selection_hidden_num[i]); + wattr_set(win, A_STANDOUT, cyan_color, NULL); + wprintw(win, "%-*s", option_selection_width, + option_selection_hidden[i]); + wstandend(win); + } } break; case nvtop_option_state_kill: - for (size_t i = 0; i < 2; ++i) { + for (size_t i = 0; i < ARRAY_SIZE(option_selection_kill); ++i) { wprintw(win, "%s", option_selection_kill[i][0]); - wattron(win, COLOR_PAIR(cyan_color) | A_STANDOUT); - wprintw(win, "%s", option_selection_kill[i][1]); - for (size_t j = strlen(option_selection_kill[i][1]); - j < option_selection_width; ++j) - wprintw(win, " "); - wattroff(win, COLOR_PAIR(cyan_color) | A_STANDOUT); + wattr_set(win, A_STANDOUT, cyan_color, NULL); + wprintw(win, "%-*s", option_selection_width, option_selection_kill[i][1]); + wstandend(win); } break; case nvtop_option_state_sort_by: - for (size_t i = 0; i < 4; ++i) { + for (size_t i = 0; i < ARRAY_SIZE(option_selection_sort); ++i) { wprintw(win, "%s", option_selection_sort[i][0]); - wattron(win, COLOR_PAIR(cyan_color) | A_STANDOUT); - wprintw(win, "%s", option_selection_sort[i][1]); - for (size_t j = strlen(option_selection_sort[i][1]); - j < option_selection_width; ++j) - wprintw(win, " "); - wattroff(win, COLOR_PAIR(cyan_color) | A_STANDOUT); + wattr_set(win, A_STANDOUT, cyan_color, NULL); + wprintw(win, "%-*s", option_selection_width, option_selection_sort[i][1]); + wstandend(win); } break; default: break; } - unsigned int cur_col, maxcols, tmp; - getmaxyx(stdscr, tmp, maxcols); - getyx(win, tmp, cur_col); + wclrtoeol(win); + unsigned int cur_col, tmp; (void)tmp; - wattron(win, COLOR_PAIR(cyan_color) | A_STANDOUT); - for (unsigned int j = cur_col; j < maxcols; ++j) - wprintw(win, " "); - wattroff(win, COLOR_PAIR(cyan_color) | A_STANDOUT); + getyx(win, tmp, cur_col); + mvwchgat(win, 0, cur_col, -1, A_STANDOUT, cyan_color, NULL); wnoutrefresh(win); + interface->process.option_window.previous_state = current_state; } -static void update_retained_data(struct device_info *dev_info, - struct nvtop_interface *interface) { - nvtop_time time_now; - nvtop_get_current_time(&time_now); - double time_diff = - nvtop_difftime(interface->past_data.last_collect, time_now) * 1000; - if (time_diff >= interface->past_data.collect_interval) { - interface->past_data.last_collect = time_now; - for (size_t i = 0; i < interface->num_devices; ++i) { - ((unsigned(*)[interface->past_data.size_data_buffer])interface->past_data - .gpu_util)[i][interface->past_data.num_collected_data % - interface->past_data.size_data_buffer] = - dev_info[i].gpu_util_rate; - ((unsigned(*)[interface->past_data.size_data_buffer])interface->past_data - .mem_util)[i][interface->past_data.num_collected_data % - interface->past_data.size_data_buffer] = - 100 * dev_info[i].used_memory / dev_info[i].total_memory; - } - interface->past_data.num_collected_data++; - } -} - -static inline double *address_recent_left_old_right(size_t buffer_size, - double buffer[buffer_size], - size_t col_size, size_t col, - size_t offset_in_column) { - return &buffer[col * col_size + offset_in_column]; -} - -static inline double *address_old_left_recent_right(size_t buffer_size, - double buffer[buffer_size], - size_t col_size, size_t col, - size_t offset_in_column) { - return &buffer[buffer_size - (col + 1) * col_size + offset_in_column]; -} - -typedef double *(*plot_buffer_access_fn)(size_t, double *, size_t, size_t, - size_t); - -static void populate_plot_data_gpu_mem(struct nvtop_interface *interface, - struct plot_window *plot) { - plot_buffer_access_fn access_fn = interface->plot_old_left_recent_right - ? address_old_left_recent_right - : address_recent_left_old_right; - memset(plot->data, 0, plot->num_data * sizeof(double)); - unsigned num_cols = plot->type == plot_gpu_duo ? 4 : 2; - unsigned upper_bound = - plot->num_data / num_cols > interface->past_data.num_collected_data - ? interface->past_data.num_collected_data - : plot->num_data / num_cols; - for (unsigned i = 0; i < upper_bound; ++i) { - unsigned(*gpudata)[interface->past_data.size_data_buffer] = - (unsigned(*)[interface->past_data.size_data_buffer]) - interface->past_data.gpu_util; - unsigned(*memdata)[interface->past_data.size_data_buffer] = - (unsigned(*)[interface->past_data.size_data_buffer]) - interface->past_data.mem_util; - *access_fn(plot->num_data, plot->data, num_cols, i, 0) = - gpudata[plot->gpu_ids[0]] - [(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]; - *access_fn(plot->num_data, plot->data, num_cols, i, 1) = - memdata[plot->gpu_ids[0]] - [(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]; - if (plot->type == plot_gpu_duo) { - *access_fn(plot->num_data, plot->data, num_cols, i, 2) = - gpudata[plot->gpu_ids[1]] - [(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]; - *access_fn(plot->num_data, plot->data, num_cols, i, 3) = - memdata[plot->gpu_ids[1]] - [(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]; - } - } -} - -static void populate_plot_data_max_gpu_mem(struct nvtop_interface *interface, - struct plot_window *plot) { - // Populate data - memset(plot->data, 0, plot->num_data * sizeof(double)); - unsigned upper_bound = - plot->num_data / 2 > interface->past_data.num_collected_data - ? interface->past_data.num_collected_data - : plot->num_data / 2; - for (unsigned k = 0; k < interface->num_devices; ++k) { - for (unsigned i = 0; i < upper_bound; ++i) { - unsigned(*gpudata)[interface->past_data.size_data_buffer] = - (unsigned(*)[interface->past_data.size_data_buffer]) - interface->past_data.gpu_util; - unsigned(*memdata)[interface->past_data.size_data_buffer] = - (unsigned(*)[interface->past_data.size_data_buffer]) - interface->past_data.mem_util; - plot->data[i * 2] = - max(plot->data[i * 2], - gpudata[k][(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]); - plot->data[i * 2 + 1] = - max(plot->data[i * 2 + 1], - memdata[k][(interface->past_data.size_data_buffer + - interface->past_data.num_collected_data - i - 1) % - interface->past_data.size_data_buffer]); +static void draw_shortcuts(struct nvtop_interface *interface) { + if (interface->setup_win.visible) { + draw_setup_window_shortcuts(interface); + } else { + draw_process_shortcuts(interface); + } +} + +void save_current_data_to_ring(unsigned devices_count, gpu_info *devices, + struct nvtop_interface *interface) { + for (unsigned dev_id = 0; dev_id < devices_count; ++dev_id) { + unsigned data_index = 0; + for (enum plot_information info = plot_gpu_rate; + info < plot_information_count; ++info) { + if (plot_isset_draw_info( + info, interface->options.device_information_drawn[dev_id])) { + unsigned data_val = 0; + switch (info) { + case plot_gpu_rate: + if (IS_VALID(gpuinfo_gpu_util_rate_valid, + devices[dev_id].dynamic_info.valid)) + data_val = devices[dev_id].dynamic_info.gpu_util_rate; + break; + case plot_gpu_mem_rate: + if (IS_VALID(gpuinfo_mem_util_rate_valid, + devices[dev_id].dynamic_info.valid)) + data_val = devices[dev_id].dynamic_info.mem_util_rate; + break; + case plot_encoder_rate: + if (IS_VALID(gpuinfo_encoder_rate_valid, + devices[dev_id].dynamic_info.valid)) + data_val = devices[dev_id].dynamic_info.encoder_rate; + break; + case plot_decoder_rate: + if (IS_VALID(gpuinfo_decoder_rate_valid, + devices[dev_id].dynamic_info.valid)) + data_val = devices[dev_id].dynamic_info.decoder_rate; + break; + case plot_gpu_temperature: + if (IS_VALID(gpuinfo_gpu_temp_valid, + devices[dev_id].dynamic_info.valid)) { + data_val = devices[dev_id].dynamic_info.gpu_temp; + if (data_val > 100) + data_val = 100u; + } + break; + case plot_gpu_power_draw_rate: + if (IS_VALID(gpuinfo_power_draw_valid, + devices[dev_id].dynamic_info.valid) && + IS_VALID(gpuinfo_power_draw_max_valid, + devices[dev_id].dynamic_info.valid)) { + data_val = devices[dev_id].dynamic_info.power_draw * 100 / + devices[dev_id].dynamic_info.power_draw_max; + if (data_val > 100) + data_val = 100u; + } + break; + case plot_fan_speed: + if (IS_VALID(gpuinfo_fan_speed_valid, + devices[dev_id].dynamic_info.valid)) { + data_val = devices[dev_id].dynamic_info.fan_speed; + } + break; + case plot_gpu_clock_rate: + if (IS_VALID(gpuinfo_curr_gpu_clock_speed_valid, + devices[dev_id].dynamic_info.valid) && + IS_VALID(gpuinfo_max_gpu_clock_speed_valid, + devices[dev_id].dynamic_info.valid)) { + data_val = devices[dev_id].dynamic_info.gpu_clock_speed * 100 / + devices[dev_id].dynamic_info.gpu_clock_speed_max; + } + break; + case plot_gpu_mem_clock_rate: + if (IS_VALID(gpuinfo_curr_mem_clock_speed_valid, + devices[dev_id].dynamic_info.valid) && + IS_VALID(gpuinfo_max_mem_clock_speed_valid, + devices[dev_id].dynamic_info.valid)) { + data_val = devices[dev_id].dynamic_info.mem_clock_speed * 100 / + devices[dev_id].dynamic_info.mem_clock_speed_max; + } + break; + case plot_information_count: + break; + } + interface_ring_buffer_push(&interface->saved_data_ring, dev_id, + data_index, data_val); + data_index++; + } } } } -static void draw_plots(struct nvtop_interface *interface) { - for (unsigned i = 0; i < interface->num_plots; ++i) { - wnoutrefresh(interface->plots[i].win); - werase(interface->plots[i].plot_window); - switch (interface->plots[i].type) { - case plot_gpu_max: - populate_plot_data_max_gpu_mem(interface, &interface->plots[i]); - nvtop_line_plot(interface->plots[i].plot_window, - interface->plots[i].num_data, interface->plots[i].data, - 0., 100., 2, (const char * [2]){"MAX GPU", "MAX MEM"}); - break; - case plot_gpu_solo: { - populate_plot_data_gpu_mem(interface, &interface->plots[i]); - char gpuNum[8]; - snprintf(gpuNum, 8, "GPU %zu", interface->plots[i].gpu_ids[0]); - nvtop_line_plot(interface->plots[i].plot_window, - interface->plots[i].num_data, interface->plots[i].data, - 0., 100., 2, (const char * [2]){gpuNum, "MEM"}); - } break; - case plot_gpu_duo: { - populate_plot_data_gpu_mem(interface, &interface->plots[i]); - char gpuNum[2][8]; - char memNum[2][8]; - snprintf(gpuNum[0], 8, "GPU %zu", interface->plots[i].gpu_ids[0]); - snprintf(gpuNum[1], 8, "GPU %zu", interface->plots[i].gpu_ids[1]); - snprintf(memNum[0], 8, "MEM %zu", interface->plots[i].gpu_ids[0]); - snprintf(memNum[1], 8, "MEM %zu", interface->plots[i].gpu_ids[1]); - nvtop_line_plot( - interface->plots[i].plot_window, interface->plots[i].num_data, - interface->plots[i].data, 0., 100., 4, - (const char * [4]){gpuNum[0], memNum[0], gpuNum[1], memNum[1]}); - } break; - default: - break; +static unsigned populate_plot_data_from_ring_buffer( + const struct nvtop_interface *interface, struct plot_window *plot_win, + unsigned size_data_buff, double data[size_data_buff], + char plot_legend[4][PLOT_MAX_LEGEND_SIZE]) { + + memset(data, 0, size_data_buff * sizeof(*data)); + unsigned total_to_draw = 0; + for (unsigned i = 0; i < plot_win->num_devices_to_plot; ++i) { + unsigned dev_id = plot_win->devices_ids[i]; + plot_info_to_draw to_draw = + interface->options.device_information_drawn[dev_id]; + total_to_draw += plot_count_draw_info(to_draw); + } + + assert(total_to_draw > 0); + assert(size_data_buff % total_to_draw == 0); + unsigned max_data_to_copy = size_data_buff / total_to_draw; + double(*data_split)[total_to_draw] = (double(*)[total_to_draw])data; + + unsigned in_processing = 0; + for (unsigned i = 0; i < plot_win->num_devices_to_plot; ++i) { + unsigned dev_id = plot_win->devices_ids[i]; + plot_info_to_draw to_draw = + interface->options.device_information_drawn[dev_id]; + unsigned data_ring_index = 0; + for (enum plot_information info = plot_gpu_rate; + info < plot_information_count; ++info) { + if (plot_isset_draw_info(info, to_draw)) { + // Populate the legend + switch (info) { + case plot_gpu_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u %%", + dev_id); + break; + case plot_gpu_mem_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU%u mem%%", dev_id); + break; + case plot_encoder_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU%u encode%%", dev_id); + break; + case plot_decoder_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU%u decode%%", dev_id); + break; + case plot_gpu_temperature: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU%u temp(c)", dev_id); + break; + case plot_gpu_power_draw_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU%u power%%", dev_id); + break; + case plot_fan_speed: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU%u fan%%", dev_id); + break; + case plot_gpu_clock_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU%u clock%%", dev_id); + break; + case plot_gpu_mem_clock_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, + "GPU%u mem clock%%", dev_id); + break; + case plot_information_count: + break; + } + // Copy the data + unsigned data_in_ring = interface_ring_buffer_data_stored( + &interface->saved_data_ring, dev_id, data_ring_index); + if (interface->options.plot_left_to_right) { + for (unsigned j = 0; j < data_in_ring && j < max_data_to_copy; ++j) { + data_split[j][in_processing] = interface_ring_buffer_get( + &interface->saved_data_ring, dev_id, data_ring_index, + data_in_ring - j - 1); + } + } else { + for (unsigned j = 0; j < data_in_ring && j < max_data_to_copy; ++j) { + data_split[max_data_to_copy - j - 1][in_processing] = + interface_ring_buffer_get(&interface->saved_data_ring, dev_id, + data_ring_index, + data_in_ring - j - 1); + } + } + data_ring_index++; + in_processing++; + } } - wnoutrefresh(interface->plots[i].plot_window); + } + return total_to_draw; +} + +static void draw_plots(struct nvtop_interface *interface) { + for (unsigned plot_id = 0; plot_id < interface->num_plots; ++plot_id) { + werase(interface->plots[plot_id].plot_window); + + char plot_legend[4][PLOT_MAX_LEGEND_SIZE]; + + unsigned num_lines = populate_plot_data_from_ring_buffer( + interface, &interface->plots[plot_id], + interface->plots[plot_id].num_data, interface->plots[plot_id].data, + plot_legend); + + nvtop_line_plot(interface->plots[plot_id].plot_window, + interface->plots[plot_id].num_data, + interface->plots[plot_id].data, num_lines, + !interface->options.plot_left_to_right, plot_legend); + + wnoutrefresh(interface->plots[plot_id].plot_window); } } -void draw_gpu_info_ncurses(struct device_info *dev_info, +void draw_gpu_info_ncurses(unsigned devices_count, gpu_info *devices, struct nvtop_interface *interface) { - update_retained_data(dev_info, interface); - draw_devices(dev_info, interface); - draw_processes(dev_info, interface); - if (interface->process.option_window.state != nvtop_option_state_hidden) - draw_options(interface); - draw_option_selection(interface); - draw_plots(interface); + draw_devices(devices_count, devices, interface); + if (!interface->setup_win.visible) { + draw_plots(interface); + draw_processes(devices_count, devices, interface); + } else { + draw_setup_window(devices_count, devices, interface); + } + draw_shortcuts(interface); doupdate(); - refresh(); } void update_window_size_to_terminal_size(struct nvtop_interface *inter) { @@ -1716,101 +1785,128 @@ refresh(); refresh(); delete_all_windows(inter); - struct option_window options = inter->process.option_window; initialize_all_windows(inter); - inter->process.option_window.selected_row = options.selected_row; - inter->process.option_window.state = options.state; } -bool is_escape_for_quit(struct nvtop_interface *inter) { - if (inter->process.option_window.state == nvtop_option_state_hidden) +bool is_escape_for_quit(struct nvtop_interface *interface) { + if (interface->process.option_window.state == nvtop_option_state_hidden && + !interface->setup_win.visible) return true; else return false; } -static void option_do_kill(struct nvtop_interface *inter) { - if (inter->process.option_window.selected_row == 0) +static void option_do_kill(struct nvtop_interface *interface) { + if (interface->process.option_window.selected_row == 0) return; - pid_t pid = inter->process.all_process[inter->process.selected_row].pid; - int sig = signalValues[inter->process.option_window.selected_row]; - kill(pid, sig); + pid_t pid = interface->process.selected_pid; + int sig = signalValues[interface->process.option_window.selected_row]; + if (pid > 0) { + kill(pid, sig); + } } -static void option_change_sort(struct nvtop_interface *inter) { - if (inter->process.option_window.selected_row == 0) +static void option_change_sort(struct nvtop_interface *interface) { + if (interface->process.option_window.selected_row == 0) return; - inter->process.sort_criterion = - process_pid + inter->process.option_window.selected_row - 1; + unsigned index = 0; + for (enum process_field i = process_pid; i < process_field_count; ++i) { + if (process_is_field_displayed( + i, interface->options.process_fields_displayed)) { + if (index == interface->process.option_window.selected_row - 1) { + interface->options.sort_processes_by = i; + return; + } + index++; + } + } } -void interface_key(int keyId, struct nvtop_interface *inter) { +void interface_key(int keyId, struct nvtop_interface *interface) { + if (interface->setup_win.visible) { + handle_setup_win_keypress(keyId, interface); + return; + } switch (keyId) { + case KEY_F(2): + if (interface->process.option_window.state == nvtop_option_state_hidden && + !interface->setup_win.visible) { + show_setup_window(interface); + } + break; + case KEY_F(12): + save_interface_options_to_config_file(interface->devices_count, + &interface->options); + break; case KEY_F(9): - if (inter->process.option_window.state == nvtop_option_state_hidden) { - inter->process.option_window.state = nvtop_option_state_kill; - inter->process.option_window.selected_row = 0; + if (process_field_displayed_count( + interface->options.process_fields_displayed) > 0 && + interface->process.option_window.state == nvtop_option_state_hidden) { + interface->process.option_window.state = nvtop_option_state_kill; + interface->process.option_window.selected_row = 0; } break; case KEY_F(6): - if (inter->process.option_window.state == nvtop_option_state_hidden) { - inter->process.option_window.state = nvtop_option_state_sort_by; - inter->process.option_window.selected_row = 0; + if (process_field_displayed_count( + interface->options.process_fields_displayed) > 0 && + interface->process.option_window.state == nvtop_option_state_hidden) { + interface->process.option_window.state = nvtop_option_state_sort_by; + interface->process.option_window.selected_row = 0; } break; case KEY_RIGHT: - if (inter->process.option_window.state == nvtop_option_state_hidden) - inter->process.offset_column += 4; + if (interface->process.option_window.state == nvtop_option_state_hidden) + interface->process.offset_column += 4; break; case KEY_LEFT: - if (inter->process.option_window.state == nvtop_option_state_hidden && - inter->process.offset_column >= 4) - inter->process.offset_column -= 4; + if (interface->process.option_window.state == nvtop_option_state_hidden && + interface->process.offset_column >= 4) + interface->process.offset_column -= 4; break; case KEY_UP: - switch (inter->process.option_window.state) { + switch (interface->process.option_window.state) { case nvtop_option_state_kill: case nvtop_option_state_sort_by: - if (inter->process.option_window.selected_row != 0) - inter->process.option_window.selected_row--; + if (interface->process.option_window.selected_row != 0) + interface->process.option_window.selected_row--; break; case nvtop_option_state_hidden: - if (inter->process.selected_row != 0) - inter->process.selected_row--; + if (interface->process.selected_row != 0) + interface->process.selected_row--; break; default: break; } break; case KEY_DOWN: - switch (inter->process.option_window.state) { + switch (interface->process.option_window.state) { case nvtop_option_state_kill: case nvtop_option_state_sort_by: - inter->process.option_window.selected_row++; + interface->process.option_window.selected_row++; break; case nvtop_option_state_hidden: - inter->process.selected_row++; + interface->process.selected_row++; break; default: break; } break; case '+': - inter->process.sort_asc = true; + interface->options.sort_descending_order = false; break; case '-': - inter->process.sort_asc = false; + interface->options.sort_descending_order = true; break; case '\n': case KEY_ENTER: - switch (inter->process.option_window.state) { + switch (interface->process.option_window.state) { case nvtop_option_state_kill: - option_do_kill(inter); - inter->process.option_window.state = nvtop_option_state_hidden; + option_do_kill(interface); + interface->process.option_window.state = nvtop_option_state_hidden; break; case nvtop_option_state_sort_by: - option_change_sort(inter); - inter->process.option_window.state = nvtop_option_state_hidden; + option_change_sort(interface); + interface->process.option_window.state = nvtop_option_state_hidden; break; case nvtop_option_state_hidden: default: @@ -1818,9 +1914,20 @@ } break; case 27: - inter->process.option_window.state = nvtop_option_state_hidden; + interface->process.option_window.state = nvtop_option_state_hidden; break; default: break; } } + +bool interface_freeze_processes(struct nvtop_interface *interface) { + return interface->process.option_window.state == nvtop_option_state_kill; +} + +extern inline void set_attribute_between(WINDOW *win, int startY, int startX, + int endX, attr_t attr, short pair); + +int interface_update_interval(const struct nvtop_interface *interface) { + return interface->options.update_interval; +} diff -Nru nvtop-1.1.0/src/interface_layout_selection.c nvtop-1.2.2/src/interface_layout_selection.c --- nvtop-1.1.0/src/interface_layout_selection.c 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/src/interface_layout_selection.c 2021-07-24 13:07:26.000000000 +0000 @@ -1,51 +1,276 @@ -#include "nvtop/interface.h" #include "nvtop/interface_layout_selection.h" +#include "nvtop/interface.h" +#include "nvtop/interface_options.h" #include #include #include #include +#include #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) -static void min_size_taken_by_process(unsigned rows, unsigned num_devices, - unsigned *rows_needed) { - *rows_needed = 1 + max(5, min(rows / 4, num_devices * 3)); +static unsigned min_rows_taken_by_process(unsigned rows, unsigned num_devices) { + return 1 + max(5, min(rows / 4, num_devices * 3)); } static const unsigned cols_needed_box_drawing = 5; -static void min_size_taken_by_plot(unsigned num_data_info_to_plot, - unsigned *rows_needed, - unsigned *cols_needed) { - *rows_needed = 7; - *cols_needed = cols_needed_box_drawing + 10 * num_data_info_to_plot; +static const unsigned min_plot_rows = 7; +static unsigned min_plot_cols(unsigned num_data_info_to_plot) { + return cols_needed_box_drawing + 10 * num_data_info_to_plot; +} + +// The merge works as follows: +// Try to merge consecutive devices pairs, i.e. (0,1),(1,2), ..., (n-1,n) +// And then every 2 separated devices pairs, i.e. (0,2),(1,3), ... ,(n-2,n) +// And then every p separated devices pairs, i.e. (0,p),(1,p), ... ,(n-p,n) +static bool who_to_merge(unsigned n_th_merge, unsigned devices_count, + const plot_info_to_draw to_draw[devices_count], + unsigned merge_ids[2]) { + unsigned valid_merge_encountered = 0; + unsigned merge1 = 0, merge2; + unsigned separation = 1; + + while (true) { + merge2 = merge1 + separation; + if (merge2 < devices_count) { + unsigned dev1_info_count = plot_count_draw_info(to_draw[merge1]); + unsigned dev2_info_count = plot_count_draw_info(to_draw[merge2]); + if (dev1_info_count > 0 && dev2_info_count > 0 && + dev1_info_count + dev2_info_count <= 4) { + if (valid_merge_encountered == n_th_merge) { + merge_ids[0] = merge1; + merge_ids[1] = merge2; + return true; + } + valid_merge_encountered++; + } + merge1++; + } else { + merge1 = 0; + separation++; + // Has exhausted all possibilities + if (separation >= devices_count) + return false; + } + } +} + +static bool move_plot_to_stack(unsigned stack_max_cols, unsigned plot_id, + unsigned destination_stack, unsigned plot_count, + unsigned stack_count, + const unsigned num_info_per_plot[plot_count], + unsigned cols_allocated_in_stacks[stack_count], + unsigned plot_in_stack[plot_count]) { + if (plot_in_stack[plot_id] == destination_stack) + return false; + unsigned cols_used_by_plot_id = min_plot_cols(num_info_per_plot[plot_id]); + unsigned cols_after_merge = + cols_allocated_in_stacks[destination_stack] + cols_used_by_plot_id; + if (cols_after_merge > stack_max_cols) { + return false; + } else { + cols_allocated_in_stacks[plot_in_stack[plot_id]] -= cols_used_by_plot_id; + cols_allocated_in_stacks[destination_stack] += cols_used_by_plot_id; + plot_in_stack[plot_id] = destination_stack; + return true; + } +} + +static unsigned info_in_plot(unsigned plot_id, unsigned devices_count, + const unsigned map_device_to_plot[devices_count], + const plot_info_to_draw to_draw[devices_count]) { + unsigned sum = 0; + for (unsigned dev_id = 0; dev_id < devices_count; ++dev_id) { + if (map_device_to_plot[dev_id] == plot_id) + sum += plot_count_draw_info(to_draw[dev_id]); + } + return sum; +} + +static unsigned cols_used_by_stack(unsigned stack_id, unsigned plot_count, + const unsigned num_info_per_plot[plot_count], + const unsigned plot_in_stack[plot_count]) { + unsigned sum = 0; + for (unsigned plot_id = 0; plot_id < plot_count; ++plot_id) { + if (plot_in_stack[plot_id] == stack_id) + sum += min_plot_cols(num_info_per_plot[plot_id]); + } + return sum; +} + +static unsigned +size_differences_between_stacks(unsigned plot_count, unsigned stack_count, + unsigned cols_allocated_in_stacks[plot_count]) { + unsigned sum = 0; + for (unsigned i = 0; i < stack_count; ++i) { + for (unsigned j = i + 1; j < stack_count; ++j) { + if (cols_allocated_in_stacks[i] > cols_allocated_in_stacks[j]) { + sum += cols_allocated_in_stacks[i] - cols_allocated_in_stacks[j]; + } else { + sum += cols_allocated_in_stacks[j] - cols_allocated_in_stacks[i]; + } + } + } + return sum; +} + +static void +preliminary_plot_positioning(unsigned rows_for_plots, unsigned plot_total_cols, + unsigned devices_count, + const plot_info_to_draw to_draw[devices_count], + unsigned map_device_to_plot[devices_count], + unsigned plot_in_stack[devices_count], + unsigned *num_plots, unsigned *plot_stack_count) { + + // Used to handle the merging process + unsigned num_info_per_devices[MAX_CHARTS]; + unsigned how_many_to_merge = 0; + + bool plot_anything = false; + for (unsigned i = 0; i < devices_count; ++i) { + num_info_per_devices[i] = plot_count_draw_info(to_draw[i]); + if (num_info_per_devices[i]) + plot_anything = true; + } + + // Get the most packed configuration possible with one chart per device if + // possible. + // If there is not enough place, merge the charts and retry. + unsigned num_plot_stacks = 0; + bool search_a_window_configuration = + plot_anything && rows_for_plots >= min_plot_rows; + while (search_a_window_configuration) { + search_a_window_configuration = false; + unsigned plot_id = 0; + num_plot_stacks = 1; + unsigned cols_used_in_stack = 0; + unsigned rows_left_to_allocate = rows_for_plots - min_plot_rows; + + for (unsigned i = 0; i < devices_count; ++i) { + unsigned num_info_for_this_plot = num_info_per_devices[i]; + if (num_info_for_this_plot == 0) + continue; + + unsigned cols_this_plot = min_plot_cols(num_info_for_this_plot); + // If there is enough horizontal space left, allocate side by side + if (plot_total_cols >= cols_this_plot + cols_used_in_stack) { + cols_used_in_stack += cols_this_plot; + plot_in_stack[plot_id] = num_plot_stacks - 1; + map_device_to_plot[i] = plot_id; + plot_id++; + } else { + // This plot is too wide for an empty stack, abort + if (cols_used_in_stack == 0) { + num_plot_stacks = 0; + break; + } + // Else allocate a new stack and retry + if (rows_left_to_allocate >= min_plot_rows) { + rows_left_to_allocate -= min_plot_rows; + num_plot_stacks++; + cols_used_in_stack = 0; + i--; + } else { // Not enough space for a stack: retry and merge one more + unsigned to_merge[2]; + if (who_to_merge(how_many_to_merge, devices_count, to_draw, + to_merge)) { + num_info_per_devices[to_merge[0]] += + num_info_per_devices[to_merge[1]]; + num_info_per_devices[to_merge[1]] = 0; + how_many_to_merge++; + search_a_window_configuration = true; + } else { // No merge left + num_plot_stacks = 0; + } + break; + } + } + } + } + + // Compute the number of plots, the mapping and the size + *num_plots = 0; + *plot_stack_count = num_plot_stacks; + if (num_plot_stacks > 0) { + for (unsigned i = 0; i < devices_count; ++i) { + if (num_info_per_devices[i]) { + map_device_to_plot[i] = *num_plots; + *num_plots += 1; + } + } + // For the devices that were merged + for (unsigned i = 0; i < how_many_to_merge; ++i) { + unsigned to_merge[2]; + who_to_merge(i, devices_count, to_draw, to_merge); + map_device_to_plot[to_merge[1]] = map_device_to_plot[to_merge[0]]; + } + } +} + +static void balance_info_on_stacks_preserving_plot_order( + unsigned stack_max_cols, unsigned stack_count, unsigned plot_count, + unsigned num_info_per_plot[plot_count], + unsigned cols_allocated_in_stacks[stack_count], + unsigned plot_in_stack[plot_count]) { + if (stack_count > plot_count) { + stack_count = plot_count; + } + unsigned moving_plot_id = plot_count - 1; + while (moving_plot_id < plot_count) { + unsigned to_stack = plot_in_stack[moving_plot_id] + 1; + if (to_stack < stack_count) { + unsigned diff_sum_before = size_differences_between_stacks( + plot_count, stack_count, cols_allocated_in_stacks); + unsigned stack_before = plot_in_stack[moving_plot_id]; + if (move_plot_to_stack(stack_max_cols, moving_plot_id, to_stack, + plot_count, stack_count, num_info_per_plot, + cols_allocated_in_stacks, plot_in_stack)) { + unsigned diff_sum_after = size_differences_between_stacks( + plot_count, stack_count, cols_allocated_in_stacks); + if (diff_sum_after <= diff_sum_before) { + moving_plot_id = plot_count; + } else { + // Move back + move_plot_to_stack(stack_max_cols, moving_plot_id, stack_before, + plot_count, stack_count, num_info_per_plot, + cols_allocated_in_stacks, plot_in_stack); + } + } + } + moving_plot_id--; + } } void compute_sizes_from_layout( - bool show_graphs, bool show_header, bool show_process, size_t num_devices, - unsigned num_info_per_device, unsigned device_header_rows, + unsigned devices_count, unsigned device_header_rows, unsigned device_header_cols, unsigned rows, unsigned cols, - struct window_position *device_positions, - struct window_position *process_position, unsigned *num_plots, - struct window_position **plot_positions, enum plot_type *plot_types) { + const plot_info_to_draw to_draw[devices_count], + process_field_displayed process_displayed, + struct window_position device_positions[devices_count], unsigned *num_plots, + struct window_position plot_positions[MAX_CHARTS], + unsigned map_device_to_plot[devices_count], + struct window_position *process_position, + struct window_position *setup_position) { unsigned min_rows_for_header = 0, header_stacks = 0, num_device_per_row = 0; - if (show_header) { - num_device_per_row = max(1, cols / device_header_cols); - header_stacks = num_devices / num_device_per_row + - ((num_devices % num_device_per_row) > 0); - if (num_devices % header_stacks == 0) - num_device_per_row = num_devices / header_stacks; - min_rows_for_header = header_stacks * device_header_rows; - } - unsigned min_rows_for_process = 0; - if (show_process) { - min_size_taken_by_process(rows, num_devices, &min_rows_for_process); - } + num_device_per_row = max(1, cols / device_header_cols); + header_stacks = devices_count / num_device_per_row + + ((devices_count % num_device_per_row) > 0); + if (devices_count % header_stacks == 0) + num_device_per_row = devices_count / header_stacks; + min_rows_for_header = header_stacks * device_header_rows; + + unsigned min_rows_for_process = + process_field_displayed_count(process_displayed) + ? min_rows_taken_by_process(rows, devices_count) + : 0; + // Not enough room for the header and process if (rows < min_rows_for_header + min_rows_for_process) { - if (rows >= min_rows_for_header + 2) { // Shrink process + if (rows >= min_rows_for_header + 2 && + process_field_displayed_count(process_displayed)) { // Shrink process min_rows_for_process = rows - min_rows_for_header; } else { // Only header if possible min_rows_for_header = rows; @@ -54,83 +279,49 @@ } unsigned rows_for_header = min_rows_for_header; unsigned rows_for_process = min_rows_for_process; - unsigned rows_left = rows - min_rows_for_header - min_rows_for_process; + unsigned rows_for_plots = rows - min_rows_for_header - min_rows_for_process; - unsigned min_plot_rows, min_plot_cols_solo, min_plot_cols_duo; - min_size_taken_by_plot(num_info_per_device, &min_plot_rows, - &min_plot_cols_solo); - min_size_taken_by_plot(num_info_per_device * 2, &min_plot_rows, - &min_plot_cols_duo); - - enum plot_type preferred_plot_type = plot_gpu_duo; - unsigned max_plot_per_row = cols / min_plot_cols_duo; - if (max_plot_per_row == 0) { - if (cols >= min_plot_cols_solo) { - max_plot_per_row = 1; - preferred_plot_type = plot_gpu_solo; - } else { - max_plot_per_row = 0; - } - } - unsigned num_borrow_line = 0; unsigned num_plot_stacks = 0; - if (max_plot_per_row > 0 && show_graphs) { - if (preferred_plot_type == plot_gpu_duo) { - num_plot_stacks = - (num_devices + (num_devices % 2)) / 2 / max_plot_per_row; - } else { - num_plot_stacks = num_devices / max_plot_per_row; - } - num_plot_stacks = max(num_plot_stacks, 1); - if (num_plot_stacks * min_plot_rows > rows_left) { - if (rows_left >= min_plot_rows) { - preferred_plot_type = plot_gpu_max; - num_plot_stacks = 1; - } else { - num_plot_stacks = 0; - } - } - if (num_plot_stacks > 0) { - switch (preferred_plot_type) { - case plot_gpu_duo: - *num_plots = (num_devices + (num_devices % 2)) / 2; - break; - case plot_gpu_solo: - *num_plots = num_devices; - break; - case plot_gpu_max: - *num_plots = 1; - break; - } - num_borrow_line = rows_left - num_plot_stacks * min_plot_rows; - } else { - goto no_plot; - } - } else { - no_plot: - num_borrow_line = rows_left; - *num_plots = 0; - } - + unsigned plot_in_stack[MAX_CHARTS]; + preliminary_plot_positioning(rows_for_plots, cols, devices_count, to_draw, + map_device_to_plot, plot_in_stack, num_plots, + &num_plot_stacks); + + // Transfer some lines to the header to separate the devices + unsigned transferable_lines = + rows_for_plots - num_plot_stacks * min_plot_rows; unsigned space_for_header = header_stacks == 0 ? 0 : header_stacks - 1; bool space_between_header_stack = false; - if (num_borrow_line >= space_for_header && show_header) { + if (transferable_lines >= space_for_header) { rows_for_header += space_for_header; - rows_left -= space_for_header; + rows_for_plots -= space_for_header; space_between_header_stack = true; } - if (*num_plots == 0 && show_process && rows_left > 0) { - rows_for_process += rows_left - 1; - } + + // Allocate additional plot stacks if there is enough vertical room if (num_plot_stacks > 0) { - // Allocate a new plot stack if there is enough vertical room while (num_plot_stacks < *num_plots && - rows_left / (num_plot_stacks + 1) >= 11 && - (num_plot_stacks + 1) * min_plot_rows <= rows_left) + rows_for_plots / (num_plot_stacks + 1) >= 11 && + (num_plot_stacks + 1) * min_plot_rows <= rows_for_plots) num_plot_stacks++; } - // Now compute the interface window positions + // Compute the cols used in each stacks to prepare balancing + unsigned num_info_per_plot[MAX_CHARTS]; + for (unsigned i = 0; i < *num_plots; ++i) { + num_info_per_plot[i] = + info_in_plot(i, devices_count, map_device_to_plot, to_draw); + } + unsigned cols_allocated_in_stacks[MAX_CHARTS]; + for (unsigned i = 0; i < num_plot_stacks; ++i) { + cols_allocated_in_stacks[i] = + cols_used_by_stack(i, *num_plots, num_info_per_plot, plot_in_stack); + } + + // Keep the plot order of apparition, but spread the plot on different stacks + balance_info_on_stacks_preserving_plot_order( + cols, num_plot_stacks, *num_plots, num_info_per_plot, + cols_allocated_in_stacks, plot_in_stack); // Device Information Header unsigned cols_header_left = cols - num_device_per_row * device_header_cols; @@ -146,7 +337,7 @@ unsigned num_this_row = 0; unsigned headerPosX = space_before_header; unsigned headerPosY = 0; - for (unsigned i = 0; i < num_devices; ++i) { + for (unsigned i = 0; i < devices_count; ++i) { device_positions[i].posX = headerPosX; device_positions[i].posY = headerPosY; device_positions[i].sizeX = device_header_cols; @@ -162,50 +353,57 @@ } unsigned rows_left_for_process = 0; - if (*num_plots == 0) { - *plot_positions = NULL; - } else { - *plot_positions = calloc(*num_plots, sizeof(**plot_positions)); - *plot_types = preferred_plot_type; - unsigned rows_per_stack = rows_left / num_plot_stacks; + if (*num_plots > 0) { + unsigned rows_per_stack = rows_for_plots / num_plot_stacks; if (rows_per_stack > 23) rows_per_stack = 23; - unsigned plot_per_row = *num_plots / num_plot_stacks; - unsigned stacks_with_extra_plot = *num_plots % num_plot_stacks; unsigned num_plot_done = 0; unsigned currentPosX = 0, currentPosY = rows_for_header; - for (unsigned i = 0; i < num_plot_stacks; ++i) { - unsigned plot_in_this_row = min(*num_plots - num_plot_done, plot_per_row); - if (stacks_with_extra_plot) { - plot_in_this_row++; - stacks_with_extra_plot--; - } - unsigned cols_per_plot = cols / plot_in_this_row; - if (*plot_types == plot_gpu_duo) - cols_per_plot -= (cols_per_plot - cols_needed_box_drawing) % - (2 * num_info_per_device); - else - cols_per_plot -= - (cols_per_plot - cols_needed_box_drawing) % num_info_per_device; - unsigned extra_cols = cols - cols_per_plot * plot_in_this_row; - unsigned cols_between_plots = - extra_cols / (plot_in_this_row <= 1 ? 1 : plot_in_this_row - 1); - for (unsigned j = 0; j < plot_in_this_row; ++j) { - (*plot_positions)[num_plot_done].posX = currentPosX; - (*plot_positions)[num_plot_done].posY = currentPosY; - (*plot_positions)[num_plot_done].sizeX = cols_per_plot; - (*plot_positions)[num_plot_done].sizeY = rows_per_stack; - currentPosX += cols_per_plot + cols_between_plots; - num_plot_done++; + for (unsigned stack_id = 0; stack_id < num_plot_stacks; ++stack_id) { + unsigned plot_in_this_stack = 0; + unsigned lines_to_draw = 0; + for (unsigned j = 0; j < *num_plots; ++j) { + if (plot_in_stack[j] == stack_id) { + plot_in_this_stack++; + lines_to_draw += num_info_per_plot[j]; + } + } + unsigned cols_for_line_drawing = + cols - plot_in_this_stack * cols_needed_box_drawing; + for (unsigned j = 0; j < *num_plots; ++j) { + if (plot_in_stack[j] == stack_id) { + unsigned max_plot_cols = + cols_needed_box_drawing + + cols_for_line_drawing * num_info_per_plot[j] / lines_to_draw; + unsigned plot_cols = + max_plot_cols - + (max_plot_cols - cols_needed_box_drawing) % num_info_per_plot[j]; + plot_positions[num_plot_done].posX = currentPosX; + plot_positions[num_plot_done].posY = currentPosY; + plot_positions[num_plot_done].sizeX = plot_cols; + plot_positions[num_plot_done].sizeY = rows_per_stack; + currentPosX += max_plot_cols; + num_plot_done++; + } } currentPosY += rows_per_stack; currentPosX = 0; } - rows_left_for_process = rows_left - rows_per_stack * num_plot_stacks; + if (process_field_displayed_count(process_displayed) > 0) + rows_left_for_process = rows_for_plots - rows_per_stack * num_plot_stacks; + } else { + // No plot displayed, allocate the leftover space to the processes + if (process_field_displayed_count(process_displayed) > 0) + rows_for_process += rows_for_plots - 1; } process_position->posX = 0; process_position->posY = rows - rows_for_process - rows_left_for_process; process_position->sizeY = rows_for_process + rows_left_for_process; process_position->sizeX = cols; + + setup_position->posX = 0; + setup_position->posY = rows_for_header; + setup_position->sizeY = rows - rows_for_header; + setup_position->sizeX = cols; } diff -Nru nvtop-1.1.0/src/interface_options.c nvtop-1.2.2/src/interface_options.c --- nvtop-1.1.0/src/interface_options.c 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/src/interface_options.c 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,427 @@ +/* + * + * Copyright (C) 2021 Maxime Schmitt + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#include "nvtop/interface_options.h" +#include "ini.h" +#include "nvtop/interface_common.h" + +#include +#include +#include +#include +#include +#include +#include + +char config_file_path[PATH_MAX]; +const char config_file_location[] = "nvtop/interface.ini"; +const char config_conf_path[] = ".config"; + +static const char *default_config_path(void) { + char *xdg_config_dir = getenv("XDG_CONFIG_HOME"); + size_t conf_path_length = 0; + if (!xdg_config_dir) { + // XDG config dir not set, default to $HOME/.config + xdg_config_dir = getenv("HOME"); + conf_path_length = sizeof(config_conf_path); + } + size_t xdg_path_length = strlen(xdg_config_dir); + if (xdg_path_length < + PATH_MAX - conf_path_length - sizeof(config_file_location)) { + strcpy(config_file_path, xdg_config_dir); + config_file_path[xdg_path_length] = '/'; + if (conf_path_length) { + strcpy(config_file_path + xdg_path_length + 1, config_conf_path); + config_file_path[xdg_path_length + conf_path_length] = '/'; + } + strcpy(config_file_path + xdg_path_length + 1 + conf_path_length, + config_file_location); + return config_file_path; + } else { + return NULL; + } +} + +void alloc_interface_options_internals(char *config_location, + unsigned num_devices, + nvtop_interface_option *options) { + options->device_information_drawn = + calloc(num_devices, sizeof(*options->device_information_drawn)); + if (!options->device_information_drawn) { + perror("Cannot allocate memory: "); + exit(EXIT_FAILURE); + } + options->plot_left_to_right = false; + options->use_color = true; + options->encode_decode_hiding_timer = 30.; + options->temperature_in_fahrenheit = false; + options->config_file_location = NULL; + options->sort_processes_by = process_memory; + options->sort_descending_order = true; + options->update_interval = 1000; + options->process_fields_displayed = 0; + if (config_location) { + options->config_file_location = malloc(strlen(config_location) + 1); + if (!options->config_file_location) { + perror("Cannot allocate memory: "); + exit(EXIT_FAILURE); + } + strcpy(options->config_file_location, config_location); + } else { + const char *default_path = default_config_path(); + options->config_file_location = malloc(strlen(default_path) + 1); + if (!options->config_file_location) { + perror("Cannot allocate memory: "); + exit(EXIT_FAILURE); + } + strcpy(options->config_file_location, default_path); + } +} + +struct nvtop_option_ini_data { + unsigned num_devices; + nvtop_interface_option *options; +}; + +static const char do_not_modify_notice[] = + "; Please do not edit this file.\n" + "; The file is automatically generated and modified by nvtop by pressing " + "F12.\n" + "; If you wish to modify an option, use nvtop's setup window (F2) and " + "follow " + "up by saving the preference (F12).\n"; + +static const char general_section[] = "GeneralOption"; +static const char general_value_use_color[] = "UseColor"; +static const char general_value_update_interval[] = "UpdateInterval"; + +static const char header_section[] = "HeaderOption"; +static const char header_value_use_fahrenheit[] = "UseFahrenheit"; +static const char header_value_encode_decode_timer[] = "EncodeHideTimer"; + +static const char chart_section[] = "ChartOption"; +static const char chart_value_reverse[] = "ReverseChart"; + +static const char process_list_section[] = "ProcessListOption"; +static const char process_value_sortby[] = "SortBy"; +static const char process_value_display_field[] = "DisplayField"; +static const char *process_sortby_vals[process_field_count + 1] = { + "pId", "user", "gpuId", "type", "gpuRate", "encRate", + "decRate", "memory", "cpuUsage", "cpuMem", "cmdline", "none"}; +static const char process_value_sort_order[] = "SortOrder"; +static const char process_sort_descending[] = "descending"; +static const char process_sort_ascending[] = "ascending"; + +static const char device_section[] = "DeviceDrawOption"; +static const char device_shown_value[] = "ShownInfo"; +static const char *device_draw_vals[plot_information_count + 1] = { + "gpuRate", "gpuMemRate", "encodeRate", "decodeRate", + "temperature", "powerDrawRate", "fanSpeed", "gpuClockRate", + "gpuMemClockRate", "none"}; + +static int nvtop_option_ini_handler(void *user, const char *section, + const char *name, const char *value) { + struct nvtop_option_ini_data *ini_data = (struct nvtop_option_ini_data *)user; + // General Options + if (strcmp(section, general_section) == 0) { + if (strcmp(name, general_value_use_color) == 0) { + if (strcmp(value, "true") == 0) { + ini_data->options->use_color = true; + } + if (strcmp(value, "false") == 0) { + ini_data->options->use_color = false; + } + } + if (strcmp(name, general_value_update_interval) == 0) { + int update_interval; + if (sscanf(value, "%d", &update_interval) == 1) + ini_data->options->update_interval = update_interval; + } + } + // Header Options + if (strcmp(section, header_section) == 0) { + if (strcmp(name, header_value_use_fahrenheit) == 0) { + if (strcmp(value, "true") == 0) { + ini_data->options->temperature_in_fahrenheit = true; + } + if (strcmp(value, "false") == 0) { + ini_data->options->temperature_in_fahrenheit = false; + } + } + if (strcmp(name, header_value_encode_decode_timer) == 0) { + double value_double; + if (sscanf(value, "%le", &value_double) == 1) + ini_data->options->encode_decode_hiding_timer = value_double; + } + } + // Chart Options + if (strcmp(section, chart_section) == 0) { + if (strcmp(name, chart_value_reverse) == 0) { + if (strcmp(value, "true") == 0) { + ini_data->options->plot_left_to_right = true; + } + if (strcmp(value, "false") == 0) { + ini_data->options->plot_left_to_right = false; + } + } + } + // Process List Options + if (strcmp(section, process_list_section) == 0) { + if (strcmp(name, process_value_sortby) == 0) { + for (enum process_field i = process_pid; i < process_field_count; ++i) { + if (strcmp(value, process_sortby_vals[i]) == 0) { + ini_data->options->sort_processes_by = i; + } + } + } + if (strcmp(name, process_value_display_field) == 0) { + for (enum process_field i = process_pid; i < process_field_count + 1; + ++i) { + if (strcmp(value, process_sortby_vals[i]) == 0) { + ini_data->options->process_fields_displayed = + process_add_field_to_display( + i, ini_data->options->process_fields_displayed); + ini_data->options->process_fields_displayed = + process_add_field_to_display( + process_field_count, + ini_data->options->process_fields_displayed); + } + } + } + if (strcmp(name, process_value_sort_order) == 0) { + if (strcmp(value, process_sort_descending) == 0) { + ini_data->options->sort_descending_order = true; + } + if (strcmp(value, process_sort_ascending) == 0) { + ini_data->options->sort_descending_order = false; + } + } + } + // Per-Device Sections + for (unsigned i = 0; i < ini_data->num_devices; ++i) { + char gpu_section_name[sizeof(device_section) + 4]; + snprintf(gpu_section_name, sizeof(device_section) + 3, "%s%u", + device_section, i); + if (strcmp(section, gpu_section_name) == 0) { + if (strcmp(name, device_shown_value) == 0) { + for (enum plot_information j = plot_gpu_rate; + j < plot_information_count + 1; ++j) { + if (strcmp(value, device_draw_vals[j]) == 0) { + ini_data->options->device_information_drawn[i] = plot_add_draw_info( + j, ini_data->options->device_information_drawn[i]); + ini_data->options->device_information_drawn[i] = plot_add_draw_info( + plot_information_count, + ini_data->options->device_information_drawn[i]); + } + } + } + } + } + return 1; +} + +bool load_interface_options_from_config_file(unsigned num_devices, + nvtop_interface_option *options) { + FILE *option_file = fopen(options->config_file_location, "r"); + if (!option_file) + return false; + struct nvtop_option_ini_data ini_data = {num_devices, options}; + int retval = ini_parse_file(option_file, nvtop_option_ini_handler, &ini_data); + fclose(option_file); + if (!process_is_field_displayed(options->sort_processes_by, + options->process_fields_displayed)) { + options->sort_processes_by = + process_default_sort_by_from(options->process_fields_displayed); + } + return retval >= 0; +} + +static bool create_config_directory_rec(char *config_directory) { + for (char *index = config_directory + 1; *index != '\0'; ++index) { + if (*index == '/') { + *index = '\0'; + if (mkdir(config_directory, + S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { + if (errno != EEXIST) { + char *error_str = strerror(errno); + fprintf(stderr, "Could not create directory \"%s\": %s\n", + config_directory, error_str); + return false; + } + } + *index = '/'; + } + } + if (mkdir(config_directory, + S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { + if (errno != EEXIST) { + char *error_str = strerror(errno); + fprintf(stderr, "Could not create directory \"%s\": %s\n", + config_directory, error_str); + return false; + } + } + return true; +} + +static const char *boolean_string(bool value) { + return value ? "true" : "false"; +} + +bool save_interface_options_to_config_file( + unsigned num_devices, const nvtop_interface_option *options) { + + char folder_path[PATH_MAX]; + strcpy(folder_path, options->config_file_location); + char *config_directory = dirname(folder_path); + if (!create_config_directory_rec(config_directory)) + return false; + + FILE *config_file = fopen(options->config_file_location, "w"); + if (!config_file) { + char *error_str = strerror(errno); + fprintf(stderr, "Could not create config file \"%s\": %s\n", + options->config_file_location, error_str); + return false; + } + + fprintf(config_file, "%s", do_not_modify_notice); + // General Options + fprintf(config_file, "[%s]\n", general_section); + fprintf(config_file, "%s = %s\n", general_value_use_color, + boolean_string(options->use_color)); + fprintf(config_file, "%s = %d\n", general_value_update_interval, + options->update_interval); + + // Header Options + fprintf(config_file, "[%s]\n", header_section); + fprintf(config_file, "%s = %s\n", header_value_use_fahrenheit, + boolean_string(options->temperature_in_fahrenheit)); + fprintf(config_file, "%s = %e\n", header_value_encode_decode_timer, + options->encode_decode_hiding_timer); + fprintf(config_file, "\n"); + + // Chart Options + fprintf(config_file, "[%s]\n", chart_section); + fprintf(config_file, "%s = %s\n", chart_value_reverse, + boolean_string(options->plot_left_to_right)); + fprintf(config_file, "\n"); + + // Process Options + fprintf(config_file, "[%s]\n", process_list_section); + fprintf(config_file, "%s = %s\n", process_value_sort_order, + options->sort_descending_order ? process_sort_descending + : process_sort_ascending); + fprintf(config_file, "%s = %s\n", process_value_sortby, + process_sortby_vals[options->sort_processes_by]); + bool display_any_field = false; + for (enum process_field field = process_pid; field < process_field_count; + ++field) { + if (process_is_field_displayed(field, options->process_fields_displayed)) { + fprintf(config_file, "%s = %s\n", process_value_display_field, + process_sortby_vals[field]); + display_any_field = true; + } + } + if (!display_any_field) + fprintf(config_file, "%s = %s\n", process_value_display_field, + process_sortby_vals[process_field_count]); + fprintf(config_file, "\n"); + + // Per-Device Sections + for (unsigned i = 0; i < num_devices; ++i) { + fprintf(config_file, "[%s%u]\n", device_section, i); + bool draw_any = false; + for (enum plot_information j = plot_gpu_rate; j < plot_information_count; + ++j) { + if (plot_isset_draw_info(j, options->device_information_drawn[i])) { + fprintf(config_file, "%s = %s\n", device_shown_value, + device_draw_vals[j]); + draw_any = true; + } + } + if (!draw_any) + fprintf(config_file, "%s = %s\n", device_shown_value, + device_draw_vals[plot_information_count]); + fprintf(config_file, "\n"); + } + + fclose(config_file); + return true; +} + +extern inline plot_info_to_draw +plot_add_draw_info(enum plot_information set_info, plot_info_to_draw to_draw); + +extern inline plot_info_to_draw +plot_remove_draw_info(enum plot_information set_info, + plot_info_to_draw to_draw); + +extern inline plot_info_to_draw plot_default_draw_info(void); + +extern inline bool plot_isset_draw_info(enum plot_information check_info, + plot_info_to_draw to_draw); + +extern inline unsigned plot_count_draw_info(plot_info_to_draw to_draw); + +extern inline bool +process_is_field_displayed(enum process_field field, + process_field_displayed cols_displayed); + +extern inline process_field_displayed +process_remove_field_to_display(enum process_field field, + process_field_displayed cols_displayed); + +extern inline process_field_displayed +process_add_field_to_display(enum process_field field, + process_field_displayed cols_displayed); + +extern inline process_field_displayed process_default_displayed_field(void); + +extern inline unsigned +process_field_displayed_count(process_field_displayed fields_displayed); + +enum process_field +process_default_sort_by_from(process_field_displayed fields_displayed) { + if (process_is_field_displayed(process_memory, fields_displayed)) + return process_memory; + if (process_is_field_displayed(process_cpu_mem_usage, fields_displayed)) + return process_cpu_mem_usage; + if (process_is_field_displayed(process_gpu_rate, fields_displayed)) + return process_gpu_rate; + if (process_is_field_displayed(process_cpu_usage, fields_displayed)) + return process_cpu_usage; + if (process_is_field_displayed(process_command, fields_displayed)) + return process_command; + if (process_is_field_displayed(process_type, fields_displayed)) + return process_type; + if (process_is_field_displayed(process_enc_rate, fields_displayed)) + return process_enc_rate; + if (process_is_field_displayed(process_dec_rate, fields_displayed)) + return process_dec_rate; + if (process_is_field_displayed(process_user, fields_displayed)) + return process_user; + if (process_is_field_displayed(process_gpu_id, fields_displayed)) + return process_gpu_id; + if (process_is_field_displayed(process_pid, fields_displayed)) + return process_pid; + return process_field_count; +} diff -Nru nvtop-1.1.0/src/interface_ring_buffer.c nvtop-1.2.2/src/interface_ring_buffer.c --- nvtop-1.1.0/src/interface_ring_buffer.c 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/src/interface_ring_buffer.c 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,59 @@ + +#include "nvtop/interface_ring_buffer.h" + +#include "stdio.h" +#include "stdlib.h" + +void interface_alloc_ring_buffer(unsigned devices_count, + unsigned per_device_data_saved, + unsigned buffer_size, + interface_ring_buffer *ring_buffer) { + ring_buffer->ring_buffer[0] = + calloc(1, sizeof(unsigned[devices_count][per_device_data_saved][2])); + if (!ring_buffer->ring_buffer[0]) { + perror("Cannot allocate memory: "); + exit(EXIT_FAILURE); + } + ring_buffer->ring_buffer[1] = malloc( + sizeof(unsigned[devices_count][per_device_data_saved][buffer_size])); + if (!ring_buffer->ring_buffer[1]) { + perror("Cannot allocate memory: "); + exit(EXIT_FAILURE); + } + ring_buffer->buffer_size = buffer_size; + ring_buffer->per_device_data_saved = per_device_data_saved; + ring_buffer->devices_count = devices_count; +} + +void interface_free_ring_buffer(interface_ring_buffer *buffer) { + free(buffer->ring_buffer[0]); + free(buffer->ring_buffer[1]); +} + +extern inline unsigned +interface_ring_buffer_data_stored(const interface_ring_buffer *buff, + unsigned device, unsigned which_data); + +extern inline unsigned +interface_index_in_ring(const interface_ring_buffer *buff, unsigned device, + unsigned which_data, unsigned index); + +extern inline unsigned +interface_ring_buffer_get(const interface_ring_buffer *buff, unsigned device, + unsigned which_data, unsigned index); + +extern inline void interface_ring_buffer_push(interface_ring_buffer *buff, + unsigned device, + unsigned which_data, + unsigned value); + +extern inline void interface_ring_buffer_pop(interface_ring_buffer *buff, + unsigned device, + unsigned which_data); + +extern inline void +interface_ring_buffer_empty_select(interface_ring_buffer *buff, unsigned device, + unsigned which_data); + +extern inline void interface_ring_buffer_empty(interface_ring_buffer *buff, + unsigned device); diff -Nru nvtop-1.1.0/src/interface_setup_win.c nvtop-1.2.2/src/interface_setup_win.c --- nvtop-1.1.0/src/interface_setup_win.c 1970-01-01 00:00:00.000000000 +0000 +++ nvtop-1.2.2/src/interface_setup_win.c 2021-07-24 13:07:26.000000000 +0000 @@ -0,0 +1,889 @@ +/* + * + * Copyright (C) 2021 Maxime Schmitt + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ + +#include "nvtop/interface_setup_win.h" +#include "nvtop/interface.h" +#include "nvtop/interface_internal_common.h" +#include "nvtop/interface_options.h" +#include "nvtop/interface_ring_buffer.h" + +#include + +static char *setup_window_category_names[setup_window_selection_count] = { + "General", "Devices", "Chart", "Processes"}; + +// All the windows used to display the setup +enum setup_window_type { + setup_window_type_setup, + setup_window_type_single, + setup_window_type_split_left, + setup_window_type_split_right, + setup_window_type_count +}; + +// General Options + +enum setup_general_options { + setup_general_color, + setup_general_update_interval, + setup_general_options_count +}; + +static const char + *setup_general_option_description[setup_general_options_count] = { + "Disable color (requires save and restart)", + "Update interval (seconds)"}; + +// Header Options + +enum setup_header_options { + setup_header_toggle_fahrenheit, + setup_header_enc_dec_timer, + setup_header_options_count +}; + +static const char + *setup_header_option_descriptions[setup_header_options_count] = { + "Temperature in fahrenheit", + "Keep displaying Encoder/Decoder rate (after reaching an idle state)"}; + +// Chart Options + +enum setup_chart_options { + setup_chart_reverse, + setup_chart_all_gpu, + setup_chart_start_gpu_list, + setup_chart_options_count +}; + +static const char *setup_chart_options_descriptions[setup_chart_options_count] = + {"Reverse plot direction", "Displayed all GPUs", "Displayed GPU"}; + +static const char *setup_chart_gpu_value_descriptions[plot_information_count] = + {"GPU utilization rate", + "GPU memory utilization rate", + "GPU encoder rate", + "GPU decoder rate", + "GPU temperature", + "Power draw rate (current/max)", + "Fan speed", + "GPU clock rate", + "GPU memory clock rate"}; + +// Process List Options + +enum setup_proc_list_options { + setup_proc_list_sort_ascending, + setup_proc_list_sort_by, + setup_proc_list_display, + setup_proc_list_options_count +}; + +static const char + *setup_proc_list_option_description[setup_proc_list_options_count] = { + "Sort Ascending", "Sort by", "Field Displayed"}; + +static const char *setup_proc_list_value_descriptions[process_field_count] = { + "Process Id", "User name", "Device Id", "Workload type", + "GPU usage", "Encoder usage", "Decoder usage", "GPU memory usage", + "CPU usage", "CPU memory usage", "Command"}; + +static unsigned int sizeof_setup_windows[setup_window_type_count] = { + [setup_window_type_setup] = 11, + [setup_window_type_single] = 0, + [setup_window_type_split_left] = 26, + [setup_window_type_split_right] = 0}; + +// For toggle options +// Show * if on, - if partial and nothing if off +enum option_state { + option_off, + option_on, + option_partially_active, +}; + +static char option_state_char(enum option_state state) { + switch (state) { + case option_on: + return '*'; + case option_partially_active: + return '-'; + case option_off: + return ' '; + default: + return ' '; + } +} + +void alloc_setup_window(struct window_position *position, + struct setup_window *setup_win) { + setup_win->visible = false; + setup_win->clean_space = + newwin(position->sizeY, position->sizeX, position->posY, position->posX); + + sizeof_setup_windows[setup_window_type_single] = + position->sizeX - sizeof_setup_windows[setup_window_type_setup] - 1; + if (sizeof_setup_windows[setup_window_type_single] > position->sizeX) + sizeof_setup_windows[setup_window_type_single] = 0; + + sizeof_setup_windows[setup_window_type_split_right] = + position->sizeX - sizeof_setup_windows[setup_window_type_setup] - + sizeof_setup_windows[setup_window_type_split_left] - 2; + if (sizeof_setup_windows[setup_window_type_split_right] > position->sizeX) + sizeof_setup_windows[setup_window_type_split_right] = 0; + + setup_win->setup = + newwin(position->sizeY, sizeof_setup_windows[setup_window_type_setup], + position->posY, position->posX); + + setup_win->single = newwin( + position->sizeY, sizeof_setup_windows[setup_window_type_single], + position->posY, + position->posX + sizeof_setup_windows[setup_window_type_setup] + 1); + + setup_win->split[0] = newwin( + position->sizeY, sizeof_setup_windows[setup_window_type_split_left], + position->posY, + position->posX + sizeof_setup_windows[setup_window_type_setup] + 1); + + setup_win->split[1] = newwin( + position->sizeY, sizeof_setup_windows[setup_window_type_split_right], + position->posY, + position->posX + sizeof_setup_windows[setup_window_type_setup] + + sizeof_setup_windows[setup_window_type_split_left] + 2); +} + +void free_setup_window(struct setup_window *setup_win) { + delwin(setup_win->clean_space); + delwin(setup_win->setup); + delwin(setup_win->single); + delwin(setup_win->split[0]); + delwin(setup_win->split[1]); +} + +void show_setup_window(struct nvtop_interface *interface) { + interface->setup_win.visible = true; + touchwin(interface->setup_win.clean_space); + wnoutrefresh(interface->setup_win.clean_space); + interface->setup_win.selected_section = setup_general_selected; + interface->setup_win.indentation_level = 0; + interface->setup_win.options_selected[0] = 0; + interface->setup_win.options_selected[1] = 0; +} + +void hide_setup_window(struct nvtop_interface *interface) { + interface->setup_win.visible = false; +} + +static void draw_setup_window_setup(struct nvtop_interface *interface) { + werase(interface->setup_win.setup); + mvwprintw(interface->setup_win.setup, 0, 0, "Setup"); + mvwchgat(interface->setup_win.setup, 0, 0, + sizeof_setup_windows[setup_window_type_setup], A_STANDOUT, + green_color, NULL); + for (enum setup_window_section category = setup_general_selected; + category < setup_window_selection_count; ++category) { + mvwprintw(interface->setup_win.setup, category + 1, 0, "%s", + setup_window_category_names[category]); + if (interface->setup_win.selected_section == category) { + if (interface->setup_win.indentation_level == 0) { + set_attribute_between(interface->setup_win.setup, category + 1, 0, + sizeof_setup_windows[setup_window_type_setup], + A_STANDOUT, cyan_color); + } else { + mvwprintw(interface->setup_win.setup, category + 1, + sizeof_setup_windows[setup_window_type_setup] - 1, ">"); + set_attribute_between(interface->setup_win.setup, category + 1, 0, + sizeof_setup_windows[setup_window_type_setup], + A_BOLD, cyan_color); + } + } + } + wnoutrefresh(interface->setup_win.setup); +} + +static void draw_setup_window_general(struct nvtop_interface *interface) { + if (interface->setup_win.indentation_level > 1) + interface->setup_win.indentation_level = 1; + if (interface->setup_win.indentation_level == 1 && + interface->setup_win.options_selected[0] >= setup_general_options_count) + interface->setup_win.options_selected[0] = setup_general_options_count - 1; + + wattr_set(interface->setup_win.single, A_STANDOUT, green_color, NULL); + mvwprintw(interface->setup_win.single, 0, 0, "General Options"); + wstandend(interface->setup_win.single); + + unsigned int cur_col, maxcols, tmp; + (void)tmp; + getmaxyx(interface->setup_win.single, tmp, maxcols); + getyx(interface->setup_win.single, tmp, cur_col); + mvwchgat(interface->setup_win.single, 0, cur_col, maxcols - cur_col, + A_STANDOUT, green_color, NULL); + + enum option_state option_state = !interface->options.use_color; + mvwprintw(interface->setup_win.single, setup_general_color + 1, 0, "[%c] %s", + option_state_char(option_state), + setup_general_option_description[setup_general_color]); + if (interface->setup_win.indentation_level == 1 && + interface->setup_win.options_selected[0] == setup_general_color) { + mvwchgat(interface->setup_win.single, setup_general_color + 1, 0, 3, + A_STANDOUT, cyan_color, NULL); + } + + int update_deciseconds = (interface->options.update_interval / 100) % 10; + int update_seconds = interface->options.update_interval / 1000; + mvwprintw(interface->setup_win.single, setup_general_update_interval + 1, 0, + "[%2u.%u] %s", update_seconds, update_deciseconds, + setup_general_option_description[setup_general_update_interval]); + if (interface->setup_win.indentation_level == 1 && + interface->setup_win.options_selected[0] == + setup_general_update_interval) { + mvwchgat(interface->setup_win.single, setup_general_update_interval + 1, 0, + 6, A_STANDOUT, cyan_color, NULL); + } + wnoutrefresh(interface->setup_win.single); +} + +static void draw_setup_window_header(struct nvtop_interface *interface) { + if (interface->setup_win.indentation_level > 1) + interface->setup_win.indentation_level = 1; + if (interface->setup_win.options_selected[0] > setup_header_enc_dec_timer) + interface->setup_win.options_selected[0] = setup_header_enc_dec_timer; + + WINDOW *options_win = interface->setup_win.single; + + wattr_set(options_win, A_STANDOUT, green_color, NULL); + mvwprintw(options_win, 0, 0, "Devices Display Options"); + wstandend(options_win); + + unsigned int cur_col, maxcols, tmp; + (void)tmp; + getmaxyx(options_win, tmp, maxcols); + getyx(options_win, tmp, cur_col); + mvwchgat(options_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, green_color, + NULL); + + enum option_state option_state; + + // Fahrenheit Option + option_state = interface->options.temperature_in_fahrenheit; + mvwprintw(options_win, setup_header_toggle_fahrenheit + 1, 0, "[%c] %s", + option_state_char(option_state), + setup_header_option_descriptions[setup_header_toggle_fahrenheit]); + if (interface->setup_win.indentation_level == 1 && + interface->setup_win.options_selected[0] == + setup_header_toggle_fahrenheit) { + mvwchgat(options_win, setup_header_toggle_fahrenheit + 1, 0, 3, A_STANDOUT, + cyan_color, NULL); + } + + // Encode/Decode hiding timer + if (interface->options.encode_decode_hiding_timer > 0) { + mvwprintw(options_win, setup_header_enc_dec_timer + 1, 0, "[%3.0fsec] %s", + interface->options.encode_decode_hiding_timer, + setup_header_option_descriptions[setup_header_enc_dec_timer]); + } else { + mvwprintw(options_win, setup_header_enc_dec_timer + 1, 0, "[always] %s", + setup_header_option_descriptions[setup_header_enc_dec_timer]); + } + if (interface->setup_win.indentation_level == 1 && + interface->setup_win.options_selected[0] == setup_header_enc_dec_timer) { + mvwchgat(options_win, setup_header_enc_dec_timer + 1, 0, 8, A_STANDOUT, + cyan_color, NULL); + } + wnoutrefresh(options_win); +} + +static void draw_setup_window_chart(unsigned devices_count, gpu_info *devices, + struct nvtop_interface *interface) { + WINDOW *option_list_win; + + // Fix indices for this window + if (interface->setup_win.options_selected[0] > devices_count + 1) + interface->setup_win.options_selected[0] = devices_count + 1; + if (interface->setup_win.options_selected[0] > 0) { + if (interface->setup_win.options_selected[1] >= plot_information_count) + interface->setup_win.options_selected[1] = plot_information_count - 1; + option_list_win = interface->setup_win.split[0]; + } else { + if (interface->setup_win.indentation_level > 1) + interface->setup_win.indentation_level = 1; + option_list_win = interface->setup_win.single; + } + werase(interface->setup_win.single); + wnoutrefresh(interface->setup_win.single); + touchwin(interface->setup_win.split[0]); + touchwin(interface->setup_win.split[1]); + + wattr_set(option_list_win, A_STANDOUT, green_color, NULL); + mvwprintw(option_list_win, 0, 0, "Chart Options"); + wstandend(option_list_win); + + unsigned int cur_col, maxcols, tmp; + (void)tmp; + getmaxyx(option_list_win, tmp, maxcols); + getyx(option_list_win, tmp, cur_col); + mvwchgat(option_list_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, + green_color, NULL); + + enum option_state option_state; + + // Reverse plot + option_state = interface->options.plot_left_to_right; + mvwprintw(option_list_win, setup_chart_reverse + 1, 0, "[%c] %s", + option_state_char(option_state), + setup_chart_options_descriptions[setup_chart_reverse]); + if (interface->setup_win.indentation_level == 1 && + interface->setup_win.options_selected[0] == setup_chart_reverse) { + mvwchgat(option_list_win, setup_chart_reverse + 1, 0, 3, A_STANDOUT, + cyan_color, NULL); + } + + // Set for all GPUs at once + if (interface->setup_win.options_selected[0] == setup_chart_all_gpu) { + if (interface->setup_win.indentation_level == 1) + wattr_set(option_list_win, A_STANDOUT, cyan_color, NULL); + if (interface->setup_win.indentation_level == 2) + wattr_set(option_list_win, A_BOLD, cyan_color, NULL); + } + mvwaddch(option_list_win, setup_chart_all_gpu + 1, 1, ACS_HLINE); + waddch(option_list_win, '>'); + wstandend(option_list_win); + wprintw(option_list_win, " %s", + setup_chart_options_descriptions[setup_chart_all_gpu]); + + // GPUs as a list + for (unsigned i = 0; i < devices_count; ++i) { + if (interface->setup_win.options_selected[0] == + setup_chart_start_gpu_list + i) { + if (interface->setup_win.indentation_level == 1) + wattr_set(option_list_win, A_STANDOUT, cyan_color, NULL); + if (interface->setup_win.indentation_level == 2) + wattr_set(option_list_win, A_BOLD, cyan_color, NULL); + } + mvwaddch(option_list_win, setup_chart_start_gpu_list + 1 + i, 1, ACS_HLINE); + waddch(option_list_win, '>'); + wstandend(option_list_win); + wprintw(option_list_win, " %s %u", + setup_chart_options_descriptions[setup_chart_start_gpu_list], i); + } + wnoutrefresh(option_list_win); + + // Window of list of metric to display in chart (4 maximum) + if (interface->setup_win.options_selected[0] >= setup_chart_all_gpu) { + WINDOW *value_list_win = interface->setup_win.split[1]; + wattr_set(value_list_win, A_STANDOUT, green_color, NULL); + mvwprintw(value_list_win, 0, 0, "Metric Displayed in Graph"); + getmaxyx(value_list_win, tmp, maxcols); + unsigned selected_gpu = + interface->setup_win.options_selected[0] - setup_chart_start_gpu_list; + if (interface->setup_win.options_selected[0] == setup_chart_all_gpu) { + wprintw(value_list_win, " (All GPUs)"); + } else { + if (IS_VALID(gpuinfo_device_name_valid, devices->static_info.valid)) { + getyx(value_list_win, tmp, cur_col); + wprintw(value_list_win, " (%.*s)", maxcols - cur_col - 3, + devices->static_info.device_name); + } else + wprintw(value_list_win, " (GPU %u)", selected_gpu); + } + wclrtoeol(value_list_win); + getyx(value_list_win, tmp, cur_col); + mvwchgat(value_list_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, + green_color, NULL); + wattr_set(value_list_win, A_NORMAL, magenta_color, NULL); + mvwprintw(value_list_win, 1, 0, "Maximum of 4 metrics per GPU"); + wstandend(value_list_win); + + for (enum plot_information i = plot_gpu_rate; i < plot_information_count; + ++i) { + if (interface->setup_win.options_selected[0] == setup_chart_all_gpu) { + plot_info_to_draw draw_union = 0, draw_intersection = 0xffff; + for (unsigned j = 0; j < devices_count; ++j) { + draw_union |= interface->options.device_information_drawn[j]; + draw_intersection = draw_intersection & + interface->options.device_information_drawn[j]; + } + if (plot_isset_draw_info(i, draw_intersection)) { + option_state = option_on; + } else { + if (plot_isset_draw_info(i, draw_union)) + option_state = option_partially_active; + else + option_state = option_off; + } + } else { + option_state = plot_isset_draw_info( + i, interface->options.device_information_drawn[selected_gpu]); + } + mvwprintw(value_list_win, i + 2, 0, "[%c] %s", + option_state_char(option_state), + setup_chart_gpu_value_descriptions[i]); + if (interface->setup_win.indentation_level == 2 && + interface->setup_win.options_selected[1] == i) { + mvwchgat(value_list_win, i + 2, 0, 3, A_STANDOUT, cyan_color, NULL); + } + } + wnoutrefresh(value_list_win); + } +} + +static void draw_setup_window_proc_list(struct nvtop_interface *interface) { + WINDOW *option_list_win; + if (interface->setup_win.options_selected[0] >= setup_proc_list_options_count) + interface->setup_win.options_selected[0] = + setup_proc_list_options_count - 1; + if (interface->setup_win.options_selected[0] < setup_proc_list_sort_by) { + option_list_win = interface->setup_win.single; + if (interface->setup_win.indentation_level > 1) + interface->setup_win.indentation_level = 1; + } else { + option_list_win = interface->setup_win.split[0]; + if (interface->setup_win.options_selected[0] == setup_proc_list_sort_by) { + unsigned fields_count = process_field_displayed_count( + interface->options.process_fields_displayed); + if (!fields_count) { + if (interface->setup_win.indentation_level > 1) + interface->setup_win.indentation_level = 1; + } else { + if (interface->setup_win.options_selected[1] >= fields_count) + interface->setup_win.options_selected[1] = fields_count - 1; + } + } + if (interface->setup_win.options_selected[0] == setup_proc_list_display) { + if (interface->setup_win.options_selected[1] >= process_field_count) + interface->setup_win.options_selected[1] = process_field_count - 1; + } + } + + werase(interface->setup_win.single); + wnoutrefresh(interface->setup_win.single); + touchwin(interface->setup_win.split[0]); + touchwin(interface->setup_win.split[1]); + + wattr_set(option_list_win, A_STANDOUT, green_color, NULL); + mvwprintw(option_list_win, 0, 0, "Process List Options"); + wstandend(option_list_win); + unsigned int cur_col, maxcols, tmp; + (void)tmp; + getmaxyx(option_list_win, tmp, maxcols); + getyx(option_list_win, tmp, cur_col); + mvwchgat(option_list_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, + green_color, NULL); + + // Sort Ascending + enum option_state option_state = !interface->options.sort_descending_order; + mvwprintw(option_list_win, setup_proc_list_sort_ascending + 1, 0, "[%c] %s", + option_state_char(option_state), + setup_proc_list_option_description[setup_proc_list_sort_ascending]); + if (interface->setup_win.indentation_level == 1 && + interface->setup_win.options_selected[0] == + setup_proc_list_sort_ascending) { + mvwchgat(option_list_win, setup_proc_list_sort_ascending + 1, 0, 3, + A_STANDOUT, cyan_color, NULL); + } + + for (enum setup_proc_list_options i = setup_proc_list_sort_by; + i < setup_proc_list_options_count; ++i) { + if (interface->setup_win.options_selected[0] == i) { + if (interface->setup_win.indentation_level == 1) + wattr_set(option_list_win, A_STANDOUT, cyan_color, NULL); + if (interface->setup_win.indentation_level == 2) + wattr_set(option_list_win, A_BOLD, cyan_color, NULL); + } + mvwaddch(option_list_win, i + 1, 1, ACS_HLINE); + waddch(option_list_win, '>'); + wstandend(option_list_win); + wprintw(option_list_win, " %s", setup_proc_list_option_description[i]); + wnoutrefresh(option_list_win); + } + + if (interface->setup_win.options_selected[0] >= setup_proc_list_sort_by) { + WINDOW *value_list_win = interface->setup_win.split[1]; + // Sort by + if (interface->setup_win.options_selected[0] == setup_proc_list_sort_by) { + wattr_set(value_list_win, A_STANDOUT, green_color, NULL); + mvwprintw(value_list_win, 0, 0, "Processes are sorted by:"); + wstandend(value_list_win); + wclrtoeol(value_list_win); + getmaxyx(value_list_win, tmp, maxcols); + getyx(value_list_win, tmp, cur_col); + mvwchgat(value_list_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, + green_color, NULL); + unsigned index = 0; + for (enum process_field field = process_pid; field < process_field_count; + ++field) { + if (process_is_field_displayed( + field, interface->options.process_fields_displayed)) { + option_state = interface->options.sort_processes_by == field; + mvwprintw(value_list_win, index + 1, 0, "[%c] %s", + option_state_char(option_state), + setup_proc_list_value_descriptions[field]); + wclrtoeol(value_list_win); + if (interface->setup_win.indentation_level == 2 && + interface->setup_win.options_selected[1] == index) { + mvwchgat(value_list_win, index + 1, 0, 3, A_STANDOUT, cyan_color, + NULL); + wmove(value_list_win, field + 2, 0); + } + index++; + } + } + if (!index) { + // Nothing displayed + wcolor_set(value_list_win, magenta_color, NULL); + mvwprintw(value_list_win, 1, 0, + "Nothing to sort: none of the process fields are displayed"); + wstandend(value_list_win); + } + } + // Process field displayed + if (interface->setup_win.options_selected[0] == setup_proc_list_display) { + wattr_set(value_list_win, A_STANDOUT, green_color, NULL); + mvwprintw(value_list_win, 0, 0, "Process Field Displayed:"); + wstandend(value_list_win); + wclrtoeol(value_list_win); + getmaxyx(value_list_win, tmp, maxcols); + getyx(value_list_win, tmp, cur_col); + mvwchgat(value_list_win, 0, cur_col, maxcols - cur_col, A_STANDOUT, + green_color, NULL); + for (enum process_field field = process_pid; field < process_field_count; + ++field) { + option_state = process_is_field_displayed( + field, interface->options.process_fields_displayed); + mvwprintw(value_list_win, field + 1, 0, "[%c] %s", + option_state_char(option_state), + setup_proc_list_value_descriptions[field]); + wclrtoeol(value_list_win); + if (interface->setup_win.indentation_level == 2 && + interface->setup_win.options_selected[1] == field) { + mvwchgat(value_list_win, field + 1, 0, 3, A_STANDOUT, cyan_color, + NULL); + wmove(value_list_win, field + 2, 0); + } + } + } + wclrtobot(value_list_win); + wnoutrefresh(value_list_win); + } +} + +static const char *setup_window_shortcuts[] = {"Enter", "ESC", "Arrow keys", + "+/-", "F12"}; + +static const char *setup_window_shortcut_description[] = { + "Toggle", "Exit", "Navigate Menu", "Increment/Decrement Values", + "Save Config"}; + +void draw_setup_window_shortcuts(struct nvtop_interface *interface) { + WINDOW *window = interface->shortcut_window; + + wmove(window, 0, 0); + for (size_t i = 0; i < ARRAY_SIZE(setup_window_shortcuts); ++i) { + wprintw(window, "%s", setup_window_shortcuts[i]); + wattr_set(window, A_STANDOUT, cyan_color, NULL); + wprintw(window, "%s ", setup_window_shortcut_description[i]); + wstandend(window); + } + wclrtoeol(window); + unsigned int cur_col, tmp; + (void)tmp; + getyx(window, tmp, cur_col); + mvwchgat(window, 0, cur_col, -1, A_STANDOUT, cyan_color, NULL); + wnoutrefresh(window); +} + +void draw_setup_window(unsigned devices_count, gpu_info *devices, + struct nvtop_interface *interface) { + draw_setup_window_setup(interface); + switch (interface->setup_win.selected_section) { + case setup_general_selected: + draw_setup_window_general(interface); + break; + case setup_header_selected: + draw_setup_window_header(interface); + break; + case setup_chart_selected: + draw_setup_window_chart(devices_count, devices, interface); + break; + case setup_process_list_selected: + draw_setup_window_proc_list(interface); + break; + default: + break; + } +} + +void handle_setup_win_keypress(int keyId, struct nvtop_interface *interface) { + if (interface->setup_win.visible) { + switch (keyId) { + + case KEY_RIGHT: + if (interface->setup_win.indentation_level < 2) + interface->setup_win.indentation_level++; + break; + + case KEY_LEFT: + if (interface->setup_win.indentation_level > 0) + interface->setup_win.indentation_level--; + break; + + case KEY_UP: + if (interface->setup_win.indentation_level == 0) { + if (interface->setup_win.selected_section != setup_general_selected) { + interface->setup_win.selected_section--; + interface->setup_win.options_selected[0] = 0; + interface->setup_win.options_selected[1] = 0; + werase(interface->setup_win.single); + werase(interface->setup_win.split[0]); + werase(interface->setup_win.split[1]); + wnoutrefresh(interface->setup_win.single); + } + } else { + if (interface->setup_win.indentation_level == 1) + interface->setup_win.options_selected[1] = 0; + if (interface->setup_win + .options_selected[interface->setup_win.indentation_level - 1] != + 0) + interface->setup_win + .options_selected[interface->setup_win.indentation_level - 1]--; + } + break; + + case KEY_DOWN: + if (interface->setup_win.indentation_level == 0) { + if (interface->setup_win.selected_section + 1 != + setup_window_selection_count) { + + interface->setup_win.selected_section++; + interface->setup_win.options_selected[0] = 0; + interface->setup_win.options_selected[1] = 0; + werase(interface->setup_win.single); + werase(interface->setup_win.split[0]); + werase(interface->setup_win.split[1]); + wnoutrefresh(interface->setup_win.single); + } + } else { + if (interface->setup_win.indentation_level == 1) + interface->setup_win.options_selected[1] = 0; + interface->setup_win + .options_selected[interface->setup_win.indentation_level - 1]++; + } + break; + + case '+': + // General Options + if (interface->setup_win.selected_section == setup_general_selected) { + if (interface->setup_win.options_selected[0] == + setup_general_update_interval) { + if (interface->options.update_interval <= 99800) + interface->options.update_interval += 100; + } + } + // Header options + if (interface->setup_win.selected_section == setup_header_selected) { + if (interface->setup_win.indentation_level == 1) { + if (interface->setup_win.options_selected[0] == + setup_header_enc_dec_timer) { + interface->options.encode_decode_hiding_timer += 5.; + } + } + } + break; + case '-': + // General Options + if (interface->setup_win.selected_section == setup_general_selected) { + if (interface->setup_win.options_selected[0] == + setup_general_update_interval) { + if (interface->options.update_interval >= 200) + interface->options.update_interval -= 100; + } + } + // Header options + if (interface->setup_win.selected_section == setup_header_selected) { + if (interface->setup_win.indentation_level == 1) { + if (interface->setup_win.options_selected[0] == + setup_header_enc_dec_timer) { + interface->options.encode_decode_hiding_timer -= 5.; + if (interface->options.encode_decode_hiding_timer < 0.) { + interface->options.encode_decode_hiding_timer = 0.; + } + } + } + } + break; + case '\n': + case KEY_ENTER: + if (interface->setup_win.indentation_level == 0) { + handle_setup_win_keypress(KEY_RIGHT, interface); + return; + } + // General Options + if (interface->setup_win.selected_section == setup_general_selected) { + if (interface->setup_win.options_selected[0] == setup_general_color) { + interface->options.use_color = !interface->options.use_color; + } + if (interface->setup_win.options_selected[0] == + setup_general_update_interval) { + } + } + // Header Options + if (interface->setup_win.selected_section == setup_header_selected) { + if (interface->setup_win.indentation_level == 1) { + if (interface->setup_win.options_selected[0] == + setup_header_toggle_fahrenheit) { + interface->options.temperature_in_fahrenheit = + !interface->options.temperature_in_fahrenheit; + } + if (interface->setup_win.options_selected[0] == + setup_header_enc_dec_timer) { + if (interface->options.encode_decode_hiding_timer > 0.) { + interface->options.encode_decode_hiding_timer = 0.; + } else { + interface->options.encode_decode_hiding_timer = 30.; + } + } + } + } + // Chart Options + if (interface->setup_win.selected_section == setup_chart_selected) { + if (interface->setup_win.indentation_level == 1) { + if (interface->setup_win.options_selected[0] == setup_chart_reverse) { + interface->options.plot_left_to_right = + !interface->options.plot_left_to_right; + } + if (interface->setup_win.options_selected[0] >= setup_chart_all_gpu) { + handle_setup_win_keypress(KEY_RIGHT, interface); + } + } else if (interface->setup_win.indentation_level == 2) { + if (interface->setup_win.options_selected[0] == setup_chart_all_gpu) { + plot_info_to_draw draw_intersection = 0xffff; + for (unsigned j = 0; j < interface->devices_count; ++j) { + draw_intersection = + draw_intersection & + interface->options.device_information_drawn[j]; + } + if (plot_isset_draw_info(interface->setup_win.options_selected[1], + draw_intersection)) { + for (unsigned i = 0; i < interface->devices_count; ++i) { + interface->options.device_information_drawn[i] = + plot_remove_draw_info( + interface->setup_win.options_selected[1], + interface->options.device_information_drawn[i]); + interface_ring_buffer_empty(&interface->saved_data_ring, i); + } + } else { + for (unsigned i = 0; i < interface->devices_count; ++i) { + interface->options.device_information_drawn[i] = + plot_add_draw_info( + interface->setup_win.options_selected[1], + interface->options.device_information_drawn[i]); + interface_ring_buffer_empty(&interface->saved_data_ring, i); + } + } + } + if (interface->setup_win.options_selected[0] > setup_chart_all_gpu) { + unsigned selected_gpu = interface->setup_win.options_selected[0] - + setup_chart_start_gpu_list; + if (plot_isset_draw_info( + interface->setup_win.options_selected[1], + interface->options.device_information_drawn[selected_gpu])) + interface->options.device_information_drawn[selected_gpu] = + plot_remove_draw_info( + interface->setup_win.options_selected[1], + interface->options + .device_information_drawn[selected_gpu]); + else + interface->options + .device_information_drawn[selected_gpu] = plot_add_draw_info( + interface->setup_win.options_selected[1], + interface->options.device_information_drawn[selected_gpu]); + interface_ring_buffer_empty(&interface->saved_data_ring, + selected_gpu); + } + } + } + // Process List Options + if (interface->setup_win.selected_section == + setup_process_list_selected) { + if (interface->setup_win.indentation_level == 1) { + if (interface->setup_win.options_selected[0] == + setup_proc_list_sort_ascending) { + interface->options.sort_descending_order = + !interface->options.sort_descending_order; + } else if (interface->setup_win.options_selected[0] == + setup_proc_list_sort_by) { + handle_setup_win_keypress(KEY_RIGHT, interface); + } + } else if (interface->setup_win.indentation_level == 2) { + if (interface->setup_win.options_selected[0] == + setup_proc_list_sort_by) { + unsigned index = 0; + for (enum process_field field = process_pid; + field < process_field_count; ++field) { + if (process_is_field_displayed( + field, interface->options.process_fields_displayed)) { + if (index == interface->setup_win.options_selected[1]) + interface->options.sort_processes_by = field; + index++; + } + } + } + if (interface->setup_win.options_selected[0] == + setup_proc_list_display) { + if (process_is_field_displayed( + interface->setup_win.options_selected[1], + interface->options.process_fields_displayed)) { + interface->options.process_fields_displayed = + process_remove_field_to_display( + interface->setup_win.options_selected[1], + interface->options.process_fields_displayed); + } else { + interface->options.process_fields_displayed = + process_add_field_to_display( + interface->setup_win.options_selected[1], + interface->options.process_fields_displayed); + } + if (!process_is_field_displayed( + interface->options.sort_processes_by, + interface->options.process_fields_displayed)) { + interface->options.sort_processes_by = + process_default_sort_by_from( + interface->options.process_fields_displayed); + } + } + } + } + break; + case KEY_F(2): + case 27: + interface->setup_win.visible = false; + update_window_size_to_terminal_size(interface); + break; + case KEY_F(12): + save_interface_options_to_config_file(interface->devices_count, + &interface->options); + break; + default: + break; + } + } +} diff -Nru nvtop-1.1.0/src/nvtop.c nvtop-1.2.2/src/nvtop.c --- nvtop-1.1.0/src/nvtop.c 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/src/nvtop.c 2021-07-24 13:07:26.000000000 +0000 @@ -1,6 +1,6 @@ /* * - * Copyright (C) 2017 Maxime Schmitt + * Copyright (C) 2017-2021 Maxime Schmitt * * This file is part of Nvtop. * @@ -29,7 +29,10 @@ #include +#include "nvtop/extract_gpuinfo.h" #include "nvtop/interface.h" +#include "nvtop/interface_common.h" +#include "nvtop/interface_options.h" #include "nvtop/time.h" #include "nvtop/version.h" @@ -50,12 +53,14 @@ "Available options:\n" " -d --delay : Select the refresh rate (1 == 0.1s)\n" " -v --version : Print the version and exit\n" + " -c --config-file : Provide a custom config file location to load/save " + "preferences\n" " -s --gpu-select : Colon separated list of GPU IDs to monitor\n" " -i --gpu-ignore : Colon separated list of GPU IDs to ignore\n" " -p --no-plot : Disable bar plot\n" - " -r --reverse-abs : Reverse abscissa: plot the recent data left and older on the right\n" + " -r --reverse-abs : Reverse abscissa: plot the recent data left and " + "older on the right\n" " -C --no-color : No colors\n" - " -N --no-cache : Always query the system for user names and command " "line information\n" " -f --freedom-unit : Use fahrenheit\n" " -E --encode-hide : Set encode/decode auto hide time in seconds " @@ -68,9 +73,12 @@ {.name = "delay", .has_arg = required_argument, .flag = NULL, .val = 'd'}, {.name = "version", .has_arg = no_argument, .flag = NULL, .val = 'v'}, {.name = "help", .has_arg = no_argument, .flag = NULL, .val = 'h'}, + {.name = "config-file", + .has_arg = required_argument, + .flag = NULL, + .val = 'c'}, {.name = "no-color", .has_arg = no_argument, .flag = NULL, .val = 'C'}, {.name = "no-colour", .has_arg = no_argument, .flag = NULL, .val = 'C'}, - {.name = "no-cache", .has_arg = no_argument, .flag = NULL, .val = 'N'}, {.name = "freedom-unit", .has_arg = no_argument, .flag = NULL, .val = 'f'}, {.name = "gpu-select", .has_arg = required_argument, @@ -89,7 +97,7 @@ {0, 0, 0, 0}, }; -static const char opts[] = "hvd:s:i:CNfE:pr"; +static const char opts[] = "hvd:s:i:c:CfE:pr"; static size_t update_mask_value(const char *str, size_t entry_mask, bool addTo) { @@ -125,15 +133,17 @@ (void)setlocale(LC_CTYPE, ""); opterr = 0; - int refresh_interval = 1000; + bool update_interval_option_set = false; + int update_interval_option; char *selectedGPU = NULL; char *ignoredGPU = NULL; - bool use_color_if_available = true; - bool cache_pid_infos = true; - bool use_fahrenheit = false; - bool show_plot = true; - bool plot_old_to_recent = true; - double encode_decode_hide_time = 30.; + bool no_color_option = false; + bool use_fahrenheit_option = false; + bool hide_plot_option = false; + bool reverse_plot_direction_option = false; + bool encode_decode_timer_option_set = false; + double encode_decode_hide_time = -1.; + char *custom_config_file_path = NULL; while (true) { int optchar = getopt_long(argc, argv, opts, long_opts, NULL); if (optchar == -1) @@ -151,7 +161,12 @@ fprintf(stderr, "Error: A negative delay requires a time machine!\n"); exit(EXIT_FAILURE); } - refresh_interval = (int)delay_val * 100u; + update_interval_option_set = true; + update_interval_option = (int)delay_val * 100u; + if (update_interval_option > 99900) + update_interval_option = 99900; + if (update_interval_option < 100) + update_interval_option = 100; } break; case 's': selectedGPU = optarg; @@ -165,14 +180,14 @@ case 'h': printf("%s\n%s", versionString, helpstring); exit(EXIT_SUCCESS); - case 'C': - use_color_if_available = false; + case 'c': + custom_config_file_path = optarg; break; - case 'N': - cache_pid_infos = false; + case 'C': + no_color_option = true; break; case 'f': - use_fahrenheit = true; + use_fahrenheit_option = true; break; case 'E': { if (sscanf(optarg, "%lf", &encode_decode_hide_time) == EOF) { @@ -180,12 +195,13 @@ optarg); exit(EXIT_FAILURE); } + encode_decode_timer_option_set = true; } break; case 'p': - show_plot = false; + hide_plot_option = true; break; case 'r': - plot_old_to_recent = false; + reverse_plot_direction_option = true; break; case ':': case '?': @@ -224,56 +240,105 @@ exit(EXIT_FAILURE); } - size_t gpu_mask; + size_t gpu_mask_nvidia; if (selectedGPU != NULL) { - gpu_mask = 0; - gpu_mask = update_mask_value(selectedGPU, gpu_mask, true); + gpu_mask_nvidia = 0; + gpu_mask_nvidia = update_mask_value(selectedGPU, gpu_mask_nvidia, true); } else { - gpu_mask = UINT_MAX; + gpu_mask_nvidia = UINT_MAX; } if (ignoredGPU != NULL) { - gpu_mask = update_mask_value(ignoredGPU, gpu_mask, false); + gpu_mask_nvidia = update_mask_value(ignoredGPU, gpu_mask_nvidia, false); } - if (!init_gpu_info_extraction()) + unsigned devices_count = 0; + gpu_info *devices = NULL; + if (!gpuinfo_init_info_extraction(gpu_mask_nvidia, &devices_count, &devices)) return EXIT_FAILURE; - size_t num_devices; - struct device_info *dev_infos; - num_devices = initialize_device_info(&dev_infos, gpu_mask); - if (num_devices == 0) { - fprintf(stdout, "No GPU left to monitor.\n"); - free(dev_infos); + if (devices_count == 0) { + fprintf(stdout, "No GPU to monitor.\n"); return EXIT_SUCCESS; } + + nvtop_interface_option interface_options; + alloc_interface_options_internals(custom_config_file_path, devices_count, + &interface_options); + load_interface_options_from_config_file(devices_count, &interface_options); + for (unsigned i = 0; i < devices_count; ++i) { + // Nothing specified in the file + if (!plot_isset_draw_info(plot_information_count, + interface_options.device_information_drawn[i])) { + interface_options.device_information_drawn[i] = plot_default_draw_info(); + } else { + interface_options.device_information_drawn[i] = + plot_remove_draw_info(plot_information_count, + interface_options.device_information_drawn[i]); + } + } + if (!process_is_field_displayed(process_field_count, + interface_options.process_fields_displayed)) { + interface_options.process_fields_displayed = + process_default_displayed_field(); + } else { + interface_options.process_fields_displayed = + process_remove_field_to_display( + process_field_count, interface_options.process_fields_displayed); + } + if (no_color_option) + interface_options.use_color = false; + if (hide_plot_option) { + for (unsigned i = 0; i < devices_count; ++i) { + interface_options.device_information_drawn[i] = 0; + } + } + if (encode_decode_timer_option_set) { + interface_options.encode_decode_hiding_timer = encode_decode_hide_time; + if (interface_options.encode_decode_hiding_timer < 0.) + interface_options.encode_decode_hiding_timer = 0.; + } + if (reverse_plot_direction_option) + interface_options.plot_left_to_right = true; + if (use_fahrenheit_option) + interface_options.temperature_in_fahrenheit = true; + if (update_interval_option_set) + interface_options.update_interval = update_interval_option; + + gpuinfo_populate_static_infos(devices_count, devices); + size_t biggest_name = 0; - for (size_t i = 0; i < num_devices; ++i) { - size_t device_name_size = strlen(dev_infos->device_name); + for (unsigned i = 0; i < devices_count; ++i) { + size_t device_name_size; + if (IS_VALID(gpuinfo_device_name_valid, devices[i].static_info.valid)) + device_name_size = strlen(devices[i].static_info.device_name); + else + device_name_size = 4; if (device_name_size > biggest_name) { biggest_name = device_name_size; } } - struct nvtop_interface *interface = initialize_curses( - num_devices, biggest_name, use_color_if_available, use_fahrenheit, - show_plot, plot_old_to_recent, encode_decode_hide_time, refresh_interval); - timeout(refresh_interval); + struct nvtop_interface *interface = + initialize_curses(devices_count, biggest_name, interface_options); + timeout(interface_update_interval(interface)); - double time_slept = refresh_interval; + double time_slept = interface_update_interval(interface); while (!signal_exit) { if (signal_resize_win) { update_window_size_to_terminal_size(interface); signal_resize_win = 0; } - if (!cache_pid_infos) - clean_pid_cache(); - if (time_slept >= refresh_interval) { - update_device_infos(num_devices, dev_infos); - timeout(refresh_interval); + if (time_slept >= interface_update_interval(interface)) { + gpuinfo_refresh_dynamic_info(devices_count, devices); + if (!interface_freeze_processes(interface)) { + gpuinfo_refresh_processes(devices_count, devices); + } + save_current_data_to_ring(devices_count, devices, interface); + timeout(interface_update_interval(interface)); time_slept = 0.; } else { - int next_sleep = (int)((refresh_interval - time_slept)); + int next_sleep = interface_update_interval(interface) - (int)time_slept; timeout(next_sleep); } - draw_gpu_info_ncurses(dev_infos, interface); + draw_gpu_info_ncurses(devices_count, devices, interface); nvtop_time time_before_sleep, time_after_sleep; nvtop_get_current_time(&time_before_sleep); @@ -300,16 +365,18 @@ case 'q': signal_exit = 1; break; + case KEY_F(2): case KEY_F(9): case KEY_F(6): + case KEY_F(12): case '+': case '-': interface_key(input_char, interface); break; - case KEY_LEFT: - case KEY_RIGHT: case KEY_UP: case KEY_DOWN: + case KEY_LEFT: + case KEY_RIGHT: case KEY_ENTER: case '\n': interface_key(input_char, interface); @@ -321,8 +388,7 @@ } clean_ncurses(interface); - clean_device_info(num_devices, dev_infos); - shutdown_gpu_info_extraction(); + gpuinfo_shutdown_info_extraction(devices_count, devices); return EXIT_SUCCESS; } diff -Nru nvtop-1.1.0/src/plot.c nvtop-1.2.2/src/plot.c --- nvtop-1.1.0/src/plot.c 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/src/plot.c 2021-07-24 13:07:26.000000000 +0000 @@ -1,135 +1,135 @@ -#include -#include +/* + * + * Copyright (C) 2019-2021 Maxime Schmitt + * + * This file is part of Nvtop. + * + * Nvtop is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nvtop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nvtop. If not, see . + * + */ #include "nvtop/plot.h" +#include +#include +#include + static inline int data_level(double rows, double data, double increment) { return (int)(rows - round(data / increment)); } void nvtop_line_plot(WINDOW *win, size_t num_data, const double *data, - double min, double max, unsigned num_plots, - const char *legend[]) { + unsigned num_plots, bool legend_left, + char legend[4][PLOT_MAX_LEGEND_SIZE]) { if (num_data == 0) return; int rows, cols; getmaxyx(win, rows, cols); - rows -= 1; // Maximum drawable character - double increment = (max - min) / (double)(rows); - int low_data_level = data_level(0, min, increment); - for (size_t k = 0; k < num_plots; ++k) { - int level_previous = data_level(rows, data[k], increment) - low_data_level; - int level_next, level_current; - wattron(win, COLOR_PAIR(1 + k % 5)); - for (size_t i = k; i < num_data || i < (size_t)cols; i += num_plots) { - level_next = i + num_plots >= num_data - ? level_next - : data_level(rows, data[i + num_plots], increment) - - low_data_level; - level_current = data_level(rows, data[i], increment) - low_data_level; - int top, bottom; - if (level_current == level_previous) { - mvwaddch(win, level_current, i, ACS_HLINE); - top = bottom = level_current; - } else { - if (level_current < level_previous) { - top = level_previous; - bottom = level_current; - } else { - top = level_current; - bottom = level_previous; - } - for (int j = bottom + 1; j < top; j++) { - mvwaddch(win, j, i, ACS_VLINE); - } - if (level_current > level_previous) { - mvwaddch(win, level_current, i, ACS_LLCORNER); - mvwaddch(win, level_previous, i, ACS_URCORNER); - } else { - mvwaddch(win, level_current, i, ACS_ULCORNER); - mvwaddch(win, level_previous, i, ACS_LRCORNER); + rows -= 1; + double increment = 100. / (double)(rows); + + unsigned lvl_before[4]; + for (size_t k = 0; k < num_plots; ++k) + lvl_before[k] = data_level(rows, data[k], increment); + + for (size_t i = 0; i < num_data || i < (size_t)cols; i += num_plots) { + for (unsigned k = 0; k < num_plots; ++k) { + unsigned lvl_now_k = data_level(rows, data[i + k], increment); + wcolor_set(win, k + 1, NULL); + // Three cases: has increased, has decreased and remained level + if (lvl_before[k] < lvl_now_k || lvl_before[k] > lvl_now_k) { + // Case 1 and 2: has increased/decreased + + // An increase goes down on the plot because (0,0) is top left + bool drawing_down = lvl_before[k] < lvl_now_k; + unsigned bottom = drawing_down ? lvl_before[k] : lvl_now_k; + unsigned top = drawing_down ? lvl_now_k : lvl_before[k]; + + // Draw the vertical line corners + mvwaddch(win, bottom, i + k, + drawing_down ? ACS_URCORNER : ACS_ULCORNER); + mvwaddch(win, top, i + k, drawing_down ? ACS_LLCORNER : ACS_LRCORNER); + // Draw the vertical line between the corners + if (top - bottom > 1) { + mvwvline(win, bottom + 1, i + k, 0, top - bottom - 1); } - } - for (unsigned j = 0; j < num_plots; ++j) { - if (j == k) - continue; - int cross_level = -low_data_level; - if (j < k || i + j < num_plots + k) - cross_level += data_level(rows, data[i - k + j], increment); - else - cross_level += - data_level(rows, data[i - k + j - num_plots], increment); - if (cross_level == top && top == bottom) - continue; - if (cross_level > bottom && cross_level < top) { - mvwaddch(win, cross_level, i, ACS_PLUS); - } else { - if (cross_level == top) { - mvwaddch(win, cross_level, i, ACS_BTEE); - } else { - if (cross_level == bottom) { - mvwaddch(win, cross_level, i, ACS_TTEE); - } else { - wattroff(win, COLOR_PAIR(1 + k % 5)); - wattron(win, COLOR_PAIR(1 + j % 5)); - mvwaddch(win, cross_level, i, ACS_HLINE); - wattroff(win, COLOR_PAIR(1 + j % 5)); - wattron(win, COLOR_PAIR(1 + k % 5)); + // Draw the continuation of the other metrics + for (unsigned j = 0; j < num_plots; ++j) { + if (j != k) { + if (lvl_before[j] == top) + // The continuation is at the same level as the bottom corner + mvwaddch(win, top, i + k, ACS_BTEE); + else if (lvl_before[j] == bottom) + // The continuation is at the same level as the top corner + mvwaddch(win, bottom, i + k, ACS_TTEE); + else if (lvl_before[j] > bottom && lvl_before[j] < top) + // The continuation lies on the vertical line + mvwaddch(win, lvl_before[j], i + k, ACS_PLUS); + else { + // The continuation lies outside the update interval so keep the + // color + wcolor_set(win, j + 1, NULL); + mvwaddch(win, lvl_before[j], i + k, ACS_HLINE); + wcolor_set(win, k + 1, NULL); + } + } + } + } else { + // Case 3: stayed level + mvwhline(win, lvl_now_k, i + k, 0, 1); + for (unsigned j = 0; j < num_plots; ++j) { + if (j != k) { + if (lvl_before[j] != lvl_now_k) { + // Add the continuation of other metric lines + wcolor_set(win, j + 1, NULL); + mvwaddch(win, lvl_before[j], i + k, ACS_HLINE); + wcolor_set(win, k + 1, NULL); } } } } - level_previous = level_current; + lvl_before[k] = lvl_now_k; } - wattroff(win, COLOR_PAIR(1 + k % 5)); } int plot_y_position = 0; for (unsigned i = 0; i < num_plots && plot_y_position < rows; ++i) { if (legend[i]) { - size_t length = strlen(legend[i]); - wattron(win, COLOR_PAIR(1 + i % 5)); - if (length < (size_t)cols) { - mvwprintw(win, plot_y_position, cols - length, "%s", legend[i]); + wcolor_set(win, i + 1, NULL); + if (legend_left) { + mvwprintw(win, plot_y_position, 0, "%.*s", cols, legend[i]); } else { - wmove(win, plot_y_position, 0); - for (int j = 0; j < cols; ++j) { - waddch(win, legend[i][j]); + size_t length = strlen(legend[i]); + if (length <= (size_t)cols) { + mvwprintw(win, plot_y_position, cols - length, "%s", legend[i]); + } else { + mvwprintw(win, plot_y_position, 0, "%.*s", length - cols, legend[i]); } } - wattroff(win, COLOR_PAIR(1 + i % 5)); plot_y_position++; } } } -void nvtop_bar_plot(WINDOW *win, size_t num_data, const double *data, - double min, double max) { - if (num_data == 0) - return; - int rows, cols; - getmaxyx(win, rows, cols); - rows -= 1; - double increment = (max - min) / (double)(rows); - int low_data_level = data_level(0, min, increment); - for (size_t i = 0; i < num_data || i < (size_t)cols; ++i) { - for (int j = data_level(rows, data[i], increment) - low_data_level; - j <= rows; j++) { - mvwaddch(win, j, i, ACS_CKBOARD); - } - } -} - void draw_rectangle(WINDOW *win, unsigned startX, unsigned startY, unsigned sizeX, unsigned sizeY) { - for (unsigned i = 0; i < sizeX - 2; ++i) { - mvwaddch(win, startY, startX + 1 + i, ACS_HLINE); - mvwaddch(win, startY + sizeY - 1, startX + 1 + i, ACS_HLINE); - } - for (unsigned i = 0; i < sizeY - 2; ++i) { - mvwaddch(win, startY + 1 + i, startX, ACS_VLINE); - mvwaddch(win, startY + 1 + i, startX + sizeX - 1, ACS_VLINE); - } + mvwhline(win, startY, startX + 1, 0, sizeX - 2); + mvwhline(win, startY + sizeY - 1, startX + 1, 0, sizeX - 2); + + mvwvline(win, startY + 1, startX, 0, sizeY - 2); + mvwvline(win, startY + 1, startX + sizeX - 1, 0, sizeY - 2); + mvwaddch(win, startY, startX, ACS_ULCORNER); mvwaddch(win, startY, startX + sizeX - 1, ACS_URCORNER); mvwaddch(win, startY + sizeY - 1, startX, ACS_LLCORNER); diff -Nru nvtop-1.1.0/TODO nvtop-1.2.2/TODO --- nvtop-1.1.0/TODO 2020-12-03 09:44:36.000000000 +0000 +++ nvtop-1.2.2/TODO 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -What is in the pipeline: - -- Add scrollable lists. - -- Add mouse selection with ncurses. - -- Add nouveau driver support if possible.