From b8858f46058b8951aee9d9c0aa25f42d3657f5e0 Mon Sep 17 00:00:00 2001 From: Nelson Chan <3271800+chakflying@users.noreply.github.com> Date: Wed, 27 Mar 2024 10:19:25 +0800 Subject: [PATCH] Feat: Handle maintenance in `UptimeCalculator` (#4406) Co-authored-by: Frank Elsinga --- .../2024-01-22-0000-stats-extras.js | 26 ++++++ server/uptime-calculator.js | 86 +++++++++++++++---- 2 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 db/knex_migrations/2024-01-22-0000-stats-extras.js diff --git a/db/knex_migrations/2024-01-22-0000-stats-extras.js b/db/knex_migrations/2024-01-22-0000-stats-extras.js new file mode 100644 index 000000000..b92e8892a --- /dev/null +++ b/db/knex_migrations/2024-01-22-0000-stats-extras.js @@ -0,0 +1,26 @@ +exports.up = function (knex) { + return knex.schema + .alterTable("stat_daily", function (table) { + table.text("extras").defaultTo(null).comment("Extra statistics during this time period"); + }) + .alterTable("stat_minutely", function (table) { + table.text("extras").defaultTo(null).comment("Extra statistics during this time period"); + }) + .alterTable("stat_hourly", function (table) { + table.text("extras").defaultTo(null).comment("Extra statistics during this time period"); + }); + +}; + +exports.down = function (knex) { + return knex.schema + .alterTable("stat_daily", function (table) { + table.dropColumn("extras"); + }) + .alterTable("stat_minutely", function (table) { + table.dropColumn("extras"); + }) + .alterTable("stat_hourly", function (table) { + table.dropColumn("extras"); + }); +}; diff --git a/server/uptime-calculator.js b/server/uptime-calculator.js index 9632e6eac..7b5326748 100644 --- a/server/uptime-calculator.js +++ b/server/uptime-calculator.js @@ -116,14 +116,23 @@ class UptimeCalculator { ]); for (let bean of minutelyStatBeans) { - let key = bean.timestamp; - this.minutelyUptimeDataList.push(key, { + let data = { up: bean.up, down: bean.down, avgPing: bean.ping, minPing: bean.pingMin, maxPing: bean.pingMax, - }); + }; + + if (bean.extras != null) { + data = { + ...data, + ...JSON.parse(bean.extras), + }; + } + + let key = bean.timestamp; + this.minutelyUptimeDataList.push(key, data); } // Load hourly data from database (recent 30 days only) @@ -133,14 +142,22 @@ class UptimeCalculator { ]); for (let bean of hourlyStatBeans) { - let key = bean.timestamp; - this.hourlyUptimeDataList.push(key, { + let data = { up: bean.up, down: bean.down, avgPing: bean.ping, minPing: bean.pingMin, maxPing: bean.pingMax, - }); + }; + + if (bean.extras != null) { + data = { + ...data, + ...JSON.parse(bean.extras), + }; + } + + this.hourlyUptimeDataList.push(bean.timestamp, data); } // Load daily data from database (recent 365 days only) @@ -150,14 +167,22 @@ class UptimeCalculator { ]); for (let bean of dailyStatBeans) { - let key = bean.timestamp; - this.dailyUptimeDataList.push(key, { + let data = { up: bean.up, down: bean.down, avgPing: bean.ping, minPing: bean.pingMin, maxPing: bean.pingMax, - }); + }; + + if (bean.extras != null) { + data = { + ...data, + ...JSON.parse(bean.extras), + }; + } + + this.dailyUptimeDataList.push(bean.timestamp, data); } } @@ -170,11 +195,6 @@ class UptimeCalculator { async update(status, ping = 0) { let date = this.getCurrentDate(); - // Don't count MAINTENANCE into uptime - if (status === MAINTENANCE) { - return date; - } - let flatStatus = this.flatStatus(status); if (flatStatus === DOWN && ping > 0) { @@ -189,7 +209,12 @@ class UptimeCalculator { let hourlyData = this.hourlyUptimeDataList[hourlyKey]; let dailyData = this.dailyUptimeDataList[dailyKey]; - if (flatStatus === UP) { + if (status === MAINTENANCE) { + minutelyData.maintenance = minutelyData.maintenance ? minutelyData.maintenance + 1 : 1; + hourlyData.maintenance = hourlyData.maintenance ? hourlyData.maintenance + 1 : 1; + dailyData.maintenance = dailyData.maintenance ? dailyData.maintenance + 1 : 1; + + } else if (flatStatus === UP) { minutelyData.up += 1; hourlyData.up += 1; dailyData.up += 1; @@ -233,7 +258,7 @@ class UptimeCalculator { } } - } else { + } else if (flatStatus === DOWN) { minutelyData.down += 1; hourlyData.down += 1; dailyData.down += 1; @@ -263,6 +288,13 @@ class UptimeCalculator { dailyStatBean.ping = dailyData.avgPing; dailyStatBean.pingMin = dailyData.minPing; dailyStatBean.pingMax = dailyData.maxPing; + { + // eslint-disable-next-line no-unused-vars + const { up, down, avgPing, minPing, maxPing, ...extras } = dailyData; + if (Object.keys(extras).length > 0) { + dailyStatBean.extras = JSON.stringify(extras); + } + } await R.store(dailyStatBean); let hourlyStatBean = await this.getHourlyStatBean(hourlyKey); @@ -271,6 +303,13 @@ class UptimeCalculator { hourlyStatBean.ping = hourlyData.avgPing; hourlyStatBean.pingMin = hourlyData.minPing; hourlyStatBean.pingMax = hourlyData.maxPing; + { + // eslint-disable-next-line no-unused-vars + const { up, down, avgPing, minPing, maxPing, ...extras } = hourlyData; + if (Object.keys(extras).length > 0) { + hourlyStatBean.extras = JSON.stringify(extras); + } + } await R.store(hourlyStatBean); let minutelyStatBean = await this.getMinutelyStatBean(divisionKey); @@ -279,6 +318,13 @@ class UptimeCalculator { minutelyStatBean.ping = minutelyData.avgPing; minutelyStatBean.pingMin = minutelyData.minPing; minutelyStatBean.pingMax = minutelyData.maxPing; + { + // eslint-disable-next-line no-unused-vars + const { up, down, avgPing, minPing, maxPing, ...extras } = minutelyData; + if (Object.keys(extras).length > 0) { + minutelyStatBean.extras = JSON.stringify(extras); + } + } await R.store(minutelyStatBean); // Remove the old data @@ -474,7 +520,7 @@ class UptimeCalculator { flatStatus(status) { switch (status) { case UP: - // case MAINTENANCE: + case MAINTENANCE: return UP; case DOWN: case PENDING: @@ -606,7 +652,11 @@ class UptimeCalculator { avgPing = totalPing / total.up; } - uptimeData.uptime = total.up / (total.up + total.down); + if (total.up + total.down === 0) { + uptimeData.uptime = 0; + } else { + uptimeData.uptime = total.up / (total.up + total.down); + } uptimeData.avgPing = avgPing; return uptimeData; }