Reimplement functionality from

https://github.com/louislam/uptime-kuma/pull/1878 with modern
knex_migration
This commit is contained in:
Stephen Papierski 2023-10-26 22:40:44 -06:00
parent 87b2e45fbf
commit daacc2842e
5 changed files with 171 additions and 1 deletions

View file

@ -0,0 +1,20 @@
exports.up = function (knex) {
// add various slow_response_notification parameters
return knex.schema
.alterTable("monitor", function(table) {
table.boolean("slow_response_notification").defaultTo(false);
table.integer("slow_response_notification_threshold").defaultTo(0);
table.integer("slow_response_notification_range").defaultTo(0);
table.string("slow_response_notification_method").defaultTo("");
});
}
exports.down = function (knex) {
return knex.schema
.alterTable("monitor", function(table) {
table.boolean("slow_response_notification").defaultTo(false);
table.integer("slow_response_notification_threshold").defaultTo(0);
table.integer("slow_response_notification_range").defaultTo(0);
table.string("slow_response_notification_method").defaultTo("");
});
}

View file

@ -132,6 +132,10 @@ class Monitor extends BeanModel {
mqttSuccessMessage: this.mqttSuccessMessage,
databaseQuery: this.databaseQuery,
authMethod: this.authMethod,
slowResponseNotification: this.isEnabledSlowResponseNotification(),
slowResponseNotificationThreshold: this.slowResponseNotificationThreshold,
slowResponseNotificationRange: this.slowResponseNotificationRange,
slowResponseNotificationMethod: this.slowResponseNotificationMethod,
grpcUrl: this.grpcUrl,
grpcProtobuf: this.grpcProtobuf,
grpcMethod: this.grpcMethod,
@ -298,6 +302,14 @@ class Monitor extends BeanModel {
return Boolean(this.gamedigGivenPortOnly);
}
/**
* Is the slow response notification enabled?
* @returns {boolean}
*/
isEnabledSlowResponseNotification() {
return Boolean(this.slowResponseNotification);
}
/**
* Start monitor
* @param {Server} io Socket server instance
@ -938,6 +950,11 @@ class Monitor extends BeanModel {
log.debug("monitor", `[${this.name}] Store`);
await R.store(bean);
if (this.isEnabledSlowResponseNotification()) {
log.debug("monitor", `[${this.name}] Check response is slow`);
await this.checkSlowResponseNotification(this);
}
log.debug("monitor", `[${this.name}] prometheus.update`);
this.prometheus?.update(bean, tlsInfo);
@ -1375,6 +1392,71 @@ class Monitor extends BeanModel {
}
}
/**
* Check heartbeat response time is slower than threshold.
* @param {Monitor} monitor The monitor to send a notification about
* @returns {Promise<void>}
*/
async checkSlowResponseNotification(monitor) {
//Get recent heartbeat list with range of time
const afterThisDate = new Date(Date.now() - (1000 * monitor.slowResponseNotificationRange));
const previousBeats = await R.getAll(`
SELECT * FROM heartbeat
WHERE monitor_id = ? AND time > datetime(?) AND status = ?`,
[
monitor.id,
afterThisDate.toISOString(),
UP,
]);
const method = monitor.slowResponseNotificationMethod;
const thresholdResponseTime = monitor.slowResponseNotificationThreshold;
let actualResponseTime = 0;
switch (method) {
case "average":
previousBeats.forEach(beat => {
actualResponseTime = actualResponseTime + beat.ping;
});
actualResponseTime = actualResponseTime / previousBeats.length;
break;
case "max":
previousBeats.forEach(beat => {
actualResponseTime = Math.max(actualResponseTime, beat.ping);
});
break;
default:
log.error("monitor", `[${this.name}] Unknown slow response notification method ${method}`);
return;
}
if (actualResponseTime < thresholdResponseTime) {
log.debug("monitor", `[${this.name}] No need to send slow notification. ${actualResponseTime} < ${thresholdResponseTime}`);
return;
}
log.debug("monitor", `[${this.name}] Try to send slow response notification (${actualResponseTime} > ${thresholdResponseTime})`);
const notificationList = await Monitor.getNotificationList(monitor);
if (notificationList.length > 0) {
for (let notification of notificationList) {
try {
log.debug("monitor", `[${this.name}] Sending to ${notification.name}`);
await Notification.send(JSON.parse(notification.config), `[${this.name}] Responding slowly (${actualResponseTime}ms > ${thresholdResponseTime}ms)`);
} catch (e) {
log.error("monitor", `[${this.name}] Cannot send slow response notification to ${notification.name}`);
log.error("monitor", e);
}
}
} else {
log.debug("monitor", `[${this.name}] No notification, no need to send slow response notification`);
}
}
/**
* Get the status of the previous heartbeat
* @param {number} monitorID ID of monitor to check

View file

@ -43,7 +43,8 @@ log.debug("server", "Arguments");
log.debug("server", args);
if (! process.env.NODE_ENV) {
process.env.NODE_ENV = "production";
// process.env.NODE_ENV = "production";
process.env.NODE_ENV = "development";
}
log.info("server", "Env: " + process.env.NODE_ENV);
@ -803,6 +804,10 @@ let needSetup = false;
bean.authMethod = monitor.authMethod;
bean.authWorkstation = monitor.authWorkstation;
bean.authDomain = monitor.authDomain;
bean.slowResponseNotification = monitor.slowResponseNotification;
bean.slowResponseNotificationThreshold = monitor.slowResponseNotificationThreshold;
bean.slowResponseNotificationRange = monitor.slowResponseNotificationRange;
bean.slowResponseNotificationMethod = monitor.slowResponseNotificationMethod;
bean.grpcUrl = monitor.grpcUrl;
bean.grpcProtobuf = monitor.grpcProtobuf;
bean.grpcServiceName = monitor.grpcServiceName;

View file

@ -485,6 +485,18 @@
"uninstall": "Uninstall",
"uninstalling": "Uninstalling",
"confirmUninstallPlugin": "Are you sure want to uninstall this plugin?",
"slowResponseNotification": "Slow Response Notification",
"slowResponseNotificationUse": "Use Slow Response Notification",
"slowResponseNotificationUseDescription": "When response time is slow, notify.",
"slowResponseNotificationThreshold": "Threshold (ms)",
"slowResponseNotificationThresholdDescription": "If response time greater than {0} ms, notify.",
"slowResponseNotificationRange": "Time Range (seconds)",
"slowResponseNotificationRangeDescription": "Gets the heartbeat information for the last {0} seconds and calculates the condition.",
"slowResponseNotificationMethod": "Calculation Method",
"slowResponseNotificationMethodAverage": "Average",
"slowResponseNotificationMethodAverageDescription": "Get the average response time of the last {0} seconds.",
"slowResponseNotificationMethodMax": "Max",
"slowResponseNotificationMethodMaxDescription": "Get the max response time of the last {0} seconds.",
"notificationRegional": "Regional",
"Clone Monitor": "Clone Monitor",
"Clone": "Clone",

View file

@ -418,6 +418,53 @@
<input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="0" step="1">
</div>
<h2 class="mt-5 mb-2">{{ $t("slowResponseNotification") }}</h2>
<div class="my-3 form-check">
<input id="slow-response-notification" v-model="monitor.slowResponseNotification" class="form-check-input" type="checkbox">
<label class="form-check-label" for="slow-response-notification">
{{ $t("slowResponseNotificationUse") }}
</label>
<div class="form-text">
{{ $t("slowResponseNotificationUseDescription") }}
</div>
</div>
<!-- Method -->
<div v-if="monitor.slowResponseNotification" class="my-3">
<label for="method" class="form-label">{{ $t("slowResponseNotificationMethod") }}</label>
<select id="method" v-model="monitor.slowResponseNotificationMethod" class="form-select">
<option value="average">
{{ $t("slowResponseNotificationMethodAverage") }}
</option>
<option value="max">
{{ $t("slowResponseNotificationMethodMax") }}
</option>
</select>
<div v-if="monitor.slowResponseNotificationMethod === 'average'" class="form-text">
{{ $t("slowResponseNotificationMethodAverageDescription", [monitor.slowResponseNotificationRange]) }}
</div>
<div v-if="monitor.slowResponseNotificationMethod === 'max'" class="form-text">
{{ $t("slowResponseNotificationMethodMaxDescription", [monitor.slowResponseNotificationRange]) }}
</div>
</div>
<div v-if="monitor.slowResponseNotification" class="my-3">
<label for="slow-response-notification-threshold" class="form-label">{{ $t("slowResponseNotificationThreshold") }}</label>
<input id="slow-response-notification-threshold" v-model="monitor.slowResponseNotificationThreshold" type="number" class="form-control" required min="0" step="1">
<div class="form-text">
{{ $t("slowResponseNotificationThresholdDescription", [monitor.slowResponseNotificationThreshold]) }}
</div>
</div>
<div v-if="monitor.slowResponseNotification" class="my-3">
<label for="slow-response-notification-range" class="form-label">{{ $t("slowResponseNotificationRange") }}</label>
<input id="slow-response-notification-range" v-model="monitor.slowResponseNotificationRange" type="number" class="form-control" required min="0" step="1">
<div class="form-text">
{{ $t("slowResponseNotificationRangeDescription", [monitor.slowResponseNotificationRange]) }}
</div>
</div>
<h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' " class="my-3 form-check">
@ -888,6 +935,10 @@ const monitorDefaults = {
},
kafkaProducerSsl: false,
gamedigGivenPortOnly: true,
slowResponseNotification: false,
slowResponseNotificationThreshold: 5000,
slowResponseNotificationRange: 60,
slowResponseNotificationMethod: "average",
};
export default {