diff --git a/CMakeLists.txt b/CMakeLists.txt index c90d5b5..3834567 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ set(SOURCES src/main.cpp src/mainwindow.cpp src/temp_monitor.cpp + src/cpu_monitor.cpp src/temperature_chart.cpp src/config_manager.cpp ${CMAKE_BINARY_DIR}/resources.c @@ -41,6 +42,7 @@ set(SOURCES set(HEADERS include/mainwindow.h include/temp_monitor.h + include/cpu_monitor.h include/temperature_chart.h include/config_manager.h ) diff --git a/include/config_manager.h b/include/config_manager.h index 1237567..cdc3ce6 100644 --- a/include/config_manager.h +++ b/include/config_manager.h @@ -22,6 +22,7 @@ public: bool hasWindowPosition() const { return hasSavedWindowPosition; } int getPollingTime() const { return pollingTime; } int getHistoryLength() const { return historyLength; } + bool getShowPerCoreMonitoring() const { return showPerCoreMonitoring; } void setWindowWidth(int w) { windowWidth = w; } void setWindowHeight(int h) { windowHeight = h; } @@ -30,6 +31,7 @@ public: void clearWindowPosition() { hasSavedWindowPosition = false; } void setPollingTime(int t) { pollingTime = t; } void setHistoryLength(int m) { historyLength = m; } + void setShowPerCoreMonitoring(bool show) { showPerCoreMonitoring = show; } std::map getSensorColors() const { return sensorColors; } std::map getSensorNames() const { return sensorNames; } @@ -50,6 +52,7 @@ private: bool hasSavedWindowPosition; int pollingTime; int historyLength; + bool showPerCoreMonitoring; std::map sensorColors; std::map sensorNames; diff --git a/include/cpu_monitor.h b/include/cpu_monitor.h new file mode 100644 index 0000000..340de80 --- /dev/null +++ b/include/cpu_monitor.h @@ -0,0 +1,44 @@ +#ifndef CPU_MONITOR_H +#define CPU_MONITOR_H + +#include +#include +#include + +struct CpuStats { + double totalUsagePercent; // Overall CPU usage (0-100%) + std::vector coreUsagePercent; // Per-core usage +}; + +class CpuMonitor { +public: + explicit CpuMonitor(); + + // Get overall CPU usage and per-core usage + CpuStats getCpuUsage(); + +private: + struct CpuTick { + int64_t user; + int64_t nice; + int64_t system; + int64_t idle; + int64_t iowait; + int64_t irq; + int64_t softirq; + }; + + struct CpuCoreState { + CpuTick previous; + CpuTick current; + bool initialized; + }; + + std::vector coreStates; // Index 0 is aggregate, 1+ are cores + + std::vector readCpuStats(); + double calculateUsagePercent(const CpuTick &prev, const CpuTick &curr); + int getNumberOfCores(); +}; + +#endif // CPU_MONITOR_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 023c213..c92c137 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -5,6 +5,7 @@ #include #include #include "temp_monitor.h" +#include "cpu_monitor.h" #include "temperature_chart.h" #include "config_manager.h" @@ -27,6 +28,7 @@ private: static void onHistoryLengthChanged(GtkSpinButton *spinButton, gpointer userData); static void onClearButtonClicked(GtkButton *button, gpointer userData); static void onQuitButtonClicked(GtkButton *button, gpointer userData); + static void onShowPerCoreToggled(GtkCheckButton *checkButton, gpointer userData); static void onWindowMap(GtkWidget *widget, gpointer userData); static void onColorSet(GObject *object, GParamSpec *pspec, gpointer userData); static void onNameChanged(GtkEditable *editable, gpointer userData); @@ -41,6 +43,7 @@ private: GtkSpinButton *historyLengthSpinBox; std::unique_ptr chart; std::unique_ptr monitor; + std::unique_ptr cpuMonitor; std::unique_ptr config; guint timerID; int refreshRateSec; diff --git a/include/temperature_chart.h b/include/temperature_chart.h index 1a4ebc0..eb89542 100644 --- a/include/temperature_chart.h +++ b/include/temperature_chart.h @@ -8,9 +8,15 @@ #include #include +enum class DataType { + TEMPERATURE, // Temperature in °C + CPU_LOAD // CPU load in % +}; + struct DataPoint { - double temperature; + double value; int64_t timestamp; + DataType type = DataType::TEMPERATURE; }; struct SeriesData { @@ -19,6 +25,7 @@ struct SeriesData { std::string id; // Unique internal ID (device + sensor) std::string name; // User-friendly display name bool visible = true; + DataType dataType = DataType::TEMPERATURE; }; class TemperatureChart { @@ -30,7 +37,9 @@ public: bool addTemperatureData(const std::string &device, const std::string &sensor, double temperature, int64_t timestamp); + bool addCpuLoadData(const std::string &cpuName, double loadPercent, int64_t timestamp); void clear(); + void clearCpuData(); void draw(); // Get list of series with their colors diff --git a/src/config_manager.cpp b/src/config_manager.cpp index 9a3faca..b396bc0 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -9,7 +9,7 @@ ConfigManager::ConfigManager() : windowWidth(1200), windowHeight(700), windowX(0), windowY(0), hasSavedWindowPosition(false), - pollingTime(1), historyLength(10) + pollingTime(1), historyLength(10), showPerCoreMonitoring(true) { } @@ -85,6 +85,8 @@ void ConfigManager::load() 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) { @@ -121,6 +123,7 @@ void ConfigManager::save() } 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"; diff --git a/src/cpu_monitor.cpp b/src/cpu_monitor.cpp new file mode 100644 index 0000000..506d4ce --- /dev/null +++ b/src/cpu_monitor.cpp @@ -0,0 +1,129 @@ +#include "cpu_monitor.h" +#include +#include +#include +#include +#include +#include + +namespace { +bool isDebugLoggingEnabled() +{ + static const bool enabled = [] { + const char* envValue = std::getenv("TEMP_MONITOR_DEBUG"); + return envValue && std::strcmp(envValue, "1") == 0; + }(); + return enabled; +} +} + +CpuMonitor::CpuMonitor() +{ + if (isDebugLoggingEnabled()) { + std::cerr << "[DEBUG] CpuMonitor constructor - initializing CPU stats" << std::endl; + } + + // Initialize CPU state tracking + std::vector initial = readCpuStats(); + for (const auto &tick : initial) { + CpuCoreState state; + state.previous = tick; + state.current = tick; + state.initialized = false; + coreStates.push_back(state); + } + + if (isDebugLoggingEnabled()) { + std::cerr << "[DEBUG] Initialized " << coreStates.size() << " CPU entries" << std::endl; + } +} + +std::vector CpuMonitor::readCpuStats() +{ + std::vector stats; + std::ifstream statFile("/proc/stat"); + + if (!statFile.is_open()) { + std::cerr << "[ERROR] Cannot open /proc/stat" << std::endl; + return stats; + } + + std::string line; + while (std::getline(statFile, line)) { + if (line.find("cpu") != 0) break; + + std::istringstream iss(line); + std::string cpuLabel; + iss >> cpuLabel; + + // Skip lines that aren't cpu or cpuN + if (cpuLabel != "cpu" && cpuLabel.find("cpu") != 0) continue; + + CpuTick tick; + tick.user = 0; + tick.nice = 0; + tick.system = 0; + tick.idle = 0; + tick.iowait = 0; + tick.irq = 0; + tick.softirq = 0; + + // Read CPU stats: user, nice, system, idle, iowait, irq, softirq + iss >> tick.user >> tick.nice >> tick.system >> tick.idle + >> tick.iowait >> tick.irq >> tick.softirq; + + stats.push_back(tick); + } + + return stats; +} + +double CpuMonitor::calculateUsagePercent(const CpuTick &prev, const CpuTick &curr) +{ + int64_t prevTotal = prev.user + prev.nice + prev.system + prev.idle + + prev.iowait + prev.irq + prev.softirq; + int64_t currTotal = curr.user + curr.nice + curr.system + curr.idle + + curr.iowait + curr.irq + curr.softirq; + + int64_t totalDiff = currTotal - prevTotal; + if (totalDiff == 0) return 0.0; + + int64_t prevIdle = prev.idle + prev.iowait; + int64_t currIdle = curr.idle + curr.iowait; + int64_t idleDiff = currIdle - prevIdle; + + int64_t workDiff = totalDiff - idleDiff; + + double usage = (static_cast(workDiff) / static_cast(totalDiff)) * 100.0; + return std::max(0.0, std::min(100.0, usage)); +} + +CpuStats CpuMonitor::getCpuUsage() +{ + std::vector current = readCpuStats(); + + CpuStats stats; + stats.totalUsagePercent = 0.0; + + // Update states and calculate usage + for (size_t i = 0; i < std::min(current.size(), coreStates.size()); i++) { + coreStates[i].previous = coreStates[i].current; + coreStates[i].current = current[i]; + + if (coreStates[i].initialized) { + double usage = calculateUsagePercent(coreStates[i].previous, coreStates[i].current); + + if (i == 0) { + // Aggregate CPU usage + stats.totalUsagePercent = usage; + } else { + // Per-core usage + stats.coreUsagePercent.push_back(usage); + } + } else { + coreStates[i].initialized = true; + } + } + + return stats; +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3b09613..ec79301 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -21,6 +21,7 @@ MainWindow::MainWindow() timerID(0), refreshRateSec(3), restoreWindowPositionPending(false) { monitor = std::make_unique(); + cpuMonitor = std::make_unique(); config = std::make_unique(); config->load(); @@ -132,6 +133,12 @@ void MainWindow::setupUI() g_signal_connect(clearButton, "clicked", G_CALLBACK(onClearButtonClicked), this); gtk_box_append(GTK_BOX(controlBox), clearButton); + // Per-core CPU monitoring checkbox + GtkWidget *perCoreCheckButton = gtk_check_button_new_with_label("Per-core CPU"); + gtk_check_button_set_active(GTK_CHECK_BUTTON(perCoreCheckButton), config->getShowPerCoreMonitoring()); + g_signal_connect(perCoreCheckButton, "toggled", G_CALLBACK(onShowPerCoreToggled), this); + gtk_box_append(GTK_BOX(controlBox), perCoreCheckButton); + // Status label statusLabel = gtk_label_new("Initializing..."); gtk_label_set_xalign(GTK_LABEL(statusLabel), 0); @@ -211,6 +218,16 @@ void MainWindow::onClearButtonClicked(GtkButton *button, gpointer userData) self->chart->clear(); } +void MainWindow::onShowPerCoreToggled(GtkCheckButton *checkButton, gpointer userData) +{ + 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::onQuitButtonClicked(GtkButton *button, gpointer userData) { (void)button; @@ -297,6 +314,63 @@ void MainWindow::updateTemperatures() } } + // Collect CPU load data + if (cpuMonitor) { + CpuStats cpuStats = cpuMonitor->getCpuUsage(); + + // Add overall CPU usage + if (chart->addCpuLoadData("Overall", cpuStats.totalUsagePercent, currentTime)) { + needsLegendUpdate = true; + + // Apply saved settings for new CPU series + std::string seriesId = "CPU - Overall"; + 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 per-core CPU usage if available and enabled + if (config->getShowPerCoreMonitoring()) { + for (size_t i = 0; i < cpuStats.coreUsagePercent.size(); i++) { + std::string coreName = "Core " + std::to_string(i); + if (chart->addCpuLoadData(coreName, cpuStats.coreUsagePercent[i], currentTime)) { + needsLegendUpdate = true; + + std::string seriesId = "CPU - " + coreName; + 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); + } + } + } + } + } + + // Update CPU labels in legend + std::string overallSeriesId = "CPU - Overall"; + if (tempLabels.count(overallSeriesId)) { + char buf[16]; + snprintf(buf, sizeof(buf), "%.0f%%", cpuStats.totalUsagePercent); + gtk_label_set_text(GTK_LABEL(tempLabels[overallSeriesId]), buf); + } + } + // Update status label std::time_t now = std::time(nullptr); std::tm *timeinfo = std::localtime(&now); diff --git a/src/temperature_chart.cpp b/src/temperature_chart.cpp index 72fbb0d..0f1bcf0 100644 --- a/src/temperature_chart.cpp +++ b/src/temperature_chart.cpp @@ -189,14 +189,16 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std:: series.color = getColorForSeries(seriesKey); series.id = seriesKey; series.name = seriesKey; + series.dataType = DataType::TEMPERATURE; seriesMap[seriesKey] = series; isNew = true; } // Add data point DataPoint point; - point.temperature = temperature; + point.value = temperature; point.timestamp = timestamp; + point.type = DataType::TEMPERATURE; seriesMap[seriesKey].points.push_back(point); @@ -218,24 +220,129 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std:: for (const auto &entry : seriesMap) { if (!entry.second.visible) continue; for (const auto &p : entry.second.points) { - if (p.temperature < currentMin) currentMin = p.temperature; - if (p.temperature > currentMax) currentMax = p.temperature; + if (p.value < currentMin) currentMin = p.value; + if (p.value > currentMax) currentMax = p.value; hasData = true; } } if (hasData) { - // Round down min to nearest 10, round up max to nearest 10 - minTemp = std::floor(currentMin / 10.0) * 10.0; - maxTemp = std::ceil(currentMax / 10.0) * 10.0; + // Different scaling for CPU load vs temperature + bool hasCpuData = false; + bool hasTemperatureData = false; - // Ensure at least 20 degrees range - if (maxTemp - minTemp < 20.0) { - maxTemp = minTemp + 20.0; + for (const auto &entry : seriesMap) { + if (entry.second.dataType == DataType::CPU_LOAD) hasCpuData = true; + if (entry.second.dataType == DataType::TEMPERATURE) hasTemperatureData = true; } - // Don't let it be too small - if (minTemp > 30.0) minTemp = 30.0; + if (hasCpuData && !hasTemperatureData) { + // CPU load only: 0-100% scale + minTemp = 0.0; + maxTemp = 100.0; + } else if (hasTemperatureData) { + // Temperature data: round to nearest 10 degrees + minTemp = std::floor(currentMin / 10.0) * 10.0; + maxTemp = std::ceil(currentMax / 10.0) * 10.0; + + // Ensure at least 20 degrees range + if (maxTemp - minTemp < 20.0) { + maxTemp = minTemp + 20.0; + } + + // Don't let it be too small + if (minTemp > 30.0) minTemp = 30.0; + } + } else { + minTemp = MIN_TEMP; + maxTemp = MAX_TEMP; + } + + // Update time range - keep dynamic window + maxTime = timestamp; + minTime = timestamp - (static_cast(historyLengthMinutes) * 60 * 1000); + + // Trigger redraw + gtk_widget_queue_draw(drawingArea); + + return isNew; +} + +bool TemperatureChart::addCpuLoadData(const std::string &cpuName, double loadPercent, int64_t timestamp) +{ + std::string seriesKey = "CPU - " + cpuName; + bool isNew = false; + + // Create series if it doesn't exist + if (seriesMap.find(seriesKey) == seriesMap.end()) { + SeriesData series; + series.color = getColorForSeries(seriesKey); + series.id = seriesKey; + series.name = "CPU " + cpuName; + series.dataType = DataType::CPU_LOAD; + seriesMap[seriesKey] = series; + isNew = true; + } + + // Add data point + DataPoint point; + point.value = std::max(0.0, std::min(100.0, loadPercent)); // Clamp to 0-100% + point.timestamp = timestamp; + point.type = DataType::CPU_LOAD; + + seriesMap[seriesKey].points.push_back(point); + + // Keep only historyLengthMinutes of data + int64_t cutoffTime = timestamp - (static_cast(historyLengthMinutes) * 60 * 1000); + for (auto &entry : seriesMap) { + auto &points = entry.second.points; + auto it = points.begin(); + while (it != points.end() && it->timestamp < cutoffTime) { + it = points.erase(it); + } + } + + // Update value range dynamically - similar to temperature data + double currentMin = 1000.0; + double currentMax = -1000.0; + bool hasData = false; + + for (const auto &entry : seriesMap) { + if (!entry.second.visible) continue; + for (const auto &p : entry.second.points) { + if (p.value < currentMin) currentMin = p.value; + if (p.value > currentMax) currentMax = p.value; + hasData = true; + } + } + + if (hasData) { + // Different scaling for CPU load vs temperature + bool hasCpuData = false; + bool hasTemperatureData = false; + + for (const auto &entry : seriesMap) { + if (entry.second.dataType == DataType::CPU_LOAD) hasCpuData = true; + if (entry.second.dataType == DataType::TEMPERATURE) hasTemperatureData = true; + } + + if (hasCpuData && !hasTemperatureData) { + // CPU load only: 0-100% scale + minTemp = 0.0; + maxTemp = 100.0; + } else if (hasTemperatureData) { + // Temperature data: round to nearest 10 degrees + minTemp = std::floor(currentMin / 10.0) * 10.0; + maxTemp = std::ceil(currentMax / 10.0) * 10.0; + + // Ensure at least 20 degrees range + if (maxTemp - minTemp < 20.0) { + maxTemp = minTemp + 20.0; + } + + // Don't let it be too small + if (minTemp > 30.0) minTemp = 30.0; + } } else { minTemp = MIN_TEMP; maxTemp = MAX_TEMP; @@ -262,6 +369,47 @@ void TemperatureChart::clear() gtk_widget_queue_draw(drawingArea); } +void TemperatureChart::clearCpuData() +{ + // Remove all CPU load series while keeping temperature data + auto it = seriesMap.begin(); + while (it != seriesMap.end()) { + if (it->second.dataType == DataType::CPU_LOAD) { + // Also remove from color map + colorMap.erase(it->first); + it = seriesMap.erase(it); + } else { + ++it; + } + } + + // Recalculate temperature range + double currentMin = 1000.0; + double currentMax = -1000.0; + bool hasData = false; + + for (const auto &entry : seriesMap) { + if (!entry.second.visible) continue; + for (const auto &p : entry.second.points) { + if (p.value < currentMin) currentMin = p.value; + if (p.value > currentMax) currentMax = p.value; + hasData = true; + } + } + + if (hasData) { + minTemp = std::floor(currentMin / 10.0) * 10.0; + maxTemp = std::ceil(currentMax / 10.0) * 10.0; + if (maxTemp - minTemp < 20.0) maxTemp = minTemp + 20.0; + if (minTemp > 30.0) minTemp = 30.0; + } else { + minTemp = MIN_TEMP; + maxTemp = MAX_TEMP; + } + + gtk_widget_queue_draw(drawingArea); +} + std::vector> TemperatureChart::getSeriesColors() const { std::vector> result; @@ -316,8 +464,8 @@ void TemperatureChart::setHistoryLength(int minutes) } for (const auto &p : points) { - if (p.temperature < currentMin) currentMin = p.temperature; - if (p.temperature > currentMax) currentMax = p.temperature; + if (p.value < currentMin) currentMin = p.value; + if (p.value > currentMax) currentMax = p.value; hasData = true; } } @@ -376,8 +524,8 @@ void TemperatureChart::setSeriesVisible(const std::string &seriesId, bool visibl for (const auto &entry : seriesMap) { if (!entry.second.visible) continue; for (const auto &p : entry.second.points) { - if (p.temperature < currentMin) currentMin = p.temperature; - if (p.temperature > currentMax) currentMax = p.temperature; + if (p.value < currentMin) currentMin = p.value; + if (p.value > currentMax) currentMax = p.value; hasVisibleData = true; } } @@ -455,12 +603,26 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 10); - // Temperature labels every 10 degrees on Y axis + // Determine if we have CPU-only data to show correct units + bool hasCpuData = false; + bool hasTemperatureData = false; + for (const auto &entry : seriesMap) { + if (entry.second.dataType == DataType::CPU_LOAD) hasCpuData = true; + if (entry.second.dataType == DataType::TEMPERATURE) hasTemperatureData = true; + } + + // Y axis labels with appropriate units for (double temp = minTemp; temp <= maxTemp; temp += 10.0) { double y = marginTop + plotHeight * (1.0 - (temp - minTemp) / (maxTemp - minTemp)); char tempStr[16]; - snprintf(tempStr, sizeof(tempStr), "%.0f°C", temp); + if (hasCpuData && !hasTemperatureData) { + // CPU load only - show as percentage + snprintf(tempStr, sizeof(tempStr), "%.0f%%", temp); + } else { + // Temperature or mixed data - show as Celsius + snprintf(tempStr, sizeof(tempStr), "%.0f°C", temp); + } // Measure text width for proper alignment cairo_text_extents_t extents; @@ -502,7 +664,7 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i // Reverse X axis - newer data on the right, older on the left int64_t historyMs = static_cast(historyLengthMinutes) * 60 * 1000; double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / historyMs); - double y = marginTop + plotHeight * (1.0 - (point.temperature - minTemp) / (maxTemp - minTemp)); + double y = marginTop + plotHeight * (1.0 - (point.value - minTemp) / (maxTemp - minTemp)); if (firstPoint) { cairo_move_to(cr, x, y); @@ -519,7 +681,7 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cr, 16); cairo_move_to(cr, width / 2 - 150, 25); - cairo_show_text(cr, "Temperatures (Real-time)"); + cairo_show_text(cr, "System Monitoring (Real-time)"); } TemperatureChart::NearestPoint TemperatureChart::findNearestDataPoint(double mouseX, double mouseY, int width, int height) @@ -550,14 +712,14 @@ TemperatureChart::NearestPoint TemperatureChart::findNearestDataPoint(double mou for (const DataPoint &point : series.points) { int64_t historyMs = static_cast(historyLengthMinutes) * 60 * 1000; double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / historyMs); - double y = marginTop + plotHeight * (1.0 - (point.temperature - minTemp) / (maxTemp - minTemp)); + double y = marginTop + plotHeight * (1.0 - (point.value - minTemp) / (maxTemp - minTemp)); double distance = std::hypot(mouseX - x, mouseY - y); if (distance < result.distance) { result.distance = distance; result.found = true; - result.temperature = point.temperature; + result.temperature = point.value; result.timestamp = point.timestamp; result.seriesId = series.id; result.seriesName = series.name; @@ -588,16 +750,19 @@ void TemperatureChart::showTooltip(double x, double y, const NearestPoint &point double minSeriesTemp = point.temperature; double maxSeriesTemp = point.temperature; double avgSeriesTemp = point.temperature; + DataType dataType = DataType::TEMPERATURE; // Default + auto seriesIt = seriesMap.find(point.seriesId); if (seriesIt != seriesMap.end() && !seriesIt->second.points.empty()) { + dataType = seriesIt->second.dataType; const auto &points = seriesIt->second.points; - minSeriesTemp = points.front().temperature; - maxSeriesTemp = points.front().temperature; + minSeriesTemp = points.front().value; + maxSeriesTemp = points.front().value; double sum = 0.0; for (const auto &dataPoint : points) { - minSeriesTemp = std::min(minSeriesTemp, dataPoint.temperature); - maxSeriesTemp = std::max(maxSeriesTemp, dataPoint.temperature); - sum += dataPoint.temperature; + minSeriesTemp = std::min(minSeriesTemp, dataPoint.value); + maxSeriesTemp = std::max(maxSeriesTemp, dataPoint.value); + sum += dataPoint.value; } avgSeriesTemp = sum / points.size(); } @@ -612,13 +777,18 @@ void TemperatureChart::showTooltip(double x, double y, const NearestPoint &point } char tooltipText[512]; + const char *unit = (dataType == DataType::CPU_LOAD) ? "%" : "°C"; snprintf(tooltipText, sizeof(tooltipText), - "%s\nAktualni: %.1f°C\nMinimum: %.1f°C\nMaximum: %.1f°C\nPrumer: %.1f°C\n%s", + "%s\nAktualni: %.1f%s\nMinimum: %.1f%s\nMaximum: %.1f%s\nPrumer: %.1f%s\n%s", point.seriesName.c_str(), point.temperature, + unit, minSeriesTemp, + unit, maxSeriesTemp, + unit, avgSeriesTemp, + unit, timeStr); gtk_label_set_text(GTK_LABEL(tooltipLabel), tooltipText);