diff --git a/db/knex_migrations/2023-11-10-slow-response-visual-improvements.js b/db/knex_migrations/2023-11-10-slow-response-visual-improvements.js new file mode 100644 index 000000000..d85e1cfd9 --- /dev/null +++ b/db/knex_migrations/2023-11-10-slow-response-visual-improvements.js @@ -0,0 +1,21 @@ +exports.up = function (knex) { + // add various slow response parameters + return knex.schema + .alterTable("heartbeat", function (table) { + table.integer("ping_status").nullable().defaultTo(null); + table.integer("ping_threshold").nullable().defaultTo(null); + table.boolean("ping_important").notNullable().defaultTo(0); + table.string("ping_msg").nullable().defaultTo(null); + }); +}; + +exports.down = function (knex) { + // remove various slow response parameters + return knex.schema + .alterTable("heartbeat", function (table) { + table.dropColumn("ping_status"); + table.dropColumn("ping_threshold"); + table.dropColumn("ping_important"); + table.dropColumn("ping_msg"); + }); +}; diff --git a/package-lock.json b/package-lock.json index e82b8ebfb..a539370c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97,6 +97,7 @@ "bootstrap": "5.1.3", "chart.js": "~4.2.1", "chartjs-adapter-dayjs-4": "~1.0.4", + "chartjs-plugin-annotation": "~3.0.1", "concurrently": "^7.1.0", "core-js": "~3.26.1", "cronstrue": "~2.24.0", @@ -6062,6 +6063,15 @@ "dayjs": "^1.9.7" } }, + "node_modules/chartjs-plugin-annotation": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/chartjs-plugin-annotation/-/chartjs-plugin-annotation-3.0.1.tgz", + "integrity": "sha512-hlIrXXKqSDgb+ZjVYHefmlZUXK8KbkCPiynSVrTb/HjTMkT62cOInaT1NTQCKtxKKOm9oHp958DY3RTAFKtkHg==", + "dev": true, + "peerDependencies": { + "chart.js": ">=4.0.0" + } + }, "node_modules/check-more-types": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", diff --git a/package.json b/package.json index b73b015fb..cb20cd307 100644 --- a/package.json +++ b/package.json @@ -163,6 +163,7 @@ "bootstrap": "5.1.3", "chart.js": "~4.2.1", "chartjs-adapter-dayjs-4": "~1.0.4", + "chartjs-plugin-annotation": "~3.0.1", "concurrently": "^7.1.0", "core-js": "~3.26.1", "cronstrue": "~2.24.0", diff --git a/server/client.js b/server/client.js index 260e77a73..cfa208dcc 100644 --- a/server/client.js +++ b/server/client.js @@ -54,7 +54,7 @@ async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = monitorID, ]); - let result = list.reverse(); + let result = R.convertToBeans("heartbeat", list.reverse()); if (toUser) { io.to(socket.userID).emit("heartbeatList", monitorID, result, overwrite); diff --git a/server/model/heartbeat.js b/server/model/heartbeat.js index 9e972a376..648921203 100644 --- a/server/model/heartbeat.js +++ b/server/model/heartbeat.js @@ -6,6 +6,9 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); * 1 = UP * 2 = PENDING * 3 = MAINTENANCE + * pingStatus: + * 4 = SLOW + * 5 = NOMINAL */ class Heartbeat extends BeanModel { @@ -37,6 +40,10 @@ class Heartbeat extends BeanModel { important: this._important, duration: this._duration, retries: this._retries, + pingThreshold: this._pingThreshold, + pingStatus: this._pingStatus, + pingImportant: this._pingImportant, + pingMsg: this._pingMsg, }; } diff --git a/server/model/monitor.js b/server/model/monitor.js index 87a6f2753..52f5ba659 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1,8 +1,7 @@ const dayjs = require("dayjs"); const axios = require("axios"); const { Prometheus } = require("../prometheus"); -const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, - SQL_DATETIME_FORMAT +const { log, UP, DOWN, PENDING, MAINTENANCE, NOMINAL, SLOW, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, SQL_DATETIME_FORMAT } = require("../../src/util"); const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery, redisPingAsync, mongodbPing, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal @@ -34,6 +33,9 @@ const rootCertificates = rootCertificatesFingerprints(); * 1 = UP * 2 = PENDING * 3 = MAINTENANCE + * pingStatus: + * 4 = SLOW + * 5 = NOMINAL */ class Monitor extends BeanModel { @@ -1565,7 +1567,7 @@ class Monitor extends BeanModel { // Create stats to append to messages/logs const methodDescription = [ "average", "max" ].includes(method) ? `${method} of last ${windowDuration}s` : method; let msgStats = `Response: ${actualResponseTime}ms (${methodDescription}) | Threshold: ${threshold}ms (${thresholdDescription})`; - // Add window duration for methods that make sense + let pingMsg = `${actualResponseTime}ms resp. (${methodDescription})`; // Verify valid response time was calculated if (actualResponseTime === 0 || !Number.isInteger(actualResponseTime)) { @@ -1579,8 +1581,11 @@ class Monitor extends BeanModel { return; } + bean.pingThreshold = threshold; + // Responding normally if (actualResponseTime < threshold) { + bean.pingStatus = NOMINAL; if (bean.slowResponseCount === 0) { log.debug("monitor", `[${this.name}] Responding normally. No need to send slow response notification | ${msgStats}`); } else { @@ -1588,6 +1593,11 @@ class Monitor extends BeanModel { log.debug("monitor", `[${this.name}] Returned to normal response time | ${msgStats}`); let msg = `[${this.name}] Returned to Normal Response Time \n${msgStats}`; Monitor.sendSlowResponseNotification(monitor, bean, msg); + + // Mark important (SLOW -> NOMINAL) + pingMsg += ` < ${threshold}ms`; + bean.pingImportant = true; + bean.pingMsg = pingMsg; } // Reset slow response count @@ -1595,6 +1605,7 @@ class Monitor extends BeanModel { // Responding slowly } else { + bean.pingStatus = SLOW; ++bean.slowResponseCount; // Always send first notification @@ -1602,6 +1613,12 @@ class Monitor extends BeanModel { log.debug("monitor", `[${this.name}] Responded slowly, sending notification | ${msgStats}`); let msg = `[${this.name}] Responded Slowly \n${msgStats}`; Monitor.sendSlowResponseNotification(monitor, bean, msg); + + // Mark important (NOMINAL -> SLOW) + pingMsg += ` > ${threshold}ms`; + bean.pingImportant = true; + bean.pingMsg = pingMsg; + // Send notification every x times } else if (this.slowResponseNotificationResendInterval > 0) { if (((bean.slowResponseCount) % this.slowResponseNotificationResendInterval) === 0) { diff --git a/server/server.js b/server/server.js index 7ae3b6249..9a20773be 100644 --- a/server/server.js +++ b/server/server.js @@ -1199,9 +1199,9 @@ let needSetup = false; let count; if (monitorID == null) { - count = await R.count("heartbeat", "important = 1"); + count = await R.count("heartbeat", "important = 1 OR ping_important = 1"); } else { - count = await R.count("heartbeat", "monitor_id = ? AND important = 1", [ + count = await R.count("heartbeat", "monitor_id = ? AND (important = 1 OR ping_important = 1)", [ monitorID, ]); } @@ -1225,7 +1225,7 @@ let needSetup = false; let list; if (monitorID == null) { list = await R.find("heartbeat", ` - important = 1 + important = 1 OR ping_important = 1 ORDER BY time DESC LIMIT ? OFFSET ? @@ -1236,7 +1236,7 @@ let needSetup = false; } else { list = await R.find("heartbeat", ` monitor_id = ? - AND important = 1 + AND (important = 1 OR ping_important = 1) ORDER BY time DESC LIMIT ? OFFSET ? @@ -1449,7 +1449,9 @@ let needSetup = false; log.info("manage", `Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`); - await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", [ + await R.exec("UPDATE heartbeat SET msg = ?, important = ?, ping_msg = ?, ping_important = ? WHERE monitor_id = ? ", [ + "", + "0", "", "0", monitorID, diff --git a/src/components/PingChart.vue b/src/components/PingChart.vue index c550b4b4f..dd9db4e3f 100644 --- a/src/components/PingChart.vue +++ b/src/components/PingChart.vue @@ -19,11 +19,12 @@