diff --git a/CMakeLists.txt b/CMakeLists.txt index 3834567..86dc6b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/config_manager.h b/include/config_manager.h index cdc3ce6..89480cd 100644 --- a/include/config_manager.h +++ b/include/config_manager.h @@ -36,6 +36,10 @@ public: std::map getSensorColors() const { return sensorColors; } std::map getSensorNames() const { return sensorNames; } std::map 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; } diff --git a/include/gpu_monitor.h b/include/gpu_monitor.h new file mode 100644 index 0000000..5705fd4 --- /dev/null +++ b/include/gpu_monitor.h @@ -0,0 +1,94 @@ +#ifndef GPU_MONITOR_H +#define GPU_MONITOR_H + +#include +#include +#include + +// Include NVML types for Linux +#ifdef __linux__ +#include +#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_; + + // Configuration + bool enabled_ = true; + bool ready_ = false; +}; + +#endif // GPU_MONITOR_H \ No newline at end of file diff --git a/include/mainwindow.h b/include/mainwindow.h index c92c137..2922706 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -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 monitor; std::unique_ptr cpuMonitor; std::unique_ptr config; + std::unique_ptr gpuMonitor; guint timerID; int refreshRateSec; GtkWidget *legendBox; diff --git a/src/config_manager.cpp b/src/config_manager.cpp index b396bc0..6b4d329 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -1,141 +1,55 @@ #include "config_manager.h" -#include -#include -#include -#include -#include -#include -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; } diff --git a/src/gpu_monitor.cpp b/src/gpu_monitor.cpp new file mode 100644 index 0000000..5510c52 --- /dev/null +++ b/src/gpu_monitor.cpp @@ -0,0 +1,62 @@ +#include "gpu_monitor.h" +#include +#include + +GpuMonitor::GpuMonitor() : impl_(std::make_unique()) {} + +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(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(memoryInfo.used) / (1024 * 1024); + stats.memoryTotalMB = static_cast(memoryInfo.total) / (1024 * 1024); + stats.deviceIndex = 0; // Currently only supporting default GPU + + return true; +} \ No newline at end of file diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ec79301..c47e126 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -22,9 +22,14 @@ MainWindow::MainWindow() { monitor = std::make_unique(); cpuMonitor = std::make_unique(); + gpuMonitor = std::make_unique(); config = std::make_unique(); 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(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(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);