added cpu usage

This commit is contained in:
Radek Davidek 2026-03-11 17:02:13 +01:00
parent 563ccdd8dc
commit 3a0dcb8aba
9 changed files with 466 additions and 29 deletions

View File

@ -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
)

View File

@ -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<std::string, std::string> getSensorColors() const { return sensorColors; }
std::map<std::string, std::string> getSensorNames() const { return sensorNames; }
@ -50,6 +52,7 @@ private:
bool hasSavedWindowPosition;
int pollingTime;
int historyLength;
bool showPerCoreMonitoring;
std::map<std::string, std::string> sensorColors;
std::map<std::string, std::string> sensorNames;

44
include/cpu_monitor.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef CPU_MONITOR_H
#define CPU_MONITOR_H
#include <string>
#include <vector>
#include <cstdint>
struct CpuStats {
double totalUsagePercent; // Overall CPU usage (0-100%)
std::vector<double> 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<CpuCoreState> coreStates; // Index 0 is aggregate, 1+ are cores
std::vector<CpuTick> readCpuStats();
double calculateUsagePercent(const CpuTick &prev, const CpuTick &curr);
int getNumberOfCores();
};
#endif // CPU_MONITOR_H

View File

@ -5,6 +5,7 @@
#include <memory>
#include <map>
#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<TemperatureChart> chart;
std::unique_ptr<TempMonitor> monitor;
std::unique_ptr<CpuMonitor> cpuMonitor;
std::unique_ptr<ConfigManager> config;
guint timerID;
int refreshRateSec;

View File

@ -8,9 +8,15 @@
#include <map>
#include <cstdint>
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

View File

@ -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";

129
src/cpu_monitor.cpp Normal file
View File

@ -0,0 +1,129 @@
#include "cpu_monitor.h"
#include <fstream>
#include <sstream>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unistd.h>
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<CpuTick> 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::CpuTick> CpuMonitor::readCpuStats()
{
std::vector<CpuTick> 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<double>(workDiff) / static_cast<double>(totalDiff)) * 100.0;
return std::max(0.0, std::min(100.0, usage));
}
CpuStats CpuMonitor::getCpuUsage()
{
std::vector<CpuTick> 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;
}

View File

@ -21,6 +21,7 @@ MainWindow::MainWindow()
timerID(0), refreshRateSec(3), restoreWindowPositionPending(false)
{
monitor = std::make_unique<TempMonitor>();
cpuMonitor = std::make_unique<CpuMonitor>();
config = std::make_unique<ConfigManager>();
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<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::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);

View File

@ -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<int64_t>(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<int64_t>(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<std::pair<std::string, GdkRGBA>> TemperatureChart::getSeriesColors() const
{
std::vector<std::pair<std::string, GdkRGBA>> 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<int64_t>(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<int64_t>(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);