From cd3767a04065de99396be7b7461d0927b20c9429 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Jan 2025 20:50:08 -0800 Subject: [PATCH 1/2] new duration algorithm --- Server/db/mongo/modules/monitorModule.js | 1 + .../db/mongo/modules/monitorModuleQueries.js | 53 +++++++++++++++---- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 745aecf88..d66cd310f 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -353,6 +353,7 @@ const getUptimeDetailsById = async (req) => { const monitorStats = { ...monitor.toObject(), stats: { + uptimeStreak: monitorData[0].uptimeStreak, avgResponseTime: monitorData[0].avgResponseTime, totalChecks: monitorData[0].totalChecks, timeSinceLastCheck: monitorData[0].timeSinceLastCheck, diff --git a/Server/db/mongo/modules/monitorModuleQueries.js b/Server/db/mongo/modules/monitorModuleQueries.js index ccafbcbdc..18914c0c4 100644 --- a/Server/db/mongo/modules/monitorModuleQueries.js +++ b/Server/db/mongo/modules/monitorModuleQueries.js @@ -31,22 +31,43 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - uptimeDuration: [ - { - $match: { - status: false, - }, - }, + uptimeStreak: [ { $sort: { - createdAt: 1, + createdAt: -1, }, }, { $group: { _id: null, - lastFalseCheck: { - $last: "$$ROOT", + checks: { $push: "$$ROOT" }, + }, + }, + { + $project: { + streak: { + $reduce: { + input: "$checks", + initialValue: { checks: [], foundFalse: false }, + in: { + $cond: [ + { + $and: [ + { $not: "$$value.foundFalse" }, // Haven't found a false yet + { $eq: ["$$this.status", true] }, // Current check is true + ], + }, + { + checks: { $concatArrays: ["$$value.checks", ["$$this"]] }, + foundFalse: false, + }, + { + checks: "$$value.checks", + foundFalse: true, // Mark that we found a false + }, + ], + }, + }, }, }, }, @@ -188,6 +209,20 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, { $project: { + uptimeStreak: { + $cond: [ + { $eq: [{ $size: { $first: "$uptimeStreak.streak.checks" } }, 0] }, + 0, + { + $subtract: [ + new Date(), + { + $last: { $first: "$uptimeStreak.streak.checks.createdAt" }, + }, + ], + }, + ], + }, avgResponseTime: { $arrayElemAt: ["$aggregateData.avgResponseTime", 0], }, From 12cc20a852169b96182e7ad14ffaf5e2647b2c6e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 Jan 2025 12:12:33 -0800 Subject: [PATCH 2/2] refactor uptime details query --- .../Uptime/Details/Charts/DownBarChart.jsx | 17 ++-- .../Uptime/Details/Charts/UpBarChart.jsx | 15 ++-- Client/src/Pages/Uptime/Details/index.jsx | 24 +++--- Server/db/mongo/modules/monitorModule.js | 21 ++--- .../db/mongo/modules/monitorModuleQueries.js | 83 +++++++------------ 5 files changed, 61 insertions(+), 99 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx index a851a93ea..bfc99937a 100644 --- a/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx +++ b/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx @@ -4,7 +4,7 @@ import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; import PropTypes from "prop-types"; import CustomLabels from "./CustomLabels"; -const DownBarChart = memo(({ stats, type, onBarHover }) => { +const DownBarChart = memo(({ monitor, type, onBarHover }) => { const theme = useTheme(); const [chartHovered, setChartHovered] = useState(false); @@ -19,7 +19,7 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => { { setChartHovered(true); onBarHover({ time: null, totalChecks: 0 }); @@ -40,8 +40,10 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={stats.downChecks?.[0] ?? {}} - lastDataPoint={stats.downChecks?.[stats.downChecks.length - 1] ?? {}} + firstDataPoint={monitor.groupedDownChecks?.[0] ?? {}} + lastDataPoint={ + monitor.groupedDownChecks?.[monitor.groupedDownChecks.length - 1] ?? {} + } type={type} /> } @@ -51,7 +53,7 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => { maxBarSize={7} background={{ fill: "transparent" }} > - {stats.downChecks.map((entry, index) => ( + {monitor.groupedDownChecks.map((entry, index) => ( { DownBarChart.displayName = "DownBarChart"; DownBarChart.propTypes = { - stats: PropTypes.shape({ - downChecks: PropTypes.arrayOf(PropTypes.object), - downChecksAggregate: PropTypes.object, + monitor: PropTypes.shape({ + groupedDownChecks: PropTypes.arrayOf(PropTypes.object), }), type: PropTypes.string, onBarHover: PropTypes.func, diff --git a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx index 08c8d778e..091b9fccd 100644 --- a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx +++ b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx @@ -4,7 +4,7 @@ import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; import PropTypes from "prop-types"; import CustomLabels from "./CustomLabels"; -const UpBarChart = memo(({ stats, type, onBarHover }) => { +const UpBarChart = memo(({ monitor, type, onBarHover }) => { const theme = useTheme(); const [chartHovered, setChartHovered] = useState(false); const [hoveredBarIndex, setHoveredBarIndex] = useState(null); @@ -26,7 +26,7 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => { { setChartHovered(true); onBarHover({ time: null, totalChecks: 0, avgResponseTime: 0 }); @@ -47,8 +47,8 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={stats.upChecks[0]} - lastDataPoint={stats.upChecks[stats.upChecks.length - 1]} + firstDataPoint={monitor.groupedUpChecks[0]} + lastDataPoint={monitor.groupedUpChecks[monitor.groupedUpChecks.length - 1]} type={type} /> } @@ -58,7 +58,7 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => { maxBarSize={7} background={{ fill: "transparent" }} > - {stats.upChecks.map((entry, index) => { + {monitor.groupedUpChecks.map((entry, index) => { let { main, light } = getColorRange(entry.avgResponseTime); return ( { - {monitor?.stats?.latestResponseTime} + {monitor?.latestResponseTime} {"ms"} } @@ -308,7 +308,7 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? hoveredUptimeData.totalChecks - : (monitor.stats?.upChecks?.reduce((count, checkGroup) => { + : (monitor?.groupedUpChecks?.reduce((count, checkGroup) => { return count + checkGroup.totalChecks; }, 0) ?? 0)} @@ -338,8 +338,8 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0) : Math.floor( - ((monitor?.stats?.upChecksAggregate?.totalChecks ?? 0) / - (monitor?.stats?.totalChecks ?? 1)) * + ((monitor?.upChecks?.totalChecks ?? 0) / + (monitor?.totalChecks ?? 1)) * 100 )} @@ -349,7 +349,7 @@ const DetailsPage = () => { @@ -366,7 +366,7 @@ const DetailsPage = () => { {hoveredIncidentsData !== null ? hoveredIncidentsData.totalChecks - : (monitor.stats?.downChecks?.reduce((count, checkGroup) => { + : (monitor?.groupedDownChecks?.reduce((count, checkGroup) => { return count + checkGroup.totalChecks; }, 0) ?? 0)} @@ -388,7 +388,7 @@ const DetailsPage = () => { )} @@ -400,9 +400,7 @@ const DetailsPage = () => { Average Response Time - + { Response Times diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index d66cd310f..b191bd1bc 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -340,32 +340,21 @@ const getUptimeDetailsById = async (req) => { }; const dateString = formatLookup[dateRange]; - const monitorData = await Check.aggregate( + const results = await Check.aggregate( buildUptimeDetailsPipeline(monitor, dates, dateString) ); + const monitorData = results[0]; const normalizedGroupChecks = NormalizeDataUptimeDetails( - monitorData[0].groupChecks, + monitorData.groupedChecks, 10, 100 ); const monitorStats = { ...monitor.toObject(), - stats: { - uptimeStreak: monitorData[0].uptimeStreak, - avgResponseTime: monitorData[0].avgResponseTime, - totalChecks: monitorData[0].totalChecks, - timeSinceLastCheck: monitorData[0].timeSinceLastCheck, - timeSinceLastFalseCheck: monitorData[0].timeSinceLastFalseCheck, - latestResponseTime: monitorData[0].latestResponseTime, - groupChecks: normalizedGroupChecks, - groupAggregate: monitorData[0].groupAggregate, - upChecksAggregate: monitorData[0].upChecksAggregate, - upChecks: monitorData[0].upChecks, - downChecksAggregate: monitorData[0].downChecksAggregate, - downChecks: monitorData[0].downChecks, - }, + ...monitorData, + groupedChecks: normalizedGroupChecks, }; return monitorStats; diff --git a/Server/db/mongo/modules/monitorModuleQueries.js b/Server/db/mongo/modules/monitorModuleQueries.js index 18914c0c4..cdbcd51d8 100644 --- a/Server/db/mongo/modules/monitorModuleQueries.js +++ b/Server/db/mongo/modules/monitorModuleQueries.js @@ -19,9 +19,6 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { avgResponseTime: { $avg: "$responseTime", }, - firstCheck: { - $first: "$$ROOT", - }, lastCheck: { $last: "$$ROOT", }, @@ -53,14 +50,16 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { $cond: [ { $and: [ - { $not: "$$value.foundFalse" }, // Haven't found a false yet - { $eq: ["$$this.status", true] }, // Current check is true + { $not: "$$value.foundFalse" }, // stop reducing if a false check has been found + { $eq: ["$$this.status", true] }, // continue reducing if current check true ], }, + // true case { checks: { $concatArrays: ["$$value.checks", ["$$this"]] }, - foundFalse: false, + foundFalse: false, // Add the check to the streak }, + // false case { checks: "$$value.checks", foundFalse: true, // Mark that we found a false @@ -72,7 +71,9 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - groupChecks: [ + // For the response time chart, should return checks for date window + // Grouped by: {day: hour}, {week: day}, {month: day} + groupedChecks: [ { $match: { createdAt: { $gte: dates.start, $lte: dates.end }, @@ -100,7 +101,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - groupAggregate: [ + // Average response time for the date window + groupAvgResponseTime: [ { $match: { createdAt: { $gte: dates.start, $lte: dates.end }, @@ -115,10 +117,12 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - upChecksAggregate: [ + // All UpChecks for the date window + upChecks: [ { $match: { status: true, + createdAt: { $gte: dates.start, $lte: dates.end }, }, }, { @@ -133,7 +137,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - upChecks: [ + // Up checks grouped by: {day: hour}, {week: day}, {month: day} + groupedUpChecks: [ { $match: { status: true, @@ -160,10 +165,12 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { $sort: { _id: 1 }, }, ], - downChecksAggregate: [ + // All down checks for the date window + downChecks: [ { $match: { status: false, + createdAt: { $gte: dates.start, $lte: dates.end }, }, }, { @@ -178,7 +185,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - downChecks: [ + // Down checks grouped by: {day: hour}, {week: day}, {month: day} for the date window + groupedDownChecks: [ { $match: { status: false, @@ -252,51 +260,18 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - timeSinceLastFalseCheck: { - $let: { - vars: { - lastFalseCheck: { - $arrayElemAt: ["$uptimeDuration.lastFalseCheck", 0], - }, - firstCheck: { - $arrayElemAt: ["$aggregateData.firstCheck", 0], - }, - }, - in: { - $cond: [ - { - $ifNull: ["$$lastFalseCheck", false], - }, - { - $subtract: [new Date(), "$$lastFalseCheck.createdAt"], - }, - { - $cond: [ - { - $ifNull: ["$$firstCheck", false], - }, - { - $subtract: [new Date(), "$$firstCheck.createdAt"], - }, - 0, - ], - }, - ], - }, - }, + groupedChecks: "$groupedChecks", + groupedAvgResponseTime: { + $arrayElemAt: ["$groupAvgResponseTime", 0], }, - groupChecks: "$groupChecks", - groupAggregate: { - $arrayElemAt: ["$groupAggregate", 0], + upChecks: { + $arrayElemAt: ["$upChecks", 0], }, - upChecksAggregate: { - $arrayElemAt: ["$upChecksAggregate", 0], + groupedUpChecks: "$groupedUpChecks", + downChecks: { + $arrayElemAt: ["$downChecks", 0], }, - upChecks: "$upChecks", - downChecksAggregate: { - $arrayElemAt: ["$downChecksAggregate", 0], - }, - downChecks: "$downChecks", + groupedDownChecks: "$groupedDownChecks", }, }, ];