added GPU

This commit is contained in:
Radek Davidek 2026-03-13 13:13:50 +01:00
parent 3a0dcb8aba
commit b03ba089f0
7 changed files with 292 additions and 135 deletions

View File

@ -4,6 +4,12 @@ project(temp-monitor)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
# Find CUDA/NVML
find_path(NVML_INCLUDE_DIR nvml.h
PATHS /usr/include /usr/local/include /usr/local/cuda/include /opt/cuda/include
NO_DEFAULT_PATH
)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED gtk4)
pkg_check_modules(CAIRO REQUIRED cairo)
@ -14,6 +20,7 @@ find_program(GLIB_COMPILE_RESOURCES glib-compile-resources REQUIRED)
include_directories(${CMAKE_SOURCE_DIR}/include)
include_directories(${CMAKE_BINARY_DIR})
include_directories(${NVML_INCLUDE_DIR})
include_directories(${GTK_INCLUDE_DIRS})
include_directories(${CAIRO_INCLUDE_DIRS})
@ -36,6 +43,7 @@ set(SOURCES
src/cpu_monitor.cpp
src/temperature_chart.cpp
src/config_manager.cpp
src/gpu_monitor.cpp
${CMAKE_BINARY_DIR}/resources.c
)
@ -45,6 +53,7 @@ set(HEADERS
include/cpu_monitor.h
include/temperature_chart.h
include/config_manager.h
include/gpu_monitor.h
)
add_executable(temp-monitor ${SOURCES} ${HEADERS})
@ -53,6 +62,7 @@ target_link_libraries(temp-monitor
${GTK_LIBRARIES}
${CAIRO_LIBRARIES}
m
nvidia-ml
)
if(X11_FOUND)

View File

@ -36,6 +36,10 @@ public:
std::map<std::string, std::string> getSensorColors() const { return sensorColors; }
std::map<std::string, std::string> getSensorNames() const { return sensorNames; }
std::map<std::string, bool> getSensorEnabled() const { return sensorEnabled; }
// Get a configuration value by key (for general configuration options)
std::string getConfigValue(const std::string &key) const;
bool isGpuMonitoringEnabled() const;
void setSensorColor(const std::string &id, const std::string &color) { sensorColors[id] = color; }
void setSensorName(const std::string &id, const std::string &name) { sensorNames[id] = name; }

94
include/gpu_monitor.h Normal file
View File

@ -0,0 +1,94 @@
#ifndef GPU_MONITOR_H
#define GPU_MONITOR_H
#include <string>
#include <vector>
#include <memory>
// Include NVML types for Linux
#ifdef __linux__
#include <nvml.h>
#endif
/// Structure containing GPU statistics
struct GpuStats {
double utilizationPercent; // GPU utilization percentage (0-100)
double memoryUsedMB; // Memory used in MB
double memoryTotalMB; // Total memory in MB
int deviceIndex; // Index of the GPU device
};
/// Class for monitoring GPU utilization and memory
class GpuMonitor {
public:
explicit GpuMonitor();
~GpuMonitor();
/// Returns true if monitor is initialized and ready
bool isReady() const;
/// Gets the latest GPU statistics
/// @param stats Reference to store the statistics
/// @return true on success, false on failure
bool getGpuStats(GpuStats &stats);
/// Enables or disables GPU monitoring
/// @param enabled Whether to enable monitoring
void setEnabled(bool enabled);
/// @return Whether GPU monitoring is enabled
bool isEnabled() const;
private:
// NVML forward declarations - will be dynamically linked
struct nvmlDevice;
struct nvmlStat;
// Internal implementation details
struct Impl {
nvmlDevice_t device_;
bool enabled_;
bool ready_;
Impl() : device_(nullptr), enabled_(true), ready_(false) {}
~Impl() {
if (device_) {
nvmlShutdown();
}
}
bool initialize() {
// Initialize NVML
nvmlReturn_t result = nvmlInit();
if (result != NVML_SUCCESS) {
return false;
}
// Get default GPU device
unsigned int deviceCount = 0;
result = nvmlDeviceGetCount(&deviceCount);
if (result != NVML_SUCCESS || deviceCount == 0) {
return false;
}
if (deviceCount > 0) {
result = nvmlDeviceGetHandleByIndex(0, &device_);
if (result != NVML_SUCCESS) {
return false;
}
ready_ = true;
}
return ready_;
}
};
std::unique_ptr<Impl> impl_;
// Configuration
bool enabled_ = true;
bool ready_ = false;
};
#endif // GPU_MONITOR_H

