uptime-kuma/server/socket-handlers/chart-socket-handler.js
circlecrystalin 4a4f91e36b Refactor numeric history to use aggregation tables (same approach as ping)
- Add numeric_value, numeric_min, numeric_max fields to stat_minutely, stat_hourly, stat_daily tables
- Modify UptimeCalculator to aggregate numeric values instead of storing directly
- Update monitor.js and SNMP monitor to pass numeric values to uptime calculator
- Update chart handler to query from aggregated stat tables instead of monitor_numeric_history
- Add cleanup logic for monitor_numeric_history table (keeps last 7 days for transition)
- Fixes GC issue where monitor_numeric_history table was never cleaned up
- Prevents UI performance issues for long-running monitors
2026-01-16 12:11:16 +01:00

95 lines
3.2 KiB
JavaScript

const { checkLogin } = require("../util-server");
const { UptimeCalculator } = require("../uptime-calculator");
const { log } = require("../../src/util");
const { R } = require("redbean-node");
const dayjs = require("dayjs");
module.exports.chartSocketHandler = (socket) => {
socket.on("getMonitorChartData", async (monitorID, period, callback) => {
try {
checkLogin(socket);
log.debug("monitor", `Get Monitor Chart Data: ${monitorID} User ID: ${socket.userID}`);
if (period == null) {
throw new Error("Invalid period.");
}
let uptimeCalculator = await UptimeCalculator.getUptimeCalculator(monitorID);
let data;
if (period <= 24) {
data = uptimeCalculator.getDataArray(period * 60, "minute");
} else if (period <= 720) {
data = uptimeCalculator.getDataArray(period, "hour");
} else {
data = uptimeCalculator.getDataArray(period / 24, "day");
}
callback({
ok: true,
data,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("getMonitorNumericHistory", async (monitorID, period, callback) => {
try {
checkLogin(socket);
log.debug("monitor", `Get Monitor Numeric History: ${monitorID} User ID: ${socket.userID}`);
if (period == null) {
throw new Error("Invalid period.");
}
const periodHours = parseInt(period);
const startTime = dayjs.utc().subtract(periodHours, "hour");
const startTimestamp = startTime.unix();
// Determine which stat table to query based on period
// Similar to getMonitorChartData logic
let statTable;
if (periodHours <= 24) {
statTable = "stat_minutely";
} else if (periodHours <= 720) {
statTable = "stat_hourly";
} else {
statTable = "stat_daily";
}
// Query numeric history data from aggregated stat tables
const numericHistory = await R.getAll(
`SELECT numeric_value, numeric_min, numeric_max, timestamp
FROM ${statTable}
WHERE monitor_id = ? AND timestamp >= ? AND numeric_value IS NOT NULL
ORDER BY timestamp ASC`,
[monitorID, startTimestamp]
);
// Convert to format expected by frontend
// Use numeric_value as the main value, with min/max for reference
const data = numericHistory.map((row) => ({
value: parseFloat(row.numeric_value),
timestamp: parseInt(row.timestamp),
time: dayjs.unix(row.timestamp).utc().format("YYYY-MM-DD HH:mm:ss"),
}));
callback({
ok: true,
data,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
};