From ecdc527492019c0b71668ec88ce5d561eaff8ab3 Mon Sep 17 00:00:00 2001 From: Basavaraja Mattihalli <10070421+Basavaraja-MS@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:14:51 +0530 Subject: [PATCH] Add PCIe RX/TX load to the utilization charts Plot the PCIe receive and transmit throughput as a percentage of the maximum link bandwidth, alongside the existing GPU and memory utilization graphs. The load is derived from the already-collected pcie_rx/pcie_tx counters normalized against the link's theoretical peak (max gen x width), so it shares the charts' 0-100% scale. The two new metrics (pcieRxRate / pcieTxRate) can be toggled per GPU from the F2 setup window and persist in the configuration file. The normalization helpers live in a dedicated, unit-tested translation unit (pcie_utilization.c). Closes #268 Co-authored-by: Cursor --- README.markdown | 4 +++ include/nvtop/interface_common.h | 2 ++ include/nvtop/pcie_utilization.h | 37 ++++++++++++++++++++ manpage/nvtop.in | 2 +- src/CMakeLists.txt | 1 + src/interface.c | 15 ++++++++ src/interface_options.c | 3 +- src/interface_setup_win.c | 6 ++-- src/pcie_utilization.c | 60 ++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/interfaceTests.cpp | 56 +++++++++++++++++++++++++++++ 11 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 include/nvtop/pcie_utilization.h create mode 100644 src/pcie_utilization.c diff --git a/README.markdown b/README.markdown index d9594c0f..5a1f52f3 100644 --- a/README.markdown +++ b/README.markdown @@ -62,6 +62,10 @@ NVTOP Options and Interactive Commands 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. +In the ``Chart`` section you can choose which metrics are plotted, including GPU and memory +utilization, temperature, power, clocks, and the **PCIe RX / TX load** (the receive and transmit +throughput as a percentage of the maximum link bandwidth). + ![NVTOP Setup Window](/screenshot/Nvtop-config.png) ### Saving Preferences diff --git a/include/nvtop/interface_common.h b/include/nvtop/interface_common.h index 306298a7..54e73d4b 100644 --- a/include/nvtop/interface_common.h +++ b/include/nvtop/interface_common.h @@ -33,6 +33,8 @@ enum plot_information { plot_gpu_clock_rate, plot_gpu_mem_clock_rate, plot_effective_load_rate, + plot_pcie_rx_rate, + plot_pcie_tx_rate, plot_information_count }; diff --git a/include/nvtop/pcie_utilization.h b/include/nvtop/pcie_utilization.h new file mode 100644 index 00000000..965fbaf1 --- /dev/null +++ b/include/nvtop/pcie_utilization.h @@ -0,0 +1,37 @@ +/* + * + * Copyright (C) 2024 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 PCIE_UTILIZATION_H__ +#define PCIE_UTILIZATION_H__ + +#include "nvtop/extract_gpuinfo_common.h" + +// Returns the maximum unidirectional PCIe bandwidth in KB/s derived from the link +// generation and width, or 0 if it cannot be determined. The per-lane figures use the +// effective data rate after PCIe encoding overhead (8b/10b for gen 1/2, 128b/130b for +// gen 3+, FLIT for gen 6). +unsigned pcie_max_bandwidth_kbs(const struct gpuinfo_static_info *static_info); + +// Converts an instantaneous PCIe throughput (KB/s) to a 0-100% load relative to the +// maximum link bandwidth. Returns 0 when the maximum bandwidth cannot be determined. +unsigned pcie_load_percent(unsigned value_kbs, const struct gpuinfo_static_info *static_info); + +#endif // PCIE_UTILIZATION_H__ diff --git a/manpage/nvtop.in b/manpage/nvtop.in index 8a7a3c41..b1b128e8 100644 --- a/manpage/nvtop.in +++ b/manpage/nvtop.in @@ -53,7 +53,7 @@ This section deals with general interface options. \fBColor support\fR and \fBin 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. +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. The available metrics are GPU utilization, GPU memory utilization, encoder rate, decoder rate, temperature, power draw rate, fan speed, GPU clock rate, GPU memory clock rate, effective load rate and PCIe RX / TX load. The PCIe RX and TX load are the receive and transmit throughput expressed as a percentage of the maximum link bandwidth (link generation times link width). .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. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b485cb40..cbe07fbc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(nvtop interface_setup_win.c interface_ring_buffer.c extract_gpuinfo.c + pcie_utilization.c time.c plot.c ini.c diff --git a/src/interface.c b/src/interface.c index ae231991..9732d977 100644 --- a/src/interface.c +++ b/src/interface.c @@ -28,6 +28,7 @@ #include "nvtop/interface_options.h" #include "nvtop/interface_ring_buffer.h" #include "nvtop/interface_setup_win.h" +#include "nvtop/pcie_utilization.h" #include "nvtop/plot.h" #include "nvtop/time.h" @@ -1736,6 +1737,14 @@ void save_current_data_to_ring(struct list_head *devices, struct nvtop_interface data_val = device->dynamic_info.effective_load_rate; } break; + case plot_pcie_rx_rate: + if (GPUINFO_DYNAMIC_FIELD_VALID(&device->dynamic_info, pcie_rx)) + data_val = pcie_load_percent(device->dynamic_info.pcie_rx, &device->static_info); + break; + case plot_pcie_tx_rate: + if (GPUINFO_DYNAMIC_FIELD_VALID(&device->dynamic_info, pcie_tx)) + data_val = pcie_load_percent(device->dynamic_info.pcie_tx, &device->static_info); + break; case plot_information_count: break; } @@ -1805,6 +1814,12 @@ static unsigned populate_plot_data_from_ring_buffer(const struct nvtop_interface case plot_effective_load_rate: snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u eff. load%%", dev_id); break; + case plot_pcie_rx_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u PCIe RX%%", dev_id); + break; + case plot_pcie_tx_rate: + snprintf(plot_legend[in_processing], PLOT_MAX_LEGEND_SIZE, "GPU%u PCIe TX%%", dev_id); + break; case plot_information_count: break; } diff --git a/src/interface_options.c b/src/interface_options.c index c482f3eb..7617dd55 100644 --- a/src/interface_options.c +++ b/src/interface_options.c @@ -202,7 +202,8 @@ static const char device_monitor[] = "Monitor"; 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", "effectiveLoadRate", "none"}; + "powerDrawRate", "fanSpeed", "gpuClockRate", "gpuMemClockRate", "effectiveLoadRate", + "pcieRxRate", "pcieTxRate", "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; diff --git a/src/interface_setup_win.c b/src/interface_setup_win.c index 27d685ea..59be2ce3 100644 --- a/src/interface_setup_win.c +++ b/src/interface_setup_win.c @@ -79,9 +79,9 @@ static const char *setup_chart_all_gpu_description = "Displayed all GPUs"; static const char *setup_chart_gpu_description = "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", "Effective load rate"}; + "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", "Effective load rate", "PCIe RX load rate", "PCIe TX load rate"}; static const char *chart_color_names[] = {"Red", "Cyan", "Green", "Yellow", "Blue", "Magenta", "White"}; static const unsigned chart_color_names_count = ARRAY_SIZE(chart_color_names); diff --git a/src/pcie_utilization.c b/src/pcie_utilization.c new file mode 100644 index 00000000..35392baa --- /dev/null +++ b/src/pcie_utilization.c @@ -0,0 +1,60 @@ +/* + * + * Copyright (C) 2024 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/pcie_utilization.h" + +unsigned pcie_max_bandwidth_kbs(const struct gpuinfo_static_info *static_info) { + if (!GPUINFO_STATIC_FIELD_VALID(static_info, max_pcie_gen) || + !GPUINFO_STATIC_FIELD_VALID(static_info, max_pcie_link_width)) + return 0; + unsigned per_lane_kbs; + switch (static_info->max_pcie_gen) { + case 1: + per_lane_kbs = 250000u; + break; + case 2: + per_lane_kbs = 500000u; + break; + case 3: + per_lane_kbs = 984600u; + break; + case 4: + per_lane_kbs = 1969000u; + break; + case 5: + per_lane_kbs = 3938000u; + break; + case 6: + per_lane_kbs = 7563000u; + break; + default: + return 0; + } + return per_lane_kbs * static_info->max_pcie_link_width; +} + +unsigned pcie_load_percent(unsigned value_kbs, const struct gpuinfo_static_info *static_info) { + unsigned max_bw = pcie_max_bandwidth_kbs(static_info); + if (max_bw == 0) + return 0; + unsigned percent = (unsigned)((unsigned long long)value_kbs * 100u / max_bw); + return percent > 100u ? 100u : percent; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4ac8c77c..894e82ce 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,6 +8,7 @@ if (BUILD_TESTING AND GTest_FOUND) ${PROJECT_SOURCE_DIR}/src/interface_layout_selection.c ${PROJECT_SOURCE_DIR}/src/extract_processinfo_fdinfo.c ${PROJECT_SOURCE_DIR}/src/interface_options.c + ${PROJECT_SOURCE_DIR}/src/pcie_utilization.c ${PROJECT_SOURCE_DIR}/src/ini.c ) target_include_directories(testLib PUBLIC diff --git a/tests/interfaceTests.cpp b/tests/interfaceTests.cpp index 235e45d1..db473601 100644 --- a/tests/interfaceTests.cpp +++ b/tests/interfaceTests.cpp @@ -27,6 +27,7 @@ extern "C" { #include "nvtop/interface.h" #include "nvtop/interface_layout_selection.h" +#include "nvtop/pcie_utilization.h" } static std::ostream &operator<<(std::ostream &os, const struct window_position &win) { @@ -200,6 +201,61 @@ TEST(InterfaceLayout, FixInfiniteLoop) { TEST(InterfaceLayout, LayoutSelection_test_fail_case1) { test_with_terminal_size(32, 3, 55, 16, 1760); } +namespace { + +// Build a gpuinfo_static_info with valid max PCIe gen / width fields. +struct gpuinfo_static_info make_static_info(unsigned gen, unsigned width) { + struct gpuinfo_static_info info = {}; + SET_GPUINFO_STATIC(&info, max_pcie_gen, gen); + SET_GPUINFO_STATIC(&info, max_pcie_link_width, width); + return info; +} + +} // namespace + +TEST(PcieUtilization, MaxBandwidthPerGeneration) { + // Per-lane effective rate (KB/s) multiplied by the link width. + struct gpuinfo_static_info gen1 = make_static_info(1, 16); + struct gpuinfo_static_info gen2 = make_static_info(2, 16); + struct gpuinfo_static_info gen3 = make_static_info(3, 16); + struct gpuinfo_static_info gen4 = make_static_info(4, 16); + struct gpuinfo_static_info gen5 = make_static_info(5, 16); + struct gpuinfo_static_info gen6 = make_static_info(6, 16); + struct gpuinfo_static_info gen5x4 = make_static_info(5, 4); + EXPECT_EQ(pcie_max_bandwidth_kbs(&gen1), 250000u * 16u); + EXPECT_EQ(pcie_max_bandwidth_kbs(&gen2), 500000u * 16u); + EXPECT_EQ(pcie_max_bandwidth_kbs(&gen3), 984600u * 16u); + EXPECT_EQ(pcie_max_bandwidth_kbs(&gen4), 1969000u * 16u); + EXPECT_EQ(pcie_max_bandwidth_kbs(&gen5), 3938000u * 16u); + EXPECT_EQ(pcie_max_bandwidth_kbs(&gen6), 7563000u * 16u); + // Width scales the bandwidth linearly. + EXPECT_EQ(pcie_max_bandwidth_kbs(&gen5x4), 3938000u * 4u); +} + +TEST(PcieUtilization, MaxBandwidthUnknownReturnsZero) { + // Unsupported / unknown generation. + struct gpuinfo_static_info gen7 = make_static_info(7, 16); + EXPECT_EQ(pcie_max_bandwidth_kbs(&gen7), 0u); + // Missing validity flags. + struct gpuinfo_static_info no_info = {}; + EXPECT_EQ(pcie_max_bandwidth_kbs(&no_info), 0u); +} + +TEST(PcieUtilization, LoadPercentClampsAndScales) { + struct gpuinfo_static_info info = make_static_info(5, 16); // 63,008,000 KB/s max + unsigned max_bw = 3938000u * 16u; + EXPECT_EQ(pcie_load_percent(0u, &info), 0u); + EXPECT_EQ(pcie_load_percent(max_bw / 2u, &info), 50u); + EXPECT_EQ(pcie_load_percent(max_bw, &info), 100u); + // Above the theoretical max is clamped to 100%. + EXPECT_EQ(pcie_load_percent(max_bw * 2u, &info), 100u); +} + +TEST(PcieUtilization, LoadPercentNoMaxReturnsZero) { + struct gpuinfo_static_info no_info = {}; + EXPECT_EQ(pcie_load_percent(1000000u, &no_info), 0u); +} + #ifdef THOROUGH_TESTING TEST(InterfaceLayout, CheckManyTermSize) {