fix: fix for #212, data interpolation introduced

This commit is contained in:
Raj Nandan Sharma
2025-01-23 09:36:37 +05:30
parent 4cc49cce9e
commit d126fe0bce
11 changed files with 116 additions and 30 deletions

View File

@@ -27,6 +27,10 @@ DNS Monitor is used to check the status of a DNS server. It can be configured to
Ping Monitor is used to check the status of a server. It can be configured to check the status of the server at different intervals.
### Data Interpolation
Kener interpolates the data for missing intervals. For example, suppose if you have a cron monitor of 5 minutes and it has run twice at 10AM and 10:05AM and the monitor is `DOWN` at 10AM. Kener will interpolate the data for 10:01AM, 10:02AM, 10:03AM and 10:04AM as `DOWN`. Similarly, if the monitor is UP at 10:05AM, Kener will interpolate the data for 10:06AM, 10:07AM, 10:08AM and 10:09AM as UP. Similarly for missing data points it will consider the previous data point. If there is no previous data point, it will consider it as `UP`.
---
## Triggers

View File

@@ -29,4 +29,14 @@ Do not forget to add the base path if you are using a subpath. For example, if y
### Inline CSS
To add inline css go to Manage kener -> Theme -> Custom CSS and add your CSS there. Do not include `<style>` tags.
To add inline css go to Manage kener -> Theme -> Custom CSS and add your CSS there.
```css
.my-class {
color: red;
}
```
<div class="note danger">
Do not include &#x3C;style&#x3E; tags.
</div>

View File

@@ -90,3 +90,17 @@ Add font url
### Font Family
Add font family name
### Custom CSS
You can add custom CSS to your status page. This will be added to the head of the page. You can add custom CSS to your status page. This will be added to the head of the page.
```css
.my-class {
color: red;
}
```
<div class="note danger">
Do not include &#x3C;style&#x3E; tags.
</div>

View File

@@ -278,7 +278,7 @@
.summaryColorClass}"
>
{l(lang, summaryTime(monitor.pageData.summaryStatus), {
status: monitor.pageData.summaryStatus,
status: l(lang, monitor.pageData.summaryStatus),
duration: monitor.pageData.summaryDuration
})}
</div>
@@ -304,7 +304,6 @@
on:click={(e) => {
dailyDataGetter(e, bar, incidents[ts]);
}}
style="transition: opacity {bar.ij * 2 + 100}ms ease-in;"
href="#"
class="oneline h-[34px] w-[6px]
{bar.border ? 'opacity-100' : 'opacity-20'} pb-1"
@@ -336,7 +335,7 @@
)}
-
{l(lang, summaryTime(bar.summaryStatus), {
status: bar.summaryStatus,
status: l(lang, bar.summaryStatus),
duration: bar.summaryDuration
})}
</div>

View File

@@ -19,7 +19,7 @@ const fdn = function (start, locale) {
};
const fdm = function (duration, locale) {
return formatDuration(duration, {
format: ["hours", "minutes"],
format: ["days", "hours", "minutes"],
zero: false,
delimiter: " ",
locale: locales[locale]
@@ -36,7 +36,6 @@ const l = function (sessionLangMap, key, args = {}) {
break;
}
}
// Replace placeholders in the string using the args object
if (obj && typeof obj === "string") {
obj = obj.replace(/%\w+/g, (placeholder) => {

View File

@@ -381,6 +381,29 @@ export const HashString = (str) => {
return hash.digest("hex");
};
export const InterpolateData = (data, start, anchorStatus, e) => {
let finalData = [];
let status = anchorStatus || "UP";
let end = e || data[data.length - 1].timestamp;
for (let i = start; i <= end; i += 60) {
let nowData = data.find((d) => d.timestamp === i);
if (!!nowData) {
status = nowData.status;
}
finalData.push({ timestamp: i, status: status });
}
return finalData;
};
export const GetLastStatusBefore = async (monitor_tag, timestamp) => {
let data = await db.getLastStatusBefore(monitor_tag, timestamp);
if (data) {
return data.status;
}
return "UP";
};
export const GetDataGroupByDayAlternative = async (
monitor_tag,
start,
@@ -394,7 +417,9 @@ export const GetDataGroupByDayAlternative = async (
const offsetSeconds = offsetMinutes * 60;
const rawData = await db.getDataGroupByDayAlternative(monitor_tag, start, end);
let rawData = await db.getDataGroupByDayAlternative(monitor_tag, start, end);
let anchorStatus = await GetLastStatusBefore(monitor_tag, start);
rawData = InterpolateData(rawData, start, anchorStatus);
const groupedData = rawData.reduce((acc, row) => {
// Calculate day group considering timezone offset
const dayGroup = Math.floor((row.timestamp + offsetSeconds) / 86400);

View File

@@ -56,6 +56,16 @@ class DbImpl {
.first();
}
//get the last status before the timestamp given monitor_tag and start timestamp
async getLastStatusBefore(monitor_tag, timestamp) {
return await this.knex("monitoring_data")
.where("monitor_tag", monitor_tag)
.where("timestamp", "<", timestamp)
.orderBy("timestamp", "desc")
.limit(1)
.first();
}
async getDataGroupByDayAlternative(monitor_tag, start, end) {
// Fetch all raw data
//{ timestamp: 1732900380, status: 'UP', latency: 42 }

View File

@@ -10,7 +10,11 @@ import {
} from "$lib/server/tool.js";
import { formatDuration, intervalToDuration } from "date-fns";
import { fdm } from "$lib/i18n/client";
import { GetDataGroupByDayAlternative } from "$lib/server/controllers/controller.js";
import {
GetDataGroupByDayAlternative,
InterpolateData,
GetLastStatusBefore
} from "$lib/server/controllers/controller.js";
function getSummaryDuration(numOfMinute, selectedLang) {
// Convert minutes to milliseconds and create duration object
@@ -33,12 +37,12 @@ function getTimezoneOffset(timeZone) {
return parseInt(hours) * 60 + parseInt(minutes);
}
function returnStatusClass(val, up, c, barStyle) {
function returnStatusClass(val, total, c, barStyle) {
// return "api-degraded-10";
if (barStyle === undefined || barStyle == "FULL" || up == 0) {
if (barStyle === undefined || barStyle == "FULL") {
return c;
} else if (barStyle == "PARTIAL") {
let totalHeight = 24 * 60;
let totalHeight = total;
let cl = `api-up`;
if (val > 0 && val <= 0.1 * totalHeight) {
cl = c + "-10";
@@ -84,7 +88,6 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
let offsetInMinutes = parseInt((GetDayStartTimestampUTC(now) - midnight) / 60);
const _90Day = {};
let latestTimestamp = 0;
let ij = 0;
for (let i = midnight90DaysAgo; i < midnightTomorrow; i += secondsInDay) {
_90Day[i] = {
UP: 0,
@@ -96,10 +99,8 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
summaryDuration: 0,
summaryStatus: NO_DATA,
message: NO_DATA,
border: true,
ij: ij
border: true
};
ij++;
latestTimestamp = i;
}
@@ -117,6 +118,11 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
let summaryStatus = "UP";
let summaryColorClass = "api-nodata";
let todayDataDb = await db.getMonitoringData(monitor.tag, midnight, midnight + secondsInDay);
let anchorStatus = await GetLastStatusBefore(monitor.tag, midnight);
todayDataDb = InterpolateData(todayDataDb, midnight, anchorStatus);
for (let i = 0; i < dbData.length; i++) {
let dayData = dbData[i];
let ts = dayData.timestamp;
@@ -132,7 +138,7 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
if (dayData.DEGRADED >= monitor.day_degraded_minimum_count) {
cssClass = returnStatusClass(
dayData.DEGRADED,
dayData.UP,
dayData.total,
StatusObj.DEGRADED,
site.barStyle
);
@@ -140,7 +146,13 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
summaryStatus = "DEGRADED";
}
if (dayData.DOWN >= monitor.day_down_minimum_count) {
cssClass = returnStatusClass(dayData.DOWN, dayData.UP, StatusObj.DOWN, site.barStyle);
cssClass = returnStatusClass(
dayData.DOWN,
dayData.total,
StatusObj.DOWN,
site.barStyle
);
summaryDuration = getSummaryDuration(dayData.DOWN, selectedLang);
summaryStatus = "DOWN";
}
@@ -162,13 +174,8 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
}
// return _90Day;
let uptime90Day = ParseUptime(uptime90DayNumerator, uptime90DayDenominator);
if (site.summaryStyle === "CURRENT") {
let todayDataDb = await db.getMonitoringData(
monitor.tag,
latestTimestamp,
latestTimestamp + secondsInDay
);
if (site.summaryStyle === "CURRENT") {
summaryColorClass = "api-up";
let lastRow = todayDataDb[todayDataDb.length - 1];

View File

@@ -86,7 +86,7 @@
<img src="https://kener.ing/logo.png" class="h-8 w-8" alt="" />
<span class="text-xl font-medium">Kener Documentation</span>
<span class="me-2 rounded border px-2.5 py-0.5 text-xs font-medium">
v3.0.0
v3.0.2
</span>
</a>
</div>

View File

@@ -8,7 +8,12 @@ import {
ParseUptime
} from "$lib/server/tool.js";
import db from "$lib/server/db/db.js";
import { GetAllSiteData, GetIncidentsByIDS } from "$lib/server/controllers/controller.js";
import {
GetAllSiteData,
GetIncidentsByIDS,
InterpolateData,
GetLastStatusBefore
} from "$lib/server/controllers/controller.js";
export async function POST({ request }) {
const payload = await request.json();
@@ -31,6 +36,8 @@ export async function POST({ request }) {
}
let dayData = await db.getMonitoringData(monitor.tag, payload.startTs, end);
let anchorStatus = await GetLastStatusBefore(monitor.tag, start);
dayData = InterpolateData(dayData, payload.startTs, anchorStatus, end);
let siteData = await GetAllSiteData();
let ups = 0;

View File

@@ -8,18 +8,29 @@ import {
ParseUptime
} from "$lib/server/tool.js";
import db from "$lib/server/db/db.js";
import { InterpolateData, GetLastStatusBefore } from "$lib/server/controllers/controller.js";
export async function POST({ request }) {
const payload = await request.json();
const monitor = payload.monitor;
const start = payload.startTs;
let end = GetMinuteStartNowTimestampUTC();
let aggregatedData = await db.getAggregatedMonitoringData(monitor.tag, start, end);
let rawData = await db.getMonitoringData(monitor.tag, start, end);
let anchorStatus = await GetLastStatusBefore(monitor.tag, start);
rawData = InterpolateData(rawData, start, anchorStatus);
let aggregatedData = rawData.reduce(
(acc, row) => {
acc[row.status] = (acc[row.status] || 0) + 1;
return acc;
},
{
UP: 0,
DOWN: 0,
DEGRADED: 0
}
);
//covert all keys to uppercase
aggregatedData = Object.keys(aggregatedData).reduce((acc, key) => {
acc[key.toUpperCase()] = aggregatedData[key];
return acc;
}, {});
let ups = Number(aggregatedData.UP);
let downs = Number(aggregatedData.DOWN);
let degradeds = Number(aggregatedData.DEGRADED);