added cpu usage
This commit is contained in:
parent
563ccdd8dc
commit
3a0dcb8aba
@ -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
|
||||
)
|
||||
|
||||
@ -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
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 <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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
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)
|
||||
{
|
||||
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);
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user