added cpu usage
This commit is contained in:
parent
563ccdd8dc
commit
3a0dcb8aba
@ -33,6 +33,7 @@ set(SOURCES
|
|||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/mainwindow.cpp
|
src/mainwindow.cpp
|
||||||
src/temp_monitor.cpp
|
src/temp_monitor.cpp
|
||||||
|
src/cpu_monitor.cpp
|
||||||
src/temperature_chart.cpp
|
src/temperature_chart.cpp
|
||||||
src/config_manager.cpp
|
src/config_manager.cpp
|
||||||
${CMAKE_BINARY_DIR}/resources.c
|
${CMAKE_BINARY_DIR}/resources.c
|
||||||
@ -41,6 +42,7 @@ set(SOURCES
|
|||||||
set(HEADERS
|
set(HEADERS
|
||||||
include/mainwindow.h
|
include/mainwindow.h
|
||||||
include/temp_monitor.h
|
include/temp_monitor.h
|
||||||
|
include/cpu_monitor.h
|
||||||
include/temperature_chart.h
|
include/temperature_chart.h
|
||||||
include/config_manager.h
|
include/config_manager.h
|
||||||
)
|
)
|
||||||
|
|||||||
@ -22,6 +22,7 @@ public:
|
|||||||
bool hasWindowPosition() const { return hasSavedWindowPosition; }
|
bool hasWindowPosition() const { return hasSavedWindowPosition; }
|
||||||
int getPollingTime() const { return pollingTime; }
|
int getPollingTime() const { return pollingTime; }
|
||||||
int getHistoryLength() const { return historyLength; }
|
int getHistoryLength() const { return historyLength; }
|
||||||
|
bool getShowPerCoreMonitoring() const { return showPerCoreMonitoring; }
|
||||||
|
|
||||||
void setWindowWidth(int w) { windowWidth = w; }
|
void setWindowWidth(int w) { windowWidth = w; }
|
||||||
void setWindowHeight(int h) { windowHeight = h; }
|
void setWindowHeight(int h) { windowHeight = h; }
|
||||||
@ -30,6 +31,7 @@ public:
|
|||||||
void clearWindowPosition() { hasSavedWindowPosition = false; }
|
void clearWindowPosition() { hasSavedWindowPosition = false; }
|
||||||
void setPollingTime(int t) { pollingTime = t; }
|
void setPollingTime(int t) { pollingTime = t; }
|
||||||
void setHistoryLength(int m) { historyLength = m; }
|
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> getSensorColors() const { return sensorColors; }
|
||||||
std::map<std::string, std::string> getSensorNames() const { return sensorNames; }
|
std::map<std::string, std::string> getSensorNames() const { return sensorNames; }
|
||||||
@ -50,6 +52,7 @@ private:
|
|||||||
bool hasSavedWindowPosition;
|
bool hasSavedWindowPosition;
|
||||||
int pollingTime;
|
int pollingTime;
|
||||||
int historyLength;
|
int historyLength;
|
||||||
|
bool showPerCoreMonitoring;
|
||||||
|
|
||||||
std::map<std::string, std::string> sensorColors;
|
std::map<std::string, std::string> sensorColors;
|
||||||
std::map<std::string, std::string> sensorNames;
|
std::map<std::string, std::string> sensorNames;
|
||||||
|
|||||||
44
include/cpu_monitor.h
Normal file
44
include/cpu_monitor.h
Normal 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
|
||||||
@ -5,6 +5,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "temp_monitor.h"
|
#include "temp_monitor.h"
|
||||||
|
#include "cpu_monitor.h"
|
||||||
#include "temperature_chart.h"
|
#include "temperature_chart.h"
|
||||||
#include "config_manager.h"
|
#include "config_manager.h"
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ private:
|
|||||||
static void onHistoryLengthChanged(GtkSpinButton *spinButton, gpointer userData);
|
static void onHistoryLengthChanged(GtkSpinButton *spinButton, gpointer userData);
|
||||||
static void onClearButtonClicked(GtkButton *button, gpointer userData);
|
static void onClearButtonClicked(GtkButton *button, gpointer userData);
|
||||||
static void onQuitButtonClicked(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 onWindowMap(GtkWidget *widget, gpointer userData);
|
||||||
static void onColorSet(GObject *object, GParamSpec *pspec, gpointer userData);
|
static void onColorSet(GObject *object, GParamSpec *pspec, gpointer userData);
|
||||||
static void onNameChanged(GtkEditable *editable, gpointer userData);
|
static void onNameChanged(GtkEditable *editable, gpointer userData);
|
||||||
@ -41,6 +43,7 @@ private:
|
|||||||
GtkSpinButton *historyLengthSpinBox;
|
GtkSpinButton *historyLengthSpinBox;
|
||||||
std::unique_ptr<TemperatureChart> chart;
|
std::unique_ptr<TemperatureChart> chart;
|
||||||
std::unique_ptr<TempMonitor> monitor;
|
std::unique_ptr<TempMonitor> monitor;
|
||||||
|
std::unique_ptr<CpuMonitor> cpuMonitor;
|
||||||
std::unique_ptr<ConfigManager> config;
|
std::unique_ptr<ConfigManager> config;
|
||||||
guint timerID;
|
guint timerID;
|
||||||
int refreshRateSec;
|
int refreshRateSec;
|
||||||
|
|||||||
@ -8,9 +8,15 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
enum class DataType {
|
||||||
|
TEMPERATURE, // Temperature in °C
|
||||||
|
CPU_LOAD // CPU load in %
|
||||||
|
};
|
||||||
|
|
||||||
struct DataPoint {
|
struct DataPoint {
|
||||||
double temperature;
|
double value;
|
||||||
int64_t timestamp;
|
int64_t timestamp;
|
||||||
|
DataType type = DataType::TEMPERATURE;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SeriesData {
|
struct SeriesData {
|
||||||
@ -19,6 +25,7 @@ struct SeriesData {
|
|||||||
std::string id; // Unique internal ID (device + sensor)
|
std::string id; // Unique internal ID (device + sensor)
|
||||||
std::string name; // User-friendly display name
|
std::string name; // User-friendly display name
|
||||||
bool visible = true;
|
bool visible = true;
|
||||||
|
DataType dataType = DataType::TEMPERATURE;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TemperatureChart {
|
class TemperatureChart {
|
||||||
@ -30,7 +37,9 @@ public:
|
|||||||
|
|
||||||
bool addTemperatureData(const std::string &device, const std::string &sensor,
|
bool addTemperatureData(const std::string &device, const std::string &sensor,
|
||||||
double temperature, int64_t timestamp);
|
double temperature, int64_t timestamp);
|
||||||
|
bool addCpuLoadData(const std::string &cpuName, double loadPercent, int64_t timestamp);
|
||||||
void clear();
|
void clear();
|
||||||
|
void clearCpuData();
|
||||||
void draw();
|
void draw();
|
||||||
|
|
||||||
// Get list of series with their colors
|
// Get list of series with their colors
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
ConfigManager::ConfigManager()
|
ConfigManager::ConfigManager()
|
||||||
: windowWidth(1200), windowHeight(700),
|
: windowWidth(1200), windowHeight(700),
|
||||||
windowX(0), windowY(0), hasSavedWindowPosition(false),
|
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);
|
pollingTime = std::stoi(value);
|
||||||
} else if (key == "history_length") {
|
} else if (key == "history_length") {
|
||||||
historyLength = std::stoi(value);
|
historyLength = std::stoi(value);
|
||||||
|
} else if (key == "show_per_core_monitoring") {
|
||||||
|
showPerCoreMonitoring = (value == "true" || value == "1");
|
||||||
} else if (key.find("color_") == 0) {
|
} else if (key.find("color_") == 0) {
|
||||||
sensorColors[key.substr(6)] = value;
|
sensorColors[key.substr(6)] = value;
|
||||||
} else if (key.find("name_") == 0) {
|
} else if (key.find("name_") == 0) {
|
||||||
@ -121,6 +123,7 @@ void ConfigManager::save()
|
|||||||
}
|
}
|
||||||
file << "polling_time = " << pollingTime << "\n";
|
file << "polling_time = " << pollingTime << "\n";
|
||||||
file << "history_length = " << historyLength << "\n";
|
file << "history_length = " << historyLength << "\n";
|
||||||
|
file << "show_per_core_monitoring = " << (showPerCoreMonitoring ? "true" : "false") << "\n";
|
||||||
|
|
||||||
for (auto const& [id, color] : sensorColors) {
|
for (auto const& [id, color] : sensorColors) {
|
||||||
file << "color_" << id << " = " << color << "\n";
|
file << "color_" << id << " = " << color << "\n";
|
||||||
|
|||||||
129
src/cpu_monitor.cpp
Normal file
129
src/cpu_monitor.cpp
Normal 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;
|
||||||
|
}
|
||||||
@ -21,6 +21,7 @@ MainWindow::MainWindow()
|
|||||||
timerID(0), refreshRateSec(3), restoreWindowPositionPending(false)
|
timerID(0), refreshRateSec(3), restoreWindowPositionPending(false)
|
||||||
{
|
{
|
||||||
monitor = std::make_unique<TempMonitor>();
|
monitor = std::make_unique<TempMonitor>();
|
||||||
|
cpuMonitor = std::make_unique<CpuMonitor>();
|
||||||
config = std::make_unique<ConfigManager>();
|
config = std::make_unique<ConfigManager>();
|
||||||
config->load();
|
config->load();
|
||||||
|
|
||||||
@ -132,6 +133,12 @@ void MainWindow::setupUI()
|
|||||||
g_signal_connect(clearButton, "clicked", G_CALLBACK(onClearButtonClicked), this);
|
g_signal_connect(clearButton, "clicked", G_CALLBACK(onClearButtonClicked), this);
|
||||||
gtk_box_append(GTK_BOX(controlBox), clearButton);
|
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
|
// Status label
|
||||||
statusLabel = gtk_label_new("Initializing...");
|
statusLabel = gtk_label_new("Initializing...");
|
||||||
gtk_label_set_xalign(GTK_LABEL(statusLabel), 0);
|
gtk_label_set_xalign(GTK_LABEL(statusLabel), 0);
|
||||||
@ -211,6 +218,16 @@ void MainWindow::onClearButtonClicked(GtkButton *button, gpointer userData)
|
|||||||
self->chart->clear();
|
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 MainWindow::onQuitButtonClicked(GtkButton *button, gpointer userData)
|
||||||
{
|
{
|
||||||
(void)button;
|
(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
|
// Update status label
|
||||||
std::time_t now = std::time(nullptr);
|
std::time_t now = std::time(nullptr);
|
||||||
std::tm *timeinfo = std::localtime(&now);
|
std::tm *timeinfo = std::localtime(&now);
|
||||||
|
|||||||
@ -189,14 +189,16 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std::
|
|||||||
series.color = getColorForSeries(seriesKey);
|
series.color = getColorForSeries(seriesKey);
|
||||||
series.id = seriesKey;
|
series.id = seriesKey;
|
||||||
series.name = seriesKey;
|
series.name = seriesKey;
|
||||||
|
series.dataType = DataType::TEMPERATURE;
|
||||||
seriesMap[seriesKey] = series;
|
seriesMap[seriesKey] = series;
|
||||||
isNew = true;
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add data point
|
// Add data point
|
||||||
DataPoint point;
|
DataPoint point;
|
||||||
point.temperature = temperature;
|
point.value = temperature;
|
||||||
point.timestamp = timestamp;
|
point.timestamp = timestamp;
|
||||||
|
point.type = DataType::TEMPERATURE;
|
||||||
|
|
||||||
seriesMap[seriesKey].points.push_back(point);
|
seriesMap[seriesKey].points.push_back(point);
|
||||||
|
|
||||||
@ -218,14 +220,28 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std::
|
|||||||
for (const auto &entry : seriesMap) {
|
for (const auto &entry : seriesMap) {
|
||||||
if (!entry.second.visible) continue;
|
if (!entry.second.visible) continue;
|
||||||
for (const auto &p : entry.second.points) {
|
for (const auto &p : entry.second.points) {
|
||||||
if (p.temperature < currentMin) currentMin = p.temperature;
|
if (p.value < currentMin) currentMin = p.value;
|
||||||
if (p.temperature > currentMax) currentMax = p.temperature;
|
if (p.value > currentMax) currentMax = p.value;
|
||||||
hasData = true;
|
hasData = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasData) {
|
if (hasData) {
|
||||||
// Round down min to nearest 10, round up max to nearest 10
|
// 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;
|
minTemp = std::floor(currentMin / 10.0) * 10.0;
|
||||||
maxTemp = std::ceil(currentMax / 10.0) * 10.0;
|
maxTemp = std::ceil(currentMax / 10.0) * 10.0;
|
||||||
|
|
||||||
@ -236,6 +252,97 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std::
|
|||||||
|
|
||||||
// Don't let it be too small
|
// Don't let it be too small
|
||||||
if (minTemp > 30.0) minTemp = 30.0;
|
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 {
|
} else {
|
||||||
minTemp = MIN_TEMP;
|
minTemp = MIN_TEMP;
|
||||||
maxTemp = MAX_TEMP;
|
maxTemp = MAX_TEMP;
|
||||||
@ -262,6 +369,47 @@ void TemperatureChart::clear()
|
|||||||
gtk_widget_queue_draw(drawingArea);
|
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>> TemperatureChart::getSeriesColors() const
|
||||||
{
|
{
|
||||||
std::vector<std::pair<std::string, GdkRGBA>> result;
|
std::vector<std::pair<std::string, GdkRGBA>> result;
|
||||||
@ -316,8 +464,8 @@ void TemperatureChart::setHistoryLength(int minutes)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &p : points) {
|
for (const auto &p : points) {
|
||||||
if (p.temperature < currentMin) currentMin = p.temperature;
|
if (p.value < currentMin) currentMin = p.value;
|
||||||
if (p.temperature > currentMax) currentMax = p.temperature;
|
if (p.value > currentMax) currentMax = p.value;
|
||||||
hasData = true;
|
hasData = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -376,8 +524,8 @@ void TemperatureChart::setSeriesVisible(const std::string &seriesId, bool visibl
|
|||||||
for (const auto &entry : seriesMap) {
|
for (const auto &entry : seriesMap) {
|
||||||
if (!entry.second.visible) continue;
|
if (!entry.second.visible) continue;
|
||||||
for (const auto &p : entry.second.points) {
|
for (const auto &p : entry.second.points) {
|
||||||
if (p.temperature < currentMin) currentMin = p.temperature;
|
if (p.value < currentMin) currentMin = p.value;
|
||||||
if (p.temperature > currentMax) currentMax = p.temperature;
|
if (p.value > currentMax) currentMax = p.value;
|
||||||
hasVisibleData = true;
|
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_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
|
||||||
cairo_set_font_size(cr, 10);
|
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) {
|
for (double temp = minTemp; temp <= maxTemp; temp += 10.0) {
|
||||||
double y = marginTop + plotHeight * (1.0 - (temp - minTemp) / (maxTemp - minTemp));
|
double y = marginTop + plotHeight * (1.0 - (temp - minTemp) / (maxTemp - minTemp));
|
||||||
|
|
||||||
char tempStr[16];
|
char tempStr[16];
|
||||||
|
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);
|
snprintf(tempStr, sizeof(tempStr), "%.0f°C", temp);
|
||||||
|
}
|
||||||
|
|
||||||
// Measure text width for proper alignment
|
// Measure text width for proper alignment
|
||||||
cairo_text_extents_t extents;
|
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
|
// Reverse X axis - newer data on the right, older on the left
|
||||||
int64_t historyMs = static_cast<int64_t>(historyLengthMinutes) * 60 * 1000;
|
int64_t historyMs = static_cast<int64_t>(historyLengthMinutes) * 60 * 1000;
|
||||||
double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / historyMs);
|
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) {
|
if (firstPoint) {
|
||||||
cairo_move_to(cr, x, y);
|
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_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
|
||||||
cairo_set_font_size(cr, 16);
|
cairo_set_font_size(cr, 16);
|
||||||
cairo_move_to(cr, width / 2 - 150, 25);
|
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)
|
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) {
|
for (const DataPoint &point : series.points) {
|
||||||
int64_t historyMs = static_cast<int64_t>(historyLengthMinutes) * 60 * 1000;
|
int64_t historyMs = static_cast<int64_t>(historyLengthMinutes) * 60 * 1000;
|
||||||
double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / historyMs);
|
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);
|
double distance = std::hypot(mouseX - x, mouseY - y);
|
||||||
|
|
||||||
if (distance < result.distance) {
|
if (distance < result.distance) {
|
||||||
result.distance = distance;
|
result.distance = distance;
|
||||||
result.found = true;
|
result.found = true;
|
||||||
result.temperature = point.temperature;
|
result.temperature = point.value;
|
||||||
result.timestamp = point.timestamp;
|
result.timestamp = point.timestamp;
|
||||||
result.seriesId = series.id;
|
result.seriesId = series.id;
|
||||||
result.seriesName = series.name;
|
result.seriesName = series.name;
|
||||||
@ -588,16 +750,19 @@ void TemperatureChart::showTooltip(double x, double y, const NearestPoint &point
|
|||||||
double minSeriesTemp = point.temperature;
|
double minSeriesTemp = point.temperature;
|
||||||
double maxSeriesTemp = point.temperature;
|
double maxSeriesTemp = point.temperature;
|
||||||
double avgSeriesTemp = point.temperature;
|
double avgSeriesTemp = point.temperature;
|
||||||
|
DataType dataType = DataType::TEMPERATURE; // Default
|
||||||
|
|
||||||
auto seriesIt = seriesMap.find(point.seriesId);
|
auto seriesIt = seriesMap.find(point.seriesId);
|
||||||
if (seriesIt != seriesMap.end() && !seriesIt->second.points.empty()) {
|
if (seriesIt != seriesMap.end() && !seriesIt->second.points.empty()) {
|
||||||
|
dataType = seriesIt->second.dataType;
|
||||||
const auto &points = seriesIt->second.points;
|
const auto &points = seriesIt->second.points;
|
||||||
minSeriesTemp = points.front().temperature;
|
minSeriesTemp = points.front().value;
|
||||||
maxSeriesTemp = points.front().temperature;
|
maxSeriesTemp = points.front().value;
|
||||||
double sum = 0.0;
|
double sum = 0.0;
|
||||||
for (const auto &dataPoint : points) {
|
for (const auto &dataPoint : points) {
|
||||||
minSeriesTemp = std::min(minSeriesTemp, dataPoint.temperature);
|
minSeriesTemp = std::min(minSeriesTemp, dataPoint.value);
|
||||||
maxSeriesTemp = std::max(maxSeriesTemp, dataPoint.temperature);
|
maxSeriesTemp = std::max(maxSeriesTemp, dataPoint.value);
|
||||||
sum += dataPoint.temperature;
|
sum += dataPoint.value;
|
||||||
}
|
}
|
||||||
avgSeriesTemp = sum / points.size();
|
avgSeriesTemp = sum / points.size();
|
||||||
}
|
}
|
||||||
@ -612,13 +777,18 @@ void TemperatureChart::showTooltip(double x, double y, const NearestPoint &point
|
|||||||
}
|
}
|
||||||
|
|
||||||
char tooltipText[512];
|
char tooltipText[512];
|
||||||
|
const char *unit = (dataType == DataType::CPU_LOAD) ? "%" : "°C";
|
||||||
snprintf(tooltipText, sizeof(tooltipText),
|
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.seriesName.c_str(),
|
||||||
point.temperature,
|
point.temperature,
|
||||||
|
unit,
|
||||||
minSeriesTemp,
|
minSeriesTemp,
|
||||||
|
unit,
|
||||||
maxSeriesTemp,
|
maxSeriesTemp,
|
||||||
|
unit,
|
||||||
avgSeriesTemp,
|
avgSeriesTemp,
|
||||||
|
unit,
|
||||||
timeStr);
|
timeStr);
|
||||||
|
|
||||||
gtk_label_set_text(GTK_LABEL(tooltipLabel), tooltipText);
|
gtk_label_set_text(GTK_LABEL(tooltipLabel), tooltipText);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user