Refactor getMonitorStatsById for clarity

This commit is contained in:
Alex Holliday
2024-11-05 12:08:19 +08:00
parent de8e9a9f9d
commit 401c3b83fe
+117 -106
View File
@@ -56,7 +56,7 @@ const calculateUptimeDuration = (checks) => {
const latestCheck = new Date(checks[0].createdAt);
let latestDownCheck = 0;
for (let i = checks.length; i <= 0; i--) {
for (let i = checks.length; i >= 0; i--) {
if (checks[i].status === false) {
latestDownCheck = new Date(checks[i].createdAt);
break;
@@ -143,6 +143,98 @@ const getIncidents = (checks) => {
}, 0);
};
/**
* Get date range parameters
* @param {string} dateRange - 'day' | 'week' | 'month'
* @returns {Object} Start and end dates
*/
const getDateRange = (dateRange) => {
const startDates = {
day: new Date(new Date().setDate(new Date().getDate() - 1)),
week: new Date(new Date().setDate(new Date().getDate() - 7)),
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
};
return {
start: startDates[dateRange],
end: new Date(),
};
};
/**
* Get checks for a monitor
* @param {string} monitorId - Monitor ID
* @param {Object} model - Check model to use
* @param {Object} dateRange - Date range parameters
* @param {number} sortOrder - Sort order (1 for ascending, -1 for descending)
* @returns {Promise<Object>} All checks and date-ranged checks
*/
const getMonitorChecks = async (monitorId, model, dateRange, sortOrder) => {
const [checksAll, checksForDateRange] = await Promise.all([
model.find({ monitorId }).sort({ createdAt: sortOrder }),
model
.find({
monitorId,
createdAt: { $gte: dateRange.start, $lte: dateRange.end },
})
.sort({ createdAt: sortOrder }),
]);
return { checksAll, checksForDateRange };
};
/**
* Process checks for display
* @param {Array} checks - Checks to process
* @param {number} numToDisplay - Number of checks to display
* @param {boolean} normalize - Whether to normalize the data
* @returns {Array} Processed checks
*/
const processChecksForDisplay = (checks, numToDisplay, normalize) => {
let processedChecks = checks;
if (numToDisplay && checks.length > numToDisplay) {
const n = Math.ceil(checks.length / numToDisplay);
processedChecks = checks.filter((_, index) => index % n === 0);
}
return normalize ? NormalizeData(processedChecks, 1, 100) : processedChecks;
};
/**
* Get time-grouped checks based on date range
* @param {Array} checks Array of check objects
* @param {string} dateRange 'day' | 'week' | 'month'
* @returns {Object} Grouped checks by time period
*/
const groupChecksByTime = (checks, dateRange) => {
return checks.reduce((acc, check) => {
const time =
dateRange === "day"
? new Date(check.createdAt).setMinutes(0, 0, 0)
: new Date(check.createdAt).toISOString().split("T")[0];
if (!acc[time]) {
acc[time] = { time, checks: [] };
}
acc[time].checks.push(check);
return acc;
}, {});
};
/**
* Calculate aggregate stats for a group of checks
* @param {Object} group Group of checks
* @returns {Object} Stats for the group
*/
const calculateGroupStats = (group) => {
const totalChecks = group.checks.length;
return {
time: group.time,
uptimePercentage: getUptimePercentage(group.checks),
totalChecks,
totalIncidents: group.checks.filter((check) => !check.status).length,
avgResponseTime:
group.checks.reduce((sum, check) => sum + check.responseTime, 0) / totalChecks,
};
};
/**
* Get stats by monitor ID
* @async
@@ -152,129 +244,48 @@ const getIncidents = (checks) => {
* @throws {Error}
*/
const getMonitorStatsById = async (req) => {
const startDates = {
day: new Date(new Date().setDate(new Date().getDate() - 1)),
week: new Date(new Date().setDate(new Date().getDate() - 7)),
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
};
const endDate = new Date();
try {
// Get monitor
const { monitorId } = req.params;
let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query;
// Get monitor, if we can't find it, abort with error
const monitor = await Monitor.findById(monitorId);
if (monitor === null || monitor === undefined) {
throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
}
// This effectively removes limit, returning all checks
if (limit === undefined) limit = 0;
// Default sort order is newest -> oldest
sortOrder = sortOrder === "asc" ? 1 : -1;
let model = CHECK_MODEL_LOOKUP[monitor.type];
// Get query params
let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query;
const sort = sortOrder === "asc" ? 1 : -1;
// Get Checks for monitor in date range requested
const model = CHECK_MODEL_LOOKUP[monitor.type];
const dates = getDateRange(dateRange);
const { checksAll, checksForDateRange } = await getMonitorChecks(
monitorId,
model,
dates,
sort
);
// Build monitor stats
const monitorStats = {
...monitor.toObject(),
uptimeDuration: calculateUptimeDuration(checksAll),
lastChecked: getLastChecked(checksAll),
latestResponseTime: getLatestResponseTime(checksAll),
periodIncidents: getIncidents(checksForDateRange),
periodTotalChecks: checksForDateRange.length,
checks: processChecksForDisplay(checksForDateRange, numToDisplay, normalize),
};
// Build checks query
const checksQuery = { monitorId: monitor._id };
// Get all checks
const checksAll = await model.find(checksQuery).sort({
createdAt: sortOrder,
});
const checksQueryForDateRange = {
...checksQuery,
createdAt: {
$gte: startDates[dateRange],
$lte: endDate,
},
};
const checksForDateRange = await model
.find(checksQueryForDateRange)
.sort({ createdAt: sortOrder });
if (monitor.type === "http" || monitor.type === "ping") {
// HTTP/PING Specific stats
monitorStats.periodAvgResponseTime = getAverageResponseTime(checksForDateRange);
monitorStats.periodUptime = getUptimePercentage(checksForDateRange);
// Aggregate data
let groupedChecks;
// Group checks by hour if range is day
if (dateRange === "day") {
groupedChecks = checksForDateRange.reduce((acc, check) => {
const time = new Date(check.createdAt);
time.setMinutes(0, 0, 0);
if (!acc[time]) {
acc[time] = { time, checks: [] };
}
acc[time].checks.push(check);
return acc;
}, {});
} else {
groupedChecks = checksForDateRange.reduce((acc, check) => {
const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part
if (!acc[time]) {
acc[time] = { time, checks: [] };
}
acc[time].checks.push(check);
return acc;
}, {});
}
// Map grouped checks to stats
const aggregateData = Object.values(groupedChecks).map((group) => {
const totalChecks = group.checks.length;
const uptimePercentage = getUptimePercentage(group.checks);
const totalIncidents = group.checks.filter(
(check) => check.status === false
).length;
const avgResponseTime =
group.checks.reduce((sum, check) => sum + check.responseTime, 0) / totalChecks;
return {
time: group.time,
uptimePercentage,
totalChecks,
totalIncidents,
avgResponseTime,
};
});
monitorStats.aggregateData = aggregateData;
const groupedChecks = groupChecksByTime(checksForDateRange, dateRange);
monitorStats.aggregateData = Object.values(groupedChecks).map(calculateGroupStats);
}
monitorStats.periodIncidents = getIncidents(checksForDateRange);
monitorStats.periodTotalChecks = checksForDateRange.length;
// If more than numToDisplay checks, pick every nth check
let nthChecks = checksForDateRange;
if (
numToDisplay !== undefined &&
checksForDateRange &&
checksForDateRange.length > numToDisplay
) {
const n = Math.ceil(checksForDateRange.length / numToDisplay);
nthChecks = checksForDateRange.filter((_, index) => index % n === 0);
}
// Normalize checks if requested
if (normalize !== undefined) {
const normailzedChecks = NormalizeData(nthChecks, 1, 100);
monitorStats.checks = normailzedChecks;
} else {
monitorStats.checks = nthChecks;
}
monitorStats.uptimeDuration = calculateUptimeDuration(checksAll);
monitorStats.lastChecked = getLastChecked(checksAll);
monitorStats.latestResponseTime = getLatestResponseTime(checksAll);
return monitorStats;
} catch (error) {
error.service = SERVICE_NAME;