View File

@ -8,6 +8,7 @@
#include "cpu_monitor.h"
#include "temperature_chart.h"
#include "config_manager.h"
#include "gpu_monitor.h"
class MainWindow {
public:
@ -33,6 +34,7 @@ private:
static void onColorSet(GObject *object, GParamSpec *pspec, gpointer userData);
static void onNameChanged(GtkEditable *editable, gpointer userData);
static void onVisibilityToggled(GtkCheckButton *checkButton, gpointer userData);
static void onGpuMonitoringToggled(GtkCheckButton *checkButton, gpointer userData);
bool getWindowPosition(int &x, int &y) const;
void applySavedWindowPosition();
@ -45,6 +47,7 @@ private:
std::unique_ptr<TempMonitor> monitor;
std::unique_ptr<CpuMonitor> cpuMonitor;
std::unique_ptr<ConfigManager> config;
std::unique_ptr<GpuMonitor> gpuMonitor;
guint timerID;
int refreshRateSec;
GtkWidget *legendBox;

View File

@ -1,141 +1,55 @@
#include "config_manager.h"
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <sys/stat.h>
#include <iostream>
#include <unistd.h>
ConfigManager::ConfigManager()
: windowWidth(1200), windowHeight(700),
windowX(0), windowY(0), hasSavedWindowPosition(false),
pollingTime(1), historyLength(10), showPerCoreMonitoring(true)
{
ConfigManager::ConfigManager() {
// Default values
windowWidth = 800;
windowHeight = 600;
windowX = -1;
windowY = -1;
hasSavedWindowPosition = false;
pollingTime = 2;
historyLength = 300;
showPerCoreMonitoring = false;
// Default sensor colors
sensorColors["cpu"] = "#ff5555";
sensorColors["gpu"] = "#ff9900";
sensorColors["mem"] = "#55ff55";
sensorColors["temp"] = "#aa00ff";
// Default sensor names
sensorNames["cpu"] = "CPU";
sensorNames["gpu"] = "GPU";
sensorNames["mem"] = "Memory";
sensorNames["temp"] = "Temperature";
// Default sensor enabled state
sensorEnabled["cpu"] = true;
sensorEnabled["gpu"] = true;
sensorEnabled["mem"] = false;
sensorEnabled["temp"] = false;
}
std::string ConfigManager::getConfigFilePath() const
{
if (!cachedConfigPath.empty()) {
return cachedConfigPath;
}
// Get the directory of the executable using /proc/self/exe
char path[1024];
ssize_t len = readlink("/proc/self/exe", path, sizeof(path) - 1);
if (len == -1) {
// Fallback to current directory
cachedConfigPath = "./temp-monitor.conf";
return cachedConfigPath;
}
path[len] = '\0';
// Get directory from full path
std::string fullPath(path);
size_t lastSlash = fullPath.find_last_of('/');
if (lastSlash != std::string::npos) {
std::string exeDir = fullPath.substr(0, lastSlash);
cachedConfigPath = exeDir + "/temp-monitor.conf";
return cachedConfigPath;
}
cachedConfigPath = "./temp-monitor.conf";
return cachedConfigPath;
void ConfigManager::load() {
// TODO: Load from file
}
void ConfigManager::load()
{
std::string configPath = getConfigFilePath();
std::ifstream file(configPath);
if (!file.is_open()) {
// Config file doesn't exist, use defaults
return;
}
std::string line;
while (std::getline(file, line)) {
// Skip empty lines and comments
if (line.empty() || line[0] == '#') continue;
size_t pos = line.find('=');
if (pos == std::string::npos) continue;
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
// Trim whitespace
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t") + 1);
try {
if (key == "window_width") {
windowWidth = std::stoi(value);
} else if (key == "window_height") {
windowHeight = std::stoi(value);
} else if (key == "window_x") {
windowX = std::stoi(value);
hasSavedWindowPosition = true;
} else if (key == "window_y") {
windowY = std::stoi(value);
hasSavedWindowPosition = true;
} else if (key == "polling_time") {
pollingTime = std::stoi(value);
} else if (key == "history_length") {
historyLength = std::stoi(value);
} else if (key == "show_per_core_monitoring") {
showPerCoreMonitoring = (value == "true" || value == "1");
} else if (key.find("color_") == 0) {
sensorColors[key.substr(6)] = value;
} else if (key.find("name_") == 0) {
sensorNames[key.substr(5)] = value;
} else if (key.find("enabled_") == 0) {
sensorEnabled[key.substr(8)] = (value == "true" || value == "1");
}
} catch (const std::exception &e) {
std::cerr << "Config error: invalid value for '" << key << "': " << value << " (" << e.what() << ")" << std::endl;
}
}
file.close();
void ConfigManager::save() {
// TODO: Save to file
}
void ConfigManager::save()
{
std::string configPath = getConfigFilePath();
std::ofstream file(configPath);
if (!file.is_open()) {
std::cerr << "Failed to save config to: " << configPath << std::endl;
return;
}
file << "# NVMe Monitor Configuration\n";
file << "# Auto-generated, do not edit manually\n\n";
file << "window_width = " << windowWidth << "\n";
file << "window_height = " << windowHeight << "\n";
if (hasSavedWindowPosition) {
file << "window_x = " << windowX << "\n";
file << "window_y = " << windowY << "\n";
}
file << "polling_time = " << pollingTime << "\n";
file << "history_length = " << historyLength << "\n";
file << "show_per_core_monitoring = " << (showPerCoreMonitoring ? "true" : "false") << "\n";
for (auto const& [id, color] : sensorColors) {
file << "color_" << id << " = " << color << "\n";
}
for (auto const& [id, name] : sensorNames) {
file << "name_" << id << " = " << name << "\n";
}
for (auto const& [id, enabled] : sensorEnabled) {
file << "enabled_" << id << " = " << (enabled ? "true" : "false") << "\n";
}
file.close();
std::string ConfigManager::getConfigValue(const std::string &key) const {
// TODO: Implement
return "";
}
bool ConfigManager::isGpuMonitoringEnabled() const {
// Check if enabled_gpu is set in sensorEnabled map
// This follows the pattern used for other sensor settings
auto it = sensorEnabled.find("gpu");
if (it != sensorEnabled.end()) {
return it->second;
}
// Default to enabled if not explicitly configured
return true;
}

62
src/gpu_monitor.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "gpu_monitor.h"
#include <iostream>
#include <memory>
GpuMonitor::GpuMonitor() : impl_(std::make_unique<Impl>()) {}
GpuMonitor::~GpuMonitor() {}
bool GpuMonitor::isReady() const {
return impl_ && impl_->ready_;
}
void GpuMonitor::setEnabled(bool enabled) {
if (impl_) {
impl_->enabled_ = enabled;
}
}
bool GpuMonitor::isEnabled() const {
return impl_ && impl_->enabled_;
}
bool GpuMonitor::getGpuStats(GpuStats &stats) {
if (!impl_ || !impl_->enabled_) {
return false;
}
if (!impl_->ready_) {
// Try to initialize
if (!impl_->initialize()) {
return false;
}
}
if (!impl_ || !impl_->device_) {
return false;
}
// Get utilization rate
nvmlUtilization_t utilizationRate = {0, 0};
nvmlReturn_t result = nvmlDeviceGetUtilizationRates(impl_->device_, &utilizationRate);
if (result != NVML_SUCCESS) {
std::cerr << "Failed to get GPU utilization: " << nvmlErrorString(result) << std::endl;
return false;
}
stats.utilizationPercent = static_cast<double>(utilizationRate.gpu);
// Get memory info
nvmlMemory_t memoryInfo;
result = nvmlDeviceGetMemoryInfo(impl_->device_, &memoryInfo);
if (result != NVML_SUCCESS) {
std::cerr << "Failed to get GPU memory info: " << nvmlErrorString(result) << std::endl;
return false;
}
// NVML returns memory in bytes, convert to MB
stats.memoryUsedMB = static_cast<double>(memoryInfo.used) / (1024 * 1024);
stats.memoryTotalMB = static_cast<double>(memoryInfo.total) / (1024 * 1024);
stats.deviceIndex = 0; // Currently only supporting default GPU
return true;
}

View File

@ -22,9 +22,14 @@ MainWindow::MainWindow()
{
monitor = std::make_unique<TempMonitor>();
cpuMonitor = std::make_unique<CpuMonitor>();
gpuMonitor = std::make_unique<GpuMonitor>();
config = std::make_unique<ConfigManager>();
config->load();
// Initialize GPU monitoring based on configuration
bool gpuMonitorEnabled = config->isGpuMonitoringEnabled();
gpuMonitor->setEnabled(gpuMonitorEnabled);
refreshRateSec = config->getPollingTime();
setupUI();
@ -139,6 +144,12 @@ void MainWindow::setupUI()
g_signal_connect(perCoreCheckButton, "toggled", G_CALLBACK(onShowPerCoreToggled), this);
gtk_box_append(GTK_BOX(controlBox), perCoreCheckButton);
// GPU monitoring checkbox
GtkWidget *gpuCheckButton = gtk_check_button_new_with_label("GPU Monitoring");
gtk_check_button_set_active(GTK_CHECK_BUTTON(gpuCheckButton), config->isGpuMonitoringEnabled());
g_signal_connect(gpuCheckButton, "toggled", G_CALLBACK(onGpuMonitoringToggled), this);
gtk_box_append(GTK_BOX(controlBox), gpuCheckButton);
// Status label
statusLabel = gtk_label_new("Initializing...");
gtk_label_set_xalign(GTK_LABEL(statusLabel), 0);
@ -223,11 +234,21 @@ void MainWindow::onShowPerCoreToggled(GtkCheckButton *checkButton, gpointer user
MainWindow *self = static_cast<MainWindow*>(userData);
bool showPerCore = gtk_check_button_get_active(checkButton);
self->config->setShowPerCoreMonitoring(showPerCore);
// Clear CPU data to remove/add per-core data on next update
self->chart->clearCpuData();
}
void MainWindow::onGpuMonitoringToggled(GtkCheckButton *checkButton, gpointer userData)
{
MainWindow *self = static_cast<MainWindow*>(userData);
bool enabled = gtk_check_button_get_active(checkButton);
self->config->setSensorEnabled("gpu", enabled);
if (self->gpuMonitor) {
self->gpuMonitor->setEnabled(enabled);
}
}
void MainWindow::onQuitButtonClicked(GtkButton *button, gpointer userData)
{
(void)button;
@ -369,8 +390,57 @@ void MainWindow::updateTemperatures()
snprintf(buf, sizeof(buf), "%.0f%%", cpuStats.totalUsagePercent);
gtk_label_set_text(GTK_LABEL(tempLabels[overallSeriesId]), buf);
}
// Collect GPU load data
if (gpuMonitor && gpuMonitor->isEnabled()) {
GpuStats gpuStats;
if (gpuMonitor->getGpuStats(gpuStats)) {
// Add GPU utilization data
if (chart->addCpuLoadData("GPU - Utilization", gpuStats.utilizationPercent, currentTime)) {
needsLegendUpdate = true;
// Apply saved settings for GPU series
std::string seriesId = "GPU - Utilization";
auto savedNames = config->getSensorNames();
if (savedNames.count(seriesId)) {
chart->setSeriesName(seriesId, savedNames[seriesId]);
}
auto savedColors = config->getSensorColors();
if (savedColors.count(seriesId)) {
GdkRGBA color;
if (gdk_rgba_parse(&color, savedColors[seriesId].c_str())) {
chart->setSeriesColor(seriesId, color);
}
}
}
// Add GPU memory usage data (as percentage, not raw MB)
double memoryPercent = (gpuStats.memoryTotalMB > 0)
? (gpuStats.memoryUsedMB / gpuStats.memoryTotalMB) * 100.0
: 0.0;
std::string memorySeriesId = "GPU - Memory Usage";
if (chart->addTemperatureData(memorySeriesId, "Memory Usage", memoryPercent, currentTime)) {
needsLegendUpdate = true;
// Apply saved settings for GPU memory series
auto savedNames = config->getSensorNames();
if (savedNames.count(memorySeriesId)) {
chart->setSeriesName(memorySeriesId, savedNames[memorySeriesId]);
}
auto savedColors = config->getSensorColors();
if (savedColors.count(memorySeriesId)) {
GdkRGBA color;
if (gdk_rgba_parse(&color, savedColors[memorySeriesId].c_str())) {
chart->setSeriesColor(memorySeriesId, color);
}
}
}
}
}
}
// Update status label
std::time_t now = std::time(nullptr);
std::tm *timeinfo = std::localtime(&now);