From 775b8846c731602358dca450df0dbca0ff8debf2 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 5 Nov 2025 09:33:29 +0000 Subject: [PATCH] refactor(monitor): tidy formatting, consolidate imports and add type annotations - Normalize whitespace/indentation and reformat several monitor utilities and message builders - Consolidate and reorder imports (including MonitorEvaluationSummary) across evaluator/data-extractor modules - Add explicit types (MonitorStatus, BasicDiskMetrics) and tighten type annotations in observation/resource code - Minor cleanups to conditional formatting in dashboard components (EvaluationLogList, SummaryInfo) --- .../Monitor/Criteria/ServerMonitorCriteria.ts | 3 +- .../Monitor/MonitorCriteriaDataExtractor.ts | 307 +-- .../Utils/Monitor/MonitorCriteriaEvaluator.ts | 3 +- .../MonitorCriteriaExpectationBuilder.ts | 240 +- .../Monitor/MonitorCriteriaMessageBuilder.ts | 132 +- .../MonitorCriteriaMessageFormatter.ts | 309 ++- .../MonitorCriteriaObservationBuilder.ts | 2198 +++++++++-------- .../Server/Utils/Monitor/MonitorMetricUtil.ts | 924 +++---- .../Server/Utils/Monitor/MonitorResource.ts | 24 +- .../Monitor/SummaryView/EvaluationLogList.tsx | 6 +- .../Monitor/SummaryView/SummaryInfo.tsx | 8 +- 11 files changed, 2075 insertions(+), 2079 deletions(-) diff --git a/Common/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts b/Common/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts index a4c8583d5e..7499c085f1 100644 --- a/Common/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +++ b/Common/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts @@ -174,8 +174,7 @@ export default class ServerMonitorCriteria { ).basicInfrastructureMetrics?.diskMetrics.find( (item: BasicDiskMetrics) => { return ( - item.diskPath.trim().toLowerCase() === - diskPath.trim().toLowerCase() + item.diskPath.trim().toLowerCase() === diskPath.trim().toLowerCase() ); }, ); diff --git a/Common/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts b/Common/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts index 3eb9a0379d..b41d8faed7 100644 --- a/Common/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts +++ b/Common/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts @@ -16,192 +16,193 @@ import MetricQueryConfigData from "../../../Types/Metrics/MetricQueryConfigData" import MetricFormulaConfigData from "../../../Types/Metrics/MetricFormulaConfigData"; export default class MonitorCriteriaDataExtractor { - public static getProbeMonitorResponse( - dataToProcess: DataToProcess, - ): ProbeMonitorResponse | null { - if ((dataToProcess as ProbeMonitorResponse).monitorStepId) { - return dataToProcess as ProbeMonitorResponse; - } + public static getProbeMonitorResponse( + dataToProcess: DataToProcess, + ): ProbeMonitorResponse | null { + if ((dataToProcess as ProbeMonitorResponse).monitorStepId) { + return dataToProcess as ProbeMonitorResponse; + } - return null; - } + return null; + } - public static getServerMonitorResponse( - dataToProcess: DataToProcess, - ): ServerMonitorResponse | null { - if ((dataToProcess as ServerMonitorResponse).hostname) { - return dataToProcess as ServerMonitorResponse; - } + public static getServerMonitorResponse( + dataToProcess: DataToProcess, + ): ServerMonitorResponse | null { + if ((dataToProcess as ServerMonitorResponse).hostname) { + return dataToProcess as ServerMonitorResponse; + } - return null; - } + return null; + } - public static getIncomingMonitorRequest( - dataToProcess: DataToProcess, - ): IncomingMonitorRequest | null { - if ( - (dataToProcess as IncomingMonitorRequest).incomingRequestReceivedAt !== - undefined - ) { - return dataToProcess as IncomingMonitorRequest; - } + public static getIncomingMonitorRequest( + dataToProcess: DataToProcess, + ): IncomingMonitorRequest | null { + if ( + (dataToProcess as IncomingMonitorRequest).incomingRequestReceivedAt !== + undefined + ) { + return dataToProcess as IncomingMonitorRequest; + } - return null; - } + return null; + } - public static getLogMonitorResponse( - dataToProcess: DataToProcess, - ): LogMonitorResponse | null { - if ((dataToProcess as LogMonitorResponse).logCount !== undefined) { - return dataToProcess as LogMonitorResponse; - } + public static getLogMonitorResponse( + dataToProcess: DataToProcess, + ): LogMonitorResponse | null { + if ((dataToProcess as LogMonitorResponse).logCount !== undefined) { + return dataToProcess as LogMonitorResponse; + } - return null; - } + return null; + } - public static getTraceMonitorResponse( - dataToProcess: DataToProcess, - ): TraceMonitorResponse | null { - if ((dataToProcess as TraceMonitorResponse).spanCount !== undefined) { - return dataToProcess as TraceMonitorResponse; - } + public static getTraceMonitorResponse( + dataToProcess: DataToProcess, + ): TraceMonitorResponse | null { + if ((dataToProcess as TraceMonitorResponse).spanCount !== undefined) { + return dataToProcess as TraceMonitorResponse; + } - return null; - } + return null; + } - public static getMetricMonitorResponse( - dataToProcess: DataToProcess, - ): MetricMonitorResponse | null { - if ((dataToProcess as MetricMonitorResponse).metricResult !== undefined) { - return dataToProcess as MetricMonitorResponse; - } + public static getMetricMonitorResponse( + dataToProcess: DataToProcess, + ): MetricMonitorResponse | null { + if ((dataToProcess as MetricMonitorResponse).metricResult !== undefined) { + return dataToProcess as MetricMonitorResponse; + } - return null; - } + return null; + } - public static getCustomCodeMonitorResponse( - dataToProcess: DataToProcess, - ): CustomCodeMonitorResponse | null { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse(dataToProcess); + public static getCustomCodeMonitorResponse( + dataToProcess: DataToProcess, + ): CustomCodeMonitorResponse | null { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(dataToProcess); - if (probeResponse?.customCodeMonitorResponse) { - return probeResponse.customCodeMonitorResponse; - } + if (probeResponse?.customCodeMonitorResponse) { + return probeResponse.customCodeMonitorResponse; + } - return null; - } + return null; + } - public static getSyntheticMonitorResponses( - dataToProcess: DataToProcess, - ): Array { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse(dataToProcess); + public static getSyntheticMonitorResponses( + dataToProcess: DataToProcess, + ): Array { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(dataToProcess); - return probeResponse?.syntheticMonitorResponse || []; - } + return probeResponse?.syntheticMonitorResponse || []; + } - public static getSslResponse( - dataToProcess: DataToProcess, - ): SslMonitorResponse | null { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse(dataToProcess); + public static getSslResponse( + dataToProcess: DataToProcess, + ): SslMonitorResponse | null { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(dataToProcess); - if (probeResponse?.sslResponse) { - return probeResponse.sslResponse; - } + if (probeResponse?.sslResponse) { + return probeResponse.sslResponse; + } - return null; - } + return null; + } - public static extractMetricValues(input: { - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - monitorStep: MonitorStep; - }): { alias: string | null; values: Array } | null { - const metricResponse: MetricMonitorResponse | null = - MonitorCriteriaDataExtractor.getMetricMonitorResponse( - input.dataToProcess, - ); + public static extractMetricValues(input: { + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + monitorStep: MonitorStep; + }): { alias: string | null; values: Array } | null { + const metricResponse: MetricMonitorResponse | null = + MonitorCriteriaDataExtractor.getMetricMonitorResponse( + input.dataToProcess, + ); - if (!metricResponse) { - return null; - } + if (!metricResponse) { + return null; + } - const aggregatedResults: Array = - metricResponse.metricResult || []; + const aggregatedResults: Array = + metricResponse.metricResult || []; - if (!aggregatedResults.length) { - return { - alias: input.criteriaFilter.metricMonitorOptions?.metricAlias || null, - values: [], - }; - } + if (!aggregatedResults.length) { + return { + alias: input.criteriaFilter.metricMonitorOptions?.metricAlias || null, + values: [], + }; + } - let alias: string | null = - input.criteriaFilter.metricMonitorOptions?.metricAlias || null; + let alias: string | null = + input.criteriaFilter.metricMonitorOptions?.metricAlias || null; - let result: AggregatedResult | undefined; + let result: AggregatedResult | undefined; - if (alias) { - const queryConfigs: Array = - input.monitorStep.data?.metricMonitor?.metricViewConfig?.queryConfigs || - []; + if (alias) { + const queryConfigs: Array = + input.monitorStep.data?.metricMonitor?.metricViewConfig?.queryConfigs || + []; - let aliasIndex: number = queryConfigs.findIndex( - (queryConfig: MetricQueryConfigData) => { - return queryConfig.metricAliasData?.metricVariable === alias; - }, - ); + let aliasIndex: number = queryConfigs.findIndex( + (queryConfig: MetricQueryConfigData) => { + return queryConfig.metricAliasData?.metricVariable === alias; + }, + ); - if (aliasIndex < 0) { - const formulaConfigs: Array = - input.monitorStep.data?.metricMonitor?.metricViewConfig - ?.formulaConfigs || []; + if (aliasIndex < 0) { + const formulaConfigs: Array = + input.monitorStep.data?.metricMonitor?.metricViewConfig + ?.formulaConfigs || []; - const formulaIndex: number = formulaConfigs.findIndex( - (formulaConfig: MetricFormulaConfigData) => { - return formulaConfig.metricAliasData?.metricVariable === alias; - }, - ); + const formulaIndex: number = formulaConfigs.findIndex( + (formulaConfig: MetricFormulaConfigData) => { + return formulaConfig.metricAliasData?.metricVariable === alias; + }, + ); - if (formulaIndex >= 0) { - aliasIndex = queryConfigs.length + formulaIndex; - } - } + if (formulaIndex >= 0) { + aliasIndex = queryConfigs.length + formulaIndex; + } + } - if (aliasIndex >= 0 && aliasIndex < aggregatedResults.length) { - result = aggregatedResults[aliasIndex]; - } - } + if (aliasIndex >= 0 && aliasIndex < aggregatedResults.length) { + result = aggregatedResults[aliasIndex]; + } + } - if (!result) { - result = aggregatedResults[0]; - if (!alias) { - const defaultAlias: string | undefined = - input.monitorStep.data?.metricMonitor?.metricViewConfig?.queryConfigs?.[0]?.metricAliasData?.metricVariable; - alias = defaultAlias || null; - } - } + if (!result) { + result = aggregatedResults[0]; + if (!alias) { + const defaultAlias: string | undefined = + input.monitorStep.data?.metricMonitor?.metricViewConfig + ?.queryConfigs?.[0]?.metricAliasData?.metricVariable; + alias = defaultAlias || null; + } + } - if (!result) { - return { - alias: alias, - values: [], - }; - } + if (!result) { + return { + alias: alias, + values: [], + }; + } - const values: Array = result.data - .map((entry: AggregateModel) => { - return entry.value; - }) - .filter((value: number) => { - return typeof value === "number" && !isNaN(value); - }); + const values: Array = result.data + .map((entry: AggregateModel) => { + return entry.value; + }) + .filter((value: number) => { + return typeof value === "number" && !isNaN(value); + }); - return { - alias: alias, - values: values, - }; - } + return { + alias: alias, + values: values, + }; + } } diff --git a/Common/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts b/Common/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts index 34091f1626..4b112ed8d7 100644 --- a/Common/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +++ b/Common/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts @@ -16,7 +16,7 @@ import MonitorCriteria from "../../../Types/Monitor/MonitorCriteria"; import MonitorCriteriaInstance from "../../../Types/Monitor/MonitorCriteriaInstance"; import MonitorStep from "../../../Types/Monitor/MonitorStep"; import FilterCondition from "../../../Types/Filter/FilterCondition"; -import { +import MonitorEvaluationSummary, { MonitorEvaluationCriteriaResult, MonitorEvaluationEvent, MonitorEvaluationFilterResult, @@ -30,7 +30,6 @@ import OneUptimeDate from "../../../Types/Date"; import { JSONObject } from "../../../Types/JSON"; import Typeof from "../../../Types/Typeof"; import ReturnResult from "../../../Types/IsolatedVM/ReturnResult"; -import MonitorEvaluationSummary from "../../../Types/Monitor/MonitorEvaluationSummary"; export default class MonitorCriteriaEvaluator { public static async processMonitorStep(input: { diff --git a/Common/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts b/Common/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts index 4faaae6d0e..de527e9e6f 100644 --- a/Common/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +++ b/Common/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts @@ -1,141 +1,141 @@ import { - CriteriaFilter, - FilterType, + CriteriaFilter, + FilterType, } from "../../../Types/Monitor/CriteriaFilter"; export default class MonitorCriteriaExpectationBuilder { - public static getCriteriaFilterDescription( - criteriaFilter: CriteriaFilter, - ): string { - const parts: Array = [criteriaFilter.checkOn]; + public static getCriteriaFilterDescription( + criteriaFilter: CriteriaFilter, + ): string { + const parts: Array = [criteriaFilter.checkOn]; - if (criteriaFilter.filterType) { - parts.push(criteriaFilter.filterType); - } + if (criteriaFilter.filterType) { + parts.push(criteriaFilter.filterType); + } - if (criteriaFilter.value !== undefined && criteriaFilter.value !== null) { - parts.push(String(criteriaFilter.value)); - } + if (criteriaFilter.value !== undefined && criteriaFilter.value !== null) { + parts.push(String(criteriaFilter.value)); + } - return parts.join(" ").trim(); - } + return parts.join(" ").trim(); + } - public static describeCriteriaExpectation( - criteriaFilter: CriteriaFilter, - ): string | null { - if (!criteriaFilter.filterType) { - return null; - } + public static describeCriteriaExpectation( + criteriaFilter: CriteriaFilter, + ): string | null { + if (!criteriaFilter.filterType) { + return null; + } - let expectation: string; + let expectation: string; - const value: string | number | undefined = criteriaFilter.value; + const value: string | number | undefined = criteriaFilter.value; - switch (criteriaFilter.filterType) { - case FilterType.GreaterThan: - expectation = `to be greater than ${value}`; - break; - case FilterType.GreaterThanOrEqualTo: - expectation = `to be greater than or equal to ${value}`; - break; - case FilterType.LessThan: - expectation = `to be less than ${value}`; - break; - case FilterType.LessThanOrEqualTo: - expectation = `to be less than or equal to ${value}`; - break; - case FilterType.EqualTo: - expectation = `to equal ${value}`; - break; - case FilterType.NotEqualTo: - expectation = `to not equal ${value}`; - break; - case FilterType.Contains: - expectation = `to contain ${value}`; - break; - case FilterType.NotContains: - expectation = `to not contain ${value}`; - break; - case FilterType.StartsWith: - expectation = `to start with ${value}`; - break; - case FilterType.EndsWith: - expectation = `to end with ${value}`; - break; - case FilterType.IsEmpty: - expectation = "to be empty"; - break; - case FilterType.IsNotEmpty: - expectation = "to not be empty"; - break; - case FilterType.True: - expectation = "to be true"; - break; - case FilterType.False: - expectation = "to be false"; - break; - case FilterType.IsExecuting: - expectation = "to be executing"; - break; - case FilterType.IsNotExecuting: - expectation = "to not be executing"; - break; - case FilterType.RecievedInMinutes: - expectation = value - ? `to receive a heartbeat within ${value} minutes` - : "to receive a heartbeat within the configured window"; - break; - case FilterType.NotRecievedInMinutes: - expectation = value - ? `to miss a heartbeat for at least ${value} minutes` - : "to miss a heartbeat within the configured window"; - break; - case FilterType.EvaluatesToTrue: - expectation = "to evaluate to true"; - break; - default: - expectation = `${criteriaFilter.filterType}${value ? ` ${value}` : ""}`; - break; - } + switch (criteriaFilter.filterType) { + case FilterType.GreaterThan: + expectation = `to be greater than ${value}`; + break; + case FilterType.GreaterThanOrEqualTo: + expectation = `to be greater than or equal to ${value}`; + break; + case FilterType.LessThan: + expectation = `to be less than ${value}`; + break; + case FilterType.LessThanOrEqualTo: + expectation = `to be less than or equal to ${value}`; + break; + case FilterType.EqualTo: + expectation = `to equal ${value}`; + break; + case FilterType.NotEqualTo: + expectation = `to not equal ${value}`; + break; + case FilterType.Contains: + expectation = `to contain ${value}`; + break; + case FilterType.NotContains: + expectation = `to not contain ${value}`; + break; + case FilterType.StartsWith: + expectation = `to start with ${value}`; + break; + case FilterType.EndsWith: + expectation = `to end with ${value}`; + break; + case FilterType.IsEmpty: + expectation = "to be empty"; + break; + case FilterType.IsNotEmpty: + expectation = "to not be empty"; + break; + case FilterType.True: + expectation = "to be true"; + break; + case FilterType.False: + expectation = "to be false"; + break; + case FilterType.IsExecuting: + expectation = "to be executing"; + break; + case FilterType.IsNotExecuting: + expectation = "to not be executing"; + break; + case FilterType.RecievedInMinutes: + expectation = value + ? `to receive a heartbeat within ${value} minutes` + : "to receive a heartbeat within the configured window"; + break; + case FilterType.NotRecievedInMinutes: + expectation = value + ? `to miss a heartbeat for at least ${value} minutes` + : "to miss a heartbeat within the configured window"; + break; + case FilterType.EvaluatesToTrue: + expectation = "to evaluate to true"; + break; + default: + expectation = `${criteriaFilter.filterType}${value ? ` ${value}` : ""}`; + break; + } - const evaluationWindow: string | null = - MonitorCriteriaExpectationBuilder.getEvaluationWindowDescription( - criteriaFilter, - ); + const evaluationWindow: string | null = + MonitorCriteriaExpectationBuilder.getEvaluationWindowDescription( + criteriaFilter, + ); - if (evaluationWindow) { - expectation += ` ${evaluationWindow}`; - } + if (evaluationWindow) { + expectation += ` ${evaluationWindow}`; + } - return expectation.trim(); - } + return expectation.trim(); + } - public static getEvaluationWindowDescription( - criteriaFilter: CriteriaFilter, - ): string | null { - const parts: Array = []; + public static getEvaluationWindowDescription( + criteriaFilter: CriteriaFilter, + ): string | null { + const parts: Array = []; - if ( - criteriaFilter.eveluateOverTime && - criteriaFilter.evaluateOverTimeOptions?.timeValueInMinutes - ) { - parts.push( - `over the last ${criteriaFilter.evaluateOverTimeOptions.timeValueInMinutes} minutes`, - ); - } + if ( + criteriaFilter.eveluateOverTime && + criteriaFilter.evaluateOverTimeOptions?.timeValueInMinutes + ) { + parts.push( + `over the last ${criteriaFilter.evaluateOverTimeOptions.timeValueInMinutes} minutes`, + ); + } - const aggregation: string | undefined = - criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType || - criteriaFilter.metricMonitorOptions?.metricAggregationType; + const aggregation: string | undefined = + criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType || + criteriaFilter.metricMonitorOptions?.metricAggregationType; - if (aggregation) { - parts.push(`using ${aggregation.toLowerCase()}`); - } + if (aggregation) { + parts.push(`using ${aggregation.toLowerCase()}`); + } - if (!parts.length) { - return null; - } + if (!parts.length) { + return null; + } - return parts.join(" "); - } + return parts.join(" "); + } } diff --git a/Common/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts b/Common/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts index 76a67f3505..9d473a44ea 100644 --- a/Common/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +++ b/Common/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts @@ -6,83 +6,83 @@ import MonitorCriteriaExpectationBuilder from "./MonitorCriteriaExpectationBuild import MonitorCriteriaObservationBuilder from "./MonitorCriteriaObservationBuilder"; export default class MonitorCriteriaMessageBuilder { - public static buildCriteriaFilterMessage(input: { - monitor: Monitor; - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - monitorStep: MonitorStep; - didMeetCriteria: boolean; - matchMessage: string | null; - }): string { - if (input.matchMessage) { - return input.matchMessage; - } + public static buildCriteriaFilterMessage(input: { + monitor: Monitor; + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + monitorStep: MonitorStep; + didMeetCriteria: boolean; + matchMessage: string | null; + }): string { + if (input.matchMessage) { + return input.matchMessage; + } - if (input.didMeetCriteria) { - const description: string = - MonitorCriteriaExpectationBuilder.getCriteriaFilterDescription( - input.criteriaFilter, - ); + if (input.didMeetCriteria) { + const description: string = + MonitorCriteriaExpectationBuilder.getCriteriaFilterDescription( + input.criteriaFilter, + ); - return `${description} condition met.`; - } + return `${description} condition met.`; + } - const failureMessage: string | null = - MonitorCriteriaMessageBuilder.buildCriteriaFilterFailureMessage({ - monitor: input.monitor, - criteriaFilter: input.criteriaFilter, - dataToProcess: input.dataToProcess, - monitorStep: input.monitorStep, - }); + const failureMessage: string | null = + MonitorCriteriaMessageBuilder.buildCriteriaFilterFailureMessage({ + monitor: input.monitor, + criteriaFilter: input.criteriaFilter, + dataToProcess: input.dataToProcess, + monitorStep: input.monitorStep, + }); - if (failureMessage) { - return failureMessage; - } + if (failureMessage) { + return failureMessage; + } - const description: string = - MonitorCriteriaExpectationBuilder.getCriteriaFilterDescription( - input.criteriaFilter, - ); + const description: string = + MonitorCriteriaExpectationBuilder.getCriteriaFilterDescription( + input.criteriaFilter, + ); - return `${description} condition was not met.`; - } + return `${description} condition was not met.`; + } - private static buildCriteriaFilterFailureMessage(input: { - monitor: Monitor; - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - monitorStep: MonitorStep; - }): string | null { - const expectation: string | null = - MonitorCriteriaExpectationBuilder.describeCriteriaExpectation( - input.criteriaFilter, - ); + private static buildCriteriaFilterFailureMessage(input: { + monitor: Monitor; + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + monitorStep: MonitorStep; + }): string | null { + const expectation: string | null = + MonitorCriteriaExpectationBuilder.describeCriteriaExpectation( + input.criteriaFilter, + ); - const observation: string | null = - MonitorCriteriaObservationBuilder.describeFilterObservation({ - monitor: input.monitor, - criteriaFilter: input.criteriaFilter, - dataToProcess: input.dataToProcess, - monitorStep: input.monitorStep, - }); + const observation: string | null = + MonitorCriteriaObservationBuilder.describeFilterObservation({ + monitor: input.monitor, + criteriaFilter: input.criteriaFilter, + dataToProcess: input.dataToProcess, + monitorStep: input.monitorStep, + }); - if (observation) { - if (expectation) { - return `${observation} (expected ${expectation}).`; - } + if (observation) { + if (expectation) { + return `${observation} (expected ${expectation}).`; + } - return `${observation}; configured filter was not met.`; - } + return `${observation}; configured filter was not met.`; + } - if (expectation) { - const description: string = - MonitorCriteriaExpectationBuilder.getCriteriaFilterDescription( - input.criteriaFilter, - ); + if (expectation) { + const description: string = + MonitorCriteriaExpectationBuilder.getCriteriaFilterDescription( + input.criteriaFilter, + ); - return `${description} did not satisfy the configured condition (${expectation}).`; - } + return `${description} did not satisfy the configured condition (${expectation}).`; + } - return null; - } + return null; + } } diff --git a/Common/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts b/Common/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts index 63c79a70d6..84aa8a21b4 100644 --- a/Common/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +++ b/Common/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts @@ -4,200 +4,193 @@ import Typeof from "../../../Types/Typeof"; import { ServerProcess } from "../../../Types/Monitor/ServerMonitor/ServerMonitorResponse"; export default class MonitorCriteriaMessageFormatter { - public static formatNumber( - value: number | null | undefined, - options?: { maximumFractionDigits?: number }, - ): string | null { - if (value === null || value === undefined || isNaN(value)) { - return null; - } + public static formatNumber( + value: number | null | undefined, + options?: { maximumFractionDigits?: number }, + ): string | null { + if (value === null || value === undefined || isNaN(value)) { + return null; + } - const fractionDigits: number = - options?.maximumFractionDigits !== undefined - ? options.maximumFractionDigits - : Math.abs(value) < 10 - ? 2 - : Math.abs(value) < 100 - ? 1 - : 0; + const fractionDigits: number = + options?.maximumFractionDigits !== undefined + ? options.maximumFractionDigits + : Math.abs(value) < 10 + ? 2 + : Math.abs(value) < 100 + ? 1 + : 0; - return value.toFixed(fractionDigits); - } + return value.toFixed(fractionDigits); + } - public static formatPercentage( - value: number | null | undefined, - ): string | null { - const formatted: string | null = MonitorCriteriaMessageFormatter.formatNumber( - value, - { - maximumFractionDigits: - value !== null && value !== undefined && Math.abs(value) < 100 ? 1 : 0, - }, - ); + public static formatPercentage( + value: number | null | undefined, + ): string | null { + const formatted: string | null = + MonitorCriteriaMessageFormatter.formatNumber(value, { + maximumFractionDigits: + value !== null && value !== undefined && Math.abs(value) < 100 + ? 1 + : 0, + }); - if (!formatted) { - return null; - } + if (!formatted) { + return null; + } - return `${formatted}%`; - } + return `${formatted}%`; + } - public static formatBytes( - bytes: number | null | undefined, - ): string | null { - if (bytes === null || bytes === undefined || isNaN(bytes)) { - return null; - } + public static formatBytes(bytes: number | null | undefined): string | null { + if (bytes === null || bytes === undefined || isNaN(bytes)) { + return null; + } - const units: Array = ["B", "KB", "MB", "GB", "TB", "PB"]; - let value: number = bytes; - let index: number = 0; + const units: Array = ["B", "KB", "MB", "GB", "TB", "PB"]; + let value: number = bytes; + let index: number = 0; - while (value >= 1024 && index < units.length - 1) { - value = value / 1024; - index++; - } + while (value >= 1024 && index < units.length - 1) { + value = value / 1024; + index++; + } - const formatted: string | null = MonitorCriteriaMessageFormatter.formatNumber( - value, - { - maximumFractionDigits: value >= 100 ? 0 : value >= 10 ? 1 : 2, - }, - ); + const formatted: string | null = + MonitorCriteriaMessageFormatter.formatNumber(value, { + maximumFractionDigits: value >= 100 ? 0 : value >= 10 ? 1 : 2, + }); - if (!formatted) { - return null; - } + if (!formatted) { + return null; + } - return `${formatted} ${units[index]}`; - } + return `${formatted} ${units[index]}`; + } - public static formatList( - items: Array, - maxItems: number = 5, - ): string { - if (!items.length) { - return ""; - } + public static formatList(items: Array, maxItems: number = 5): string { + if (!items.length) { + return ""; + } - const trimmedItems: Array = items.slice(0, maxItems); - const suffix: string = - items.length > maxItems ? `, +${items.length - maxItems} more` : ""; + const trimmedItems: Array = items.slice(0, maxItems); + const suffix: string = + items.length > maxItems ? `, +${items.length - maxItems} more` : ""; - return `${trimmedItems.join(", ")} ${suffix}`.trim(); - } + return `${trimmedItems.join(", ")} ${suffix}`.trim(); + } - public static formatSnippet(text: string, maxLength: number = 120): string { - const sanitized: string = text.replace(/\s+/g, " ").trim(); + public static formatSnippet(text: string, maxLength: number = 120): string { + const sanitized: string = text.replace(/\s+/g, " ").trim(); - if (sanitized.length <= maxLength) { - return sanitized; - } + if (sanitized.length <= maxLength) { + return sanitized; + } - return `${sanitized.slice(0, maxLength)}…`; - } + return `${sanitized.slice(0, maxLength)}…`; + } - public static describeProcesses( - processes: Array, - ): string | null { - if (!processes.length) { - return null; - } + public static describeProcesses( + processes: Array, + ): string | null { + if (!processes.length) { + return null; + } - const processSummaries: Array = processes.map( - (process: ServerProcess) => { - return `${process.name} (pid ${process.pid})`; - }, - ); + const processSummaries: Array = processes.map( + (process: ServerProcess) => { + return `${process.name} (pid ${process.pid})`; + }, + ); - return MonitorCriteriaMessageFormatter.formatList(processSummaries); - } + return MonitorCriteriaMessageFormatter.formatList(processSummaries); + } - public static computeDiskUsagePercent( - diskMetric: BasicInfrastructureMetrics["diskMetrics"][number], - ): number | null { - if (!diskMetric) { - return null; - } + public static computeDiskUsagePercent( + diskMetric: BasicInfrastructureMetrics["diskMetrics"][number], + ): number | null { + if (!diskMetric) { + return null; + } - if ( - diskMetric.percentUsed !== undefined && - diskMetric.percentUsed !== null && - !isNaN(diskMetric.percentUsed) - ) { - return diskMetric.percentUsed; - } + if ( + diskMetric.percentUsed !== undefined && + diskMetric.percentUsed !== null && + !isNaN(diskMetric.percentUsed) + ) { + return diskMetric.percentUsed; + } - if ( - diskMetric.percentFree !== undefined && - diskMetric.percentFree !== null && - !isNaN(diskMetric.percentFree) - ) { - return 100 - diskMetric.percentFree; - } + if ( + diskMetric.percentFree !== undefined && + diskMetric.percentFree !== null && + !isNaN(diskMetric.percentFree) + ) { + return 100 - diskMetric.percentFree; + } - if (diskMetric.total && diskMetric.used && diskMetric.total > 0) { - return (diskMetric.used / diskMetric.total) * 100; - } + if (diskMetric.total && diskMetric.used && diskMetric.total > 0) { + return (diskMetric.used / diskMetric.total) * 100; + } - return null; - } + return null; + } - public static summarizeNumericSeries(values: Array): string | null { - if (!values.length) { - return null; - } + public static summarizeNumericSeries(values: Array): string | null { + if (!values.length) { + return null; + } - const latest: number | undefined = values[values.length - 1]; + const latest: number | undefined = values[values.length - 1]; - if (latest === undefined) { - return null; - } + if (latest === undefined) { + return null; + } - const latestFormatted: string | null = - MonitorCriteriaMessageFormatter.formatNumber(latest, { - maximumFractionDigits: 2, - }); + const latestFormatted: string | null = + MonitorCriteriaMessageFormatter.formatNumber(latest, { + maximumFractionDigits: 2, + }); - let summary: string = `latest ${latestFormatted ?? latest}`; + let summary: string = `latest ${latestFormatted ?? latest}`; - if (values.length > 1) { - const min: number = Math.min(...values); - const max: number = Math.max(...values); + if (values.length > 1) { + const min: number = Math.min(...values); + const max: number = Math.max(...values); - const minFormatted: string | null = - MonitorCriteriaMessageFormatter.formatNumber(min, { - maximumFractionDigits: 2, - }); - const maxFormatted: string | null = - MonitorCriteriaMessageFormatter.formatNumber(max, { - maximumFractionDigits: 2, - }); + const minFormatted: string | null = + MonitorCriteriaMessageFormatter.formatNumber(min, { + maximumFractionDigits: 2, + }); + const maxFormatted: string | null = + MonitorCriteriaMessageFormatter.formatNumber(max, { + maximumFractionDigits: 2, + }); - summary += ` (min ${minFormatted ?? min}, max ${maxFormatted ?? max})`; - } + summary += ` (min ${minFormatted ?? min}, max ${maxFormatted ?? max})`; + } - summary += ` across ${values.length} data point${ - values.length === 1 ? "" : "s" - }`; + summary += ` across ${values.length} data point${ + values.length === 1 ? "" : "s" + }`; - return summary; - } + return summary; + } - public static formatResultValue(value: unknown): string { - if (value === null || value === undefined) { - return "undefined"; - } + public static formatResultValue(value: unknown): string { + if (value === null || value === undefined) { + return "undefined"; + } - if (typeof value === Typeof.Object) { - try { - return JSON.stringify(value); - } catch (err) { - logger.error(err); - return "[object]"; - } - } + if (typeof value === Typeof.Object) { + try { + return JSON.stringify(value); + } catch (err) { + logger.error(err); + return "[object]"; + } + } - return value.toString(); - } + return value.toString(); + } } diff --git a/Common/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts b/Common/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts index 29bb0c9308..2ebc4ac2f2 100644 --- a/Common/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +++ b/Common/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts @@ -3,17 +3,16 @@ import OneUptimeDate from "../../../Types/Date"; import Monitor from "../../../Models/DatabaseModels/Monitor"; import MonitorStep from "../../../Types/Monitor/MonitorStep"; import DataToProcess from "./DataToProcess"; -import { - CheckOn, - CriteriaFilter, -} from "../../../Types/Monitor/CriteriaFilter"; +import { CheckOn, CriteriaFilter } from "../../../Types/Monitor/CriteriaFilter"; import { JSONObject } from "../../../Types/JSON"; import ProbeMonitorResponse from "../../../Types/Probe/ProbeMonitorResponse"; import ServerMonitorResponse, { - ServerProcess, + ServerProcess, } from "../../../Types/Monitor/ServerMonitor/ServerMonitorResponse"; import IncomingMonitorRequest from "../../../Types/Monitor/IncomingMonitor/IncomingMonitorRequest"; -import BasicInfrastructureMetrics from "../../../Types/Infrastructure/BasicMetrics"; +import BasicInfrastructureMetrics, { + BasicDiskMetrics, +} from "../../../Types/Infrastructure/BasicMetrics"; import Typeof from "../../../Types/Typeof"; import SslMonitorResponse from "../../../Types/Monitor/SSLMonitor/SslMonitorResponse"; import SyntheticMonitorResponse from "../../../Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse"; @@ -25,1095 +24,1100 @@ import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor"; import MonitorCriteriaExpectationBuilder from "./MonitorCriteriaExpectationBuilder"; export default class MonitorCriteriaObservationBuilder { - public static describeFilterObservation(input: { - monitor: Monitor; - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - monitorStep: MonitorStep; - }): string | null { - const { criteriaFilter } = input; - - switch (criteriaFilter.checkOn) { - case CheckOn.ResponseTime: - return MonitorCriteriaObservationBuilder.describeResponseTimeObservation( - input, - ); - case CheckOn.ResponseStatusCode: - return MonitorCriteriaObservationBuilder.describeResponseStatusCodeObservation( - input, - ); - case CheckOn.ResponseHeader: - return MonitorCriteriaObservationBuilder.describeResponseHeaderObservation( - input, - ); - case CheckOn.ResponseHeaderValue: - return MonitorCriteriaObservationBuilder.describeResponseHeaderValueObservation( - input, - ); - case CheckOn.ResponseBody: - return MonitorCriteriaObservationBuilder.describeResponseBodyObservation( - input, - ); - case CheckOn.IsOnline: - return MonitorCriteriaObservationBuilder.describeIsOnlineObservation( - input, - ); - case CheckOn.IsRequestTimeout: - return MonitorCriteriaObservationBuilder.describeIsTimeoutObservation( - input, - ); - case CheckOn.IncomingRequest: - return MonitorCriteriaObservationBuilder.describeIncomingRequestObservation( - input, - ); - case CheckOn.RequestBody: - return MonitorCriteriaObservationBuilder.describeRequestBodyObservation( - input, - ); - case CheckOn.RequestHeader: - return MonitorCriteriaObservationBuilder.describeRequestHeaderObservation( - input, - ); - case CheckOn.RequestHeaderValue: - return MonitorCriteriaObservationBuilder.describeRequestHeaderValueObservation( - input, - ); - case CheckOn.JavaScriptExpression: - return MonitorCriteriaObservationBuilder.describeJavaScriptExpressionObservation( - input, - ); - case CheckOn.CPUUsagePercent: - return MonitorCriteriaObservationBuilder.describeCpuUsageObservation(input); - case CheckOn.MemoryUsagePercent: - return MonitorCriteriaObservationBuilder.describeMemoryUsageObservation( - input, - ); - case CheckOn.DiskUsagePercent: - return MonitorCriteriaObservationBuilder.describeDiskUsageObservation( - input, - ); - case CheckOn.ServerProcessName: - return MonitorCriteriaObservationBuilder.describeServerProcessNameObservation( - input, - ); - case CheckOn.ServerProcessPID: - return MonitorCriteriaObservationBuilder.describeServerProcessPidObservation( - input, - ); - case CheckOn.ServerProcessCommand: - return MonitorCriteriaObservationBuilder.describeServerProcessCommandObservation( - input, - ); - case CheckOn.ExpiresInHours: - return MonitorCriteriaObservationBuilder.describeCertificateExpiresInHoursObservation( - input, - ); - case CheckOn.ExpiresInDays: - return MonitorCriteriaObservationBuilder.describeCertificateExpiresInDaysObservation( - input, - ); - case CheckOn.IsSelfSignedCertificate: - return MonitorCriteriaObservationBuilder.describeIsSelfSignedObservation( - input, - ); - case CheckOn.IsExpiredCertificate: - return MonitorCriteriaObservationBuilder.describeIsExpiredObservation( - input, - ); - case CheckOn.IsValidCertificate: - return MonitorCriteriaObservationBuilder.describeIsValidObservation(input); - case CheckOn.IsNotAValidCertificate: - return MonitorCriteriaObservationBuilder.describeIsInvalidObservation( - input, - ); - case CheckOn.ResultValue: - return MonitorCriteriaObservationBuilder.describeResultValueObservation( - input, - ); - case CheckOn.Error: - return MonitorCriteriaObservationBuilder.describeErrorObservation(input); - case CheckOn.ExecutionTime: - return MonitorCriteriaObservationBuilder.describeExecutionTimeObservation( - input, - ); - case CheckOn.ScreenSizeType: - return MonitorCriteriaObservationBuilder.describeScreenSizeObservation( - input, - ); - case CheckOn.BrowserType: - return MonitorCriteriaObservationBuilder.describeBrowserObservation(input); - case CheckOn.LogCount: - return MonitorCriteriaObservationBuilder.describeLogCountObservation(input); - case CheckOn.SpanCount: - return MonitorCriteriaObservationBuilder.describeSpanCountObservation(input); - case CheckOn.MetricValue: - return MonitorCriteriaObservationBuilder.describeMetricValueObservation( - input, - ); - default: - return null; - } - } - - private static describeResponseTimeObservation(input: { - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - }): string | null { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse( - input.dataToProcess, - ); - - if (!probeResponse) { - return null; - } - - const responseTime: number | undefined = - probeResponse.responseTimeInMs ?? undefined; - - if (responseTime === undefined || responseTime === null) { - return "Response time metric was not recorded"; - } - - const formatted: string | null = - MonitorCriteriaMessageFormatter.formatNumber(responseTime, { - maximumFractionDigits: 2, - }); - - const evaluationWindow: string | null = - MonitorCriteriaExpectationBuilder.getEvaluationWindowDescription( - input.criteriaFilter, - ); - - let message: string = `Response Time (in ms) was ${formatted ?? responseTime} ms`; - - if (evaluationWindow) { - message += ` ${evaluationWindow}`; - } - - return message; - } - - private static describeResponseStatusCodeObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse( - input.dataToProcess, - ); - - if (!probeResponse) { - return null; - } - - if (probeResponse.responseCode === undefined) { - return "Response status code was not recorded"; - } - - return `Response Status Code was ${probeResponse.responseCode}.`; - } - - private static describeResponseHeaderObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse( - input.dataToProcess, - ); - - if (!probeResponse) { - return null; - } - - const headers: Array = Object.keys( - probeResponse.responseHeaders || {}, - ).map((header: string) => { - return header.toLowerCase(); - }); - - if (!headers.length) { - return "Response headers were empty."; - } - - return `Response headers present: ${MonitorCriteriaMessageFormatter.formatList(headers)}.`; - } - - private static describeResponseHeaderValueObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse( - input.dataToProcess, - ); - - if (!probeResponse) { - return null; - } - - const headerValues: Array = Object.values( - probeResponse.responseHeaders || {}, - ).map((value: string) => { - return value.toLowerCase(); - }); - - if (!headerValues.length) { - return "Response header values were empty."; - } - - return `Response header values: ${MonitorCriteriaMessageFormatter.formatList(headerValues)}.`; - } - - private static describeResponseBodyObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse( - input.dataToProcess, - ); - - if (!probeResponse) { - return null; - } - - if (!probeResponse.responseBody) { - return "Response body was empty."; - } - - let bodyAsString: string; - - if (typeof probeResponse.responseBody === Typeof.Object) { - try { - bodyAsString = JSON.stringify(probeResponse.responseBody); - } catch (err) { - logger.error(err); - bodyAsString = "[object]"; - } - } else { - bodyAsString = probeResponse.responseBody as string; - } - - return `Response body sample: ${MonitorCriteriaMessageFormatter.formatSnippet(bodyAsString)}.`; - } - - private static describeIsOnlineObservation(input: { - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - }): string | null { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse( - input.dataToProcess, - ); - - if (probeResponse && probeResponse.isOnline !== undefined) { - return `Monitor reported ${ - probeResponse.isOnline ? "online" : "offline" - } status at ${OneUptimeDate.getDateAsLocalFormattedString( - probeResponse.monitoredAt, - )}.`; - } - - const serverResponse: ServerMonitorResponse | null = - MonitorCriteriaDataExtractor.getServerMonitorResponse( - input.dataToProcess, - ); - - if (serverResponse) { - const lastHeartbeat: Date = serverResponse.requestReceivedAt; - const timeNow: Date = - serverResponse.timeNow || OneUptimeDate.getCurrentDate(); - const minutesSinceHeartbeat: number = - OneUptimeDate.getDifferenceInMinutes(lastHeartbeat, timeNow); - - const formattedMinutes: string | null = - MonitorCriteriaMessageFormatter.formatNumber(minutesSinceHeartbeat, { - maximumFractionDigits: 2, - }); - - return `Server heartbeat last received ${ - formattedMinutes ?? minutesSinceHeartbeat - } minutes ago.`; - } - - return null; - } - - private static describeIsTimeoutObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse( - input.dataToProcess, - ); - - if (probeResponse && probeResponse.isTimeout !== undefined) { - return probeResponse.isTimeout - ? "Request timed out." - : "Request completed before timeout."; - } - - return "Timeout information was unavailable."; - } - - private static describeIncomingRequestObservation(input: { - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - }): string | null { - const incomingRequest: IncomingMonitorRequest | null = - MonitorCriteriaDataExtractor.getIncomingMonitorRequest( - input.dataToProcess, - ); - - if (!incomingRequest) { - return null; - } - - const lastHeartbeat: Date = incomingRequest.incomingRequestReceivedAt; - const checkedAt: Date = - incomingRequest.checkedAt || OneUptimeDate.getCurrentDate(); - - const minutesSinceHeartbeat: number = - OneUptimeDate.getDifferenceInMinutes(lastHeartbeat, checkedAt); - - const formattedMinutes: string | null = - MonitorCriteriaMessageFormatter.formatNumber(minutesSinceHeartbeat, { - maximumFractionDigits: 2, - }); - - return `Last incoming request was ${ - formattedMinutes ?? minutesSinceHeartbeat - } minutes ago (checked at ${OneUptimeDate.getDateAsLocalFormattedString( - checkedAt, - )}).`; - } - - private static describeRequestBodyObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const incomingRequest: IncomingMonitorRequest | null = - MonitorCriteriaDataExtractor.getIncomingMonitorRequest( - input.dataToProcess, - ); - - if (!incomingRequest) { - return null; - } - - const requestBody: string | JSONObject | undefined = - incomingRequest.requestBody; - - if (!requestBody) { - return "Request body was empty."; - } - - let requestBodyAsString: string; - - if (typeof requestBody === Typeof.Object) { - try { - requestBodyAsString = JSON.stringify(requestBody); - } catch (err) { - logger.error(err); - requestBodyAsString = "[object]"; - } - } else { - requestBodyAsString = requestBody as string; - } - - return `Request body sample: ${MonitorCriteriaMessageFormatter.formatSnippet(requestBodyAsString)}.`; - } - - private static describeRequestHeaderObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const incomingRequest: IncomingMonitorRequest | null = - MonitorCriteriaDataExtractor.getIncomingMonitorRequest( - input.dataToProcess, - ); - - if (!incomingRequest) { - return null; - } - - const headers: Array = Object.keys( - incomingRequest.requestHeaders || {}, - ).map((header: string) => { - return header.toLowerCase(); - }); - - if (!headers.length) { - return "Request headers were empty."; - } - - return `Request headers present: ${MonitorCriteriaMessageFormatter.formatList(headers)}.`; - } - - private static describeRequestHeaderValueObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const incomingRequest: IncomingMonitorRequest | null = - MonitorCriteriaDataExtractor.getIncomingMonitorRequest( - input.dataToProcess, - ); - - if (!incomingRequest) { - return null; - } - - const headerValues: Array = Object.values( - incomingRequest.requestHeaders || {}, - ).map((value: string) => { - return value.toLowerCase(); - }); - - if (!headerValues.length) { - return "Request header values were empty."; - } - - return `Request header values: ${MonitorCriteriaMessageFormatter.formatList(headerValues)}.`; - } - - private static describeJavaScriptExpressionObservation(input: { - criteriaFilter: CriteriaFilter; - }): string | null { - if (!input.criteriaFilter.value) { - return "JavaScript expression evaluated to false."; - } - - return `JavaScript expression "${input.criteriaFilter.value}" evaluated to false.`; - } - - private static describeCpuUsageObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const serverResponse: ServerMonitorResponse | null = - MonitorCriteriaDataExtractor.getServerMonitorResponse( - input.dataToProcess, - ); - - if (!serverResponse) { - return null; - } - - const cpuMetrics: BasicInfrastructureMetrics | undefined = - serverResponse.basicInfrastructureMetrics; - - if (!cpuMetrics || !cpuMetrics.cpuMetrics) { - return "CPU usage metrics were unavailable."; - } - - const cpuPercent: string | null = - MonitorCriteriaMessageFormatter.formatPercentage( - cpuMetrics.cpuMetrics.percentUsed, - ); - - const coreInfo: string = cpuMetrics.cpuMetrics.cores - ? ` across ${cpuMetrics.cpuMetrics.cores} core${ - cpuMetrics.cpuMetrics.cores > 1 ? "s" : "" - }` - : ""; - - return `CPU Usage (in %) was ${cpuPercent ?? "unavailable"}${coreInfo}.`; - } - - private static describeMemoryUsageObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const serverResponse: ServerMonitorResponse | null = - MonitorCriteriaDataExtractor.getServerMonitorResponse( - input.dataToProcess, - ); - - if (!serverResponse) { - return null; - } - - const memoryMetrics: BasicInfrastructureMetrics | undefined = - serverResponse.basicInfrastructureMetrics; - - if (!memoryMetrics || !memoryMetrics.memoryMetrics) { - return "Memory usage metrics were unavailable."; - } - - const percentUsed: string | null = - MonitorCriteriaMessageFormatter.formatPercentage( - memoryMetrics.memoryMetrics.percentUsed, - ); - - const used: string | null = MonitorCriteriaMessageFormatter.formatBytes( - memoryMetrics.memoryMetrics.used, - ); - const total: string | null = MonitorCriteriaMessageFormatter.formatBytes( - memoryMetrics.memoryMetrics.total, - ); - - return `Memory Usage (in %) was ${percentUsed ?? "unavailable"} (${used ?? "?"} used of ${total ?? "?"}).`; - } - - private static describeDiskUsageObservation(input: { - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - }): string | null { - const serverResponse: ServerMonitorResponse | null = - MonitorCriteriaDataExtractor.getServerMonitorResponse( - input.dataToProcess, - ); - - if (!serverResponse) { - return null; - } - - const diskPath: string = - input.criteriaFilter.serverMonitorOptions?.diskPath || "/"; - - const diskMetric: BasicInfrastructureMetrics | undefined = - serverResponse.basicInfrastructureMetrics; - - if (!diskMetric || !diskMetric.diskMetrics?.length) { - return `Disk metrics for path ${diskPath} were unavailable.`; - } - - const matchedDisk = diskMetric.diskMetrics.find((disk) => { - return disk.diskPath.trim().toLowerCase() === diskPath.trim().toLowerCase(); - }); - - if (!matchedDisk) { - return `Disk metrics did not include path ${diskPath}.`; - } - - const percentUsedValue: number | null = - MonitorCriteriaMessageFormatter.computeDiskUsagePercent(matchedDisk); - const percentUsed: string | null = - MonitorCriteriaMessageFormatter.formatPercentage( - percentUsedValue ?? undefined, - ); - - const used: string | null = MonitorCriteriaMessageFormatter.formatBytes( - matchedDisk.used, - ); - const total: string | null = MonitorCriteriaMessageFormatter.formatBytes( - matchedDisk.total, - ); - const free: string | null = MonitorCriteriaMessageFormatter.formatBytes( - matchedDisk.free, - ); - - return `Disk Usage (in %) on disk ${diskPath} was ${ - percentUsed ?? "unavailable" - } (${used ?? "?"} used of ${total ?? "?"}, free ${free ?? "?"}).`; - } - - private static describeServerProcessNameObservation(input: { - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - }): string | null { - const serverResponse: ServerMonitorResponse | null = - MonitorCriteriaDataExtractor.getServerMonitorResponse( - input.dataToProcess, - ); - - if (!serverResponse) { - return null; - } - - const thresholdName: string = - (input.criteriaFilter.value ?? "").toString().trim().toLowerCase(); - - const processes: Array = serverResponse.processes || []; - - const matchingProcesses: Array = processes.filter( - (process: ServerProcess) => { - return process.name.trim().toLowerCase() === thresholdName; - }, - ); - - if (matchingProcesses.length > 0) { - const summary: string = matchingProcesses - .map((process: ServerProcess) => { - return `${process.name} (pid ${process.pid})`; - }) - .join(", "); - - return `Process ${input.criteriaFilter.value} is running (${summary}).`; - } - - const processSummary: string | null = - MonitorCriteriaMessageFormatter.describeProcesses(processes); - - if (processSummary) { - return `Process ${input.criteriaFilter.value} was not running. Active processes: ${processSummary}.`; - } - - return `Process ${input.criteriaFilter.value} was not running.`; - } - - private static describeServerProcessPidObservation(input: { - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - }): string | null { - const serverResponse: ServerMonitorResponse | null = - MonitorCriteriaDataExtractor.getServerMonitorResponse( - input.dataToProcess, - ); - - if (!serverResponse) { - return null; - } - - const thresholdPid: string = - (input.criteriaFilter.value ?? "").toString().trim().toLowerCase(); - - const processes: Array = serverResponse.processes || []; - - const matchingProcesses: Array = processes.filter( - (process: ServerProcess) => { - return process.pid.toString().trim().toLowerCase() === thresholdPid; - }, - ); - - if (matchingProcesses.length > 0) { - const summary: string = matchingProcesses - .map((process: ServerProcess) => { - return `${process.name} (pid ${process.pid})`; - }) - .join(", "); - - return `Process with PID ${input.criteriaFilter.value} is running (${summary}).`; - } - - const processSummary: string | null = - MonitorCriteriaMessageFormatter.describeProcesses(processes); - - if (processSummary) { - return `Process with PID ${input.criteriaFilter.value} was not running. Active processes: ${processSummary}.`; - } - - return `Process with PID ${input.criteriaFilter.value} was not running.`; - } - - private static describeServerProcessCommandObservation(input: { - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - }): string | null { - const serverResponse: ServerMonitorResponse | null = - MonitorCriteriaDataExtractor.getServerMonitorResponse( - input.dataToProcess, - ); - - if (!serverResponse) { - return null; - } - - const thresholdCommand: string = - (input.criteriaFilter.value ?? "").toString().trim().toLowerCase(); - - const processes: Array = serverResponse.processes || []; - - const matchingProcesses: Array = processes.filter( - (process: ServerProcess) => { - return process.command.trim().toLowerCase() === thresholdCommand; - }, - ); - - if (matchingProcesses.length > 0) { - const summary: string = matchingProcesses - .map((process: ServerProcess) => { - return `${process.command} (pid ${process.pid})`; - }) - .join(", "); - - return `Process with command ${input.criteriaFilter.value} is running (${summary}).`; - } - - const processSummary: string | null = - MonitorCriteriaMessageFormatter.describeProcesses(processes); - - if (processSummary) { - return `Process with command ${input.criteriaFilter.value} was not running. Active processes: ${processSummary}.`; - } - - return `Process with command ${input.criteriaFilter.value} was not running.`; - } - - private static describeCertificateExpiresInHoursObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const sslResponse: SslMonitorResponse | null = - MonitorCriteriaDataExtractor.getSslResponse(input.dataToProcess); - - if (!sslResponse || !sslResponse.expiresAt) { - return "SSL certificate expiration time was unavailable."; - } - - const hoursRemaining: number = OneUptimeDate.getHoursBetweenTwoDates( - OneUptimeDate.getCurrentDate(), - sslResponse.expiresAt, - ); - - const formattedHours: string | null = - MonitorCriteriaMessageFormatter.formatNumber(hoursRemaining, { - maximumFractionDigits: 2, - }); - - return `SSL certificate expires at ${ - OneUptimeDate.getDateAsLocalFormattedString(sslResponse.expiresAt) - } (${formattedHours ?? hoursRemaining} hours remaining).`; - } - - private static describeCertificateExpiresInDaysObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const sslResponse: SslMonitorResponse | null = - MonitorCriteriaDataExtractor.getSslResponse(input.dataToProcess); - - if (!sslResponse || !sslResponse.expiresAt) { - return "SSL certificate expiration time was unavailable."; - } - - const daysRemaining: number = OneUptimeDate.getDaysBetweenTwoDates( - OneUptimeDate.getCurrentDate(), - sslResponse.expiresAt, - ); - - const formattedDays: string | null = - MonitorCriteriaMessageFormatter.formatNumber(daysRemaining, { - maximumFractionDigits: 2, - }); - - return `SSL certificate expires at ${ - OneUptimeDate.getDateAsLocalFormattedString(sslResponse.expiresAt) - } (${formattedDays ?? daysRemaining} days remaining).`; - } - - private static describeIsSelfSignedObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const sslResponse: SslMonitorResponse | null = - MonitorCriteriaDataExtractor.getSslResponse(input.dataToProcess); - - if (!sslResponse || sslResponse.isSelfSigned === undefined) { - return "SSL certificate self-signed status was unavailable."; - } - - return sslResponse.isSelfSigned - ? "SSL certificate is self signed." - : "SSL certificate is not self signed."; - } - - private static describeIsExpiredObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const sslResponse: SslMonitorResponse | null = - MonitorCriteriaDataExtractor.getSslResponse(input.dataToProcess); - - if (!sslResponse || !sslResponse.expiresAt) { - return "SSL certificate expiration time was unavailable."; - } - - const isExpired: boolean = OneUptimeDate.isBefore( - sslResponse.expiresAt, - OneUptimeDate.getCurrentDate(), - ); - - return isExpired - ? "SSL certificate is expired." - : "SSL certificate is not expired."; - } - - private static describeIsValidObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse( - input.dataToProcess, - ); - - const sslResponse: SslMonitorResponse | undefined = - probeResponse?.sslResponse; - - const isValid: boolean = Boolean( - sslResponse && - probeResponse?.isOnline && - sslResponse.expiresAt && - !sslResponse.isSelfSigned && - OneUptimeDate.isAfter( - sslResponse.expiresAt, - OneUptimeDate.getCurrentDate(), - ), - ); - - if (!sslResponse) { - return "SSL certificate details were unavailable."; - } - - return isValid - ? "SSL certificate is valid." - : "SSL certificate is not valid."; - } - - private static describeIsInvalidObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const probeResponse: ProbeMonitorResponse | null = - MonitorCriteriaDataExtractor.getProbeMonitorResponse( - input.dataToProcess, - ); - - const sslResponse: SslMonitorResponse | undefined = - probeResponse?.sslResponse; - - const isInvalid: boolean = - !sslResponse || - !probeResponse?.isOnline || - Boolean( - sslResponse && - sslResponse.expiresAt && - (sslResponse.isSelfSigned || - OneUptimeDate.isBefore( - sslResponse.expiresAt, - OneUptimeDate.getCurrentDate(), - )), - ); - - if (!sslResponse) { - return "SSL certificate details were unavailable."; - } - - return isInvalid - ? "SSL certificate is not valid." - : "SSL certificate is valid."; - } - - private static describeExecutionTimeObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const syntheticResponses: Array = - MonitorCriteriaDataExtractor.getSyntheticMonitorResponses( - input.dataToProcess, - ); - - const executionTimes: Array = syntheticResponses - .map((response: SyntheticMonitorResponse) => { - return response.executionTimeInMS; - }) - .filter((value: number) => { - return typeof value === "number" && !isNaN(value); - }); - - if (executionTimes.length > 0) { - const summary: string | null = - MonitorCriteriaMessageFormatter.summarizeNumericSeries( - executionTimes, - ); - - if (summary) { - return `Execution Time (in ms) recorded ${summary}.`; - } - } - - const customCodeResponse: CustomCodeMonitorResponse | null = - MonitorCriteriaDataExtractor.getCustomCodeMonitorResponse( - input.dataToProcess, - ); - - if (customCodeResponse) { - const formatted: string | null = - MonitorCriteriaMessageFormatter.formatNumber( - customCodeResponse.executionTimeInMS, - { maximumFractionDigits: 2 }, - ); - - return `Execution Time (in ms) was ${ - formatted ?? customCodeResponse.executionTimeInMS - } ms.`; - } - - return "Execution time was unavailable."; - } - - private static describeResultValueObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const syntheticResponses: Array = - MonitorCriteriaDataExtractor.getSyntheticMonitorResponses( - input.dataToProcess, - ); - - const resultValues: Array = syntheticResponses - .map((response: SyntheticMonitorResponse) => { - return MonitorCriteriaMessageFormatter.formatResultValue( - response.result, - ); - }) - .filter((value: string) => { - return value !== "undefined"; - }); - - if (resultValues.length > 0) { - const uniqueResults: Array = Array.from(new Set(resultValues)); - - return `Result Value samples: ${MonitorCriteriaMessageFormatter.formatList(uniqueResults)}.`; - } - - const customCodeResponse: CustomCodeMonitorResponse | null = - MonitorCriteriaDataExtractor.getCustomCodeMonitorResponse( - input.dataToProcess, - ); - - if (customCodeResponse && customCodeResponse.result !== undefined) { - const formatted: string = MonitorCriteriaMessageFormatter.formatResultValue( - customCodeResponse.result, - ); - - return `Result Value was ${MonitorCriteriaMessageFormatter.formatSnippet(formatted)}.`; - } - - return "Result value was unavailable."; - } - - private static describeErrorObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const syntheticResponses: Array = - MonitorCriteriaDataExtractor.getSyntheticMonitorResponses( - input.dataToProcess, - ); - - const errors: Array = syntheticResponses - .map((response: SyntheticMonitorResponse) => { - return response.scriptError; - }) - .filter((value: string | undefined): value is string => { - return Boolean(value); - }) - .map((error: string) => { - return MonitorCriteriaMessageFormatter.formatSnippet(error, 80); - }); - - if (errors.length > 0) { - return `Script errors: ${MonitorCriteriaMessageFormatter.formatList(errors)}.`; - } - - const customCodeResponse: CustomCodeMonitorResponse | null = - MonitorCriteriaDataExtractor.getCustomCodeMonitorResponse( - input.dataToProcess, - ); - - if (customCodeResponse?.scriptError) { - return `Script error: ${MonitorCriteriaMessageFormatter.formatSnippet(customCodeResponse.scriptError, 80)}.`; - } - - if (customCodeResponse?.logMessages?.length) { - return `Script log messages: ${MonitorCriteriaMessageFormatter.formatList(customCodeResponse.logMessages)}.`; - } - - return "No script errors were reported."; - } - - private static describeScreenSizeObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const syntheticResponses: Array = - MonitorCriteriaDataExtractor.getSyntheticMonitorResponses( - input.dataToProcess, - ); - - if (!syntheticResponses.length) { - return "Synthetic monitor results were unavailable."; - } - - const screenSizes: Array = Array.from( - new Set( - syntheticResponses.map((response: SyntheticMonitorResponse) => { - return response.screenSizeType; - }), - ), - ); - - return `Synthetic monitor screen sizes: ${MonitorCriteriaMessageFormatter.formatList(screenSizes)}.`; - } - - private static describeBrowserObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const syntheticResponses: Array = - MonitorCriteriaDataExtractor.getSyntheticMonitorResponses( - input.dataToProcess, - ); - - if (!syntheticResponses.length) { - return "Synthetic monitor results were unavailable."; - } - - const browsers: Array = Array.from( - new Set( - syntheticResponses.map((response: SyntheticMonitorResponse) => { - return response.browserType; - }), - ), - ); - - return `Synthetic monitor browsers: ${MonitorCriteriaMessageFormatter.formatList(browsers)}.`; - } - - private static describeLogCountObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const logResponse: LogMonitorResponse | null = - MonitorCriteriaDataExtractor.getLogMonitorResponse(input.dataToProcess); - - if (!logResponse) { - return null; - } - - return `Log count was ${logResponse.logCount}.`; - } - - private static describeSpanCountObservation(input: { - dataToProcess: DataToProcess; - }): string | null { - const traceResponse: TraceMonitorResponse | null = - MonitorCriteriaDataExtractor.getTraceMonitorResponse( - input.dataToProcess, - ); - - if (!traceResponse) { - return null; - } - - return `Span count was ${traceResponse.spanCount}.`; - } - - private static describeMetricValueObservation(input: { - criteriaFilter: CriteriaFilter; - dataToProcess: DataToProcess; - monitorStep: MonitorStep; - }): string | null { - const metricValues = MonitorCriteriaDataExtractor.extractMetricValues({ - criteriaFilter: input.criteriaFilter, - dataToProcess: input.dataToProcess, - monitorStep: input.monitorStep, - }); - - if (!metricValues) { - return null; - } - - if (!metricValues.values.length) { - return `Metric Value${ - metricValues.alias ? ` (${metricValues.alias})` : "" - } returned no data points.`; - } - - const summary: string | null = - MonitorCriteriaMessageFormatter.summarizeNumericSeries( - metricValues.values, - ); - - if (!summary) { - return null; - } - - return `Metric Value${ - metricValues.alias ? ` (${metricValues.alias})` : "" - } recorded ${summary}.`; - } + public static describeFilterObservation(input: { + monitor: Monitor; + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + monitorStep: MonitorStep; + }): string | null { + const { criteriaFilter } = input; + + switch (criteriaFilter.checkOn) { + case CheckOn.ResponseTime: + return MonitorCriteriaObservationBuilder.describeResponseTimeObservation( + input, + ); + case CheckOn.ResponseStatusCode: + return MonitorCriteriaObservationBuilder.describeResponseStatusCodeObservation( + input, + ); + case CheckOn.ResponseHeader: + return MonitorCriteriaObservationBuilder.describeResponseHeaderObservation( + input, + ); + case CheckOn.ResponseHeaderValue: + return MonitorCriteriaObservationBuilder.describeResponseHeaderValueObservation( + input, + ); + case CheckOn.ResponseBody: + return MonitorCriteriaObservationBuilder.describeResponseBodyObservation( + input, + ); + case CheckOn.IsOnline: + return MonitorCriteriaObservationBuilder.describeIsOnlineObservation( + input, + ); + case CheckOn.IsRequestTimeout: + return MonitorCriteriaObservationBuilder.describeIsTimeoutObservation( + input, + ); + case CheckOn.IncomingRequest: + return MonitorCriteriaObservationBuilder.describeIncomingRequestObservation( + input, + ); + case CheckOn.RequestBody: + return MonitorCriteriaObservationBuilder.describeRequestBodyObservation( + input, + ); + case CheckOn.RequestHeader: + return MonitorCriteriaObservationBuilder.describeRequestHeaderObservation( + input, + ); + case CheckOn.RequestHeaderValue: + return MonitorCriteriaObservationBuilder.describeRequestHeaderValueObservation( + input, + ); + case CheckOn.JavaScriptExpression: + return MonitorCriteriaObservationBuilder.describeJavaScriptExpressionObservation( + input, + ); + case CheckOn.CPUUsagePercent: + return MonitorCriteriaObservationBuilder.describeCpuUsageObservation( + input, + ); + case CheckOn.MemoryUsagePercent: + return MonitorCriteriaObservationBuilder.describeMemoryUsageObservation( + input, + ); + case CheckOn.DiskUsagePercent: + return MonitorCriteriaObservationBuilder.describeDiskUsageObservation( + input, + ); + case CheckOn.ServerProcessName: + return MonitorCriteriaObservationBuilder.describeServerProcessNameObservation( + input, + ); + case CheckOn.ServerProcessPID: + return MonitorCriteriaObservationBuilder.describeServerProcessPidObservation( + input, + ); + case CheckOn.ServerProcessCommand: + return MonitorCriteriaObservationBuilder.describeServerProcessCommandObservation( + input, + ); + case CheckOn.ExpiresInHours: + return MonitorCriteriaObservationBuilder.describeCertificateExpiresInHoursObservation( + input, + ); + case CheckOn.ExpiresInDays: + return MonitorCriteriaObservationBuilder.describeCertificateExpiresInDaysObservation( + input, + ); + case CheckOn.IsSelfSignedCertificate: + return MonitorCriteriaObservationBuilder.describeIsSelfSignedObservation( + input, + ); + case CheckOn.IsExpiredCertificate: + return MonitorCriteriaObservationBuilder.describeIsExpiredObservation( + input, + ); + case CheckOn.IsValidCertificate: + return MonitorCriteriaObservationBuilder.describeIsValidObservation( + input, + ); + case CheckOn.IsNotAValidCertificate: + return MonitorCriteriaObservationBuilder.describeIsInvalidObservation( + input, + ); + case CheckOn.ResultValue: + return MonitorCriteriaObservationBuilder.describeResultValueObservation( + input, + ); + case CheckOn.Error: + return MonitorCriteriaObservationBuilder.describeErrorObservation( + input, + ); + case CheckOn.ExecutionTime: + return MonitorCriteriaObservationBuilder.describeExecutionTimeObservation( + input, + ); + case CheckOn.ScreenSizeType: + return MonitorCriteriaObservationBuilder.describeScreenSizeObservation( + input, + ); + case CheckOn.BrowserType: + return MonitorCriteriaObservationBuilder.describeBrowserObservation( + input, + ); + case CheckOn.LogCount: + return MonitorCriteriaObservationBuilder.describeLogCountObservation( + input, + ); + case CheckOn.SpanCount: + return MonitorCriteriaObservationBuilder.describeSpanCountObservation( + input, + ); + case CheckOn.MetricValue: + return MonitorCriteriaObservationBuilder.describeMetricValueObservation( + input, + ); + default: + return null; + } + } + + private static describeResponseTimeObservation(input: { + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + }): string | null { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(input.dataToProcess); + + if (!probeResponse) { + return null; + } + + const responseTime: number | undefined = + probeResponse.responseTimeInMs ?? undefined; + + if (responseTime === undefined || responseTime === null) { + return "Response time metric was not recorded"; + } + + const formatted: string | null = + MonitorCriteriaMessageFormatter.formatNumber(responseTime, { + maximumFractionDigits: 2, + }); + + const evaluationWindow: string | null = + MonitorCriteriaExpectationBuilder.getEvaluationWindowDescription( + input.criteriaFilter, + ); + + let message: string = `Response Time (in ms) was ${formatted ?? responseTime} ms`; + + if (evaluationWindow) { + message += ` ${evaluationWindow}`; + } + + return message; + } + + private static describeResponseStatusCodeObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(input.dataToProcess); + + if (!probeResponse) { + return null; + } + + if (probeResponse.responseCode === undefined) { + return "Response status code was not recorded"; + } + + return `Response Status Code was ${probeResponse.responseCode}.`; + } + + private static describeResponseHeaderObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(input.dataToProcess); + + if (!probeResponse) { + return null; + } + + const headers: Array = Object.keys( + probeResponse.responseHeaders || {}, + ).map((header: string) => { + return header.toLowerCase(); + }); + + if (!headers.length) { + return "Response headers were empty."; + } + + return `Response headers present: ${MonitorCriteriaMessageFormatter.formatList(headers)}.`; + } + + private static describeResponseHeaderValueObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(input.dataToProcess); + + if (!probeResponse) { + return null; + } + + const headerValues: Array = Object.values( + probeResponse.responseHeaders || {}, + ).map((value: string) => { + return value.toLowerCase(); + }); + + if (!headerValues.length) { + return "Response header values were empty."; + } + + return `Response header values: ${MonitorCriteriaMessageFormatter.formatList(headerValues)}.`; + } + + private static describeResponseBodyObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(input.dataToProcess); + + if (!probeResponse) { + return null; + } + + if (!probeResponse.responseBody) { + return "Response body was empty."; + } + + let bodyAsString: string; + + if (typeof probeResponse.responseBody === Typeof.Object) { + try { + bodyAsString = JSON.stringify(probeResponse.responseBody); + } catch (err) { + logger.error(err); + bodyAsString = "[object]"; + } + } else { + bodyAsString = probeResponse.responseBody as string; + } + + return `Response body sample: ${MonitorCriteriaMessageFormatter.formatSnippet(bodyAsString)}.`; + } + + private static describeIsOnlineObservation(input: { + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + }): string | null { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(input.dataToProcess); + + if (probeResponse && probeResponse.isOnline !== undefined) { + return `Monitor reported ${ + probeResponse.isOnline ? "online" : "offline" + } status at ${OneUptimeDate.getDateAsLocalFormattedString( + probeResponse.monitoredAt, + )}.`; + } + + const serverResponse: ServerMonitorResponse | null = + MonitorCriteriaDataExtractor.getServerMonitorResponse( + input.dataToProcess, + ); + + if (serverResponse) { + const lastHeartbeat: Date = serverResponse.requestReceivedAt; + const timeNow: Date = + serverResponse.timeNow || OneUptimeDate.getCurrentDate(); + const minutesSinceHeartbeat: number = + OneUptimeDate.getDifferenceInMinutes(lastHeartbeat, timeNow); + + const formattedMinutes: string | null = + MonitorCriteriaMessageFormatter.formatNumber(minutesSinceHeartbeat, { + maximumFractionDigits: 2, + }); + + return `Server heartbeat last received ${ + formattedMinutes ?? minutesSinceHeartbeat + } minutes ago.`; + } + + return null; + } + + private static describeIsTimeoutObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(input.dataToProcess); + + if (probeResponse && probeResponse.isTimeout !== undefined) { + return probeResponse.isTimeout + ? "Request timed out." + : "Request completed before timeout."; + } + + return "Timeout information was unavailable."; + } + + private static describeIncomingRequestObservation(input: { + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + }): string | null { + const incomingRequest: IncomingMonitorRequest | null = + MonitorCriteriaDataExtractor.getIncomingMonitorRequest( + input.dataToProcess, + ); + + if (!incomingRequest) { + return null; + } + + const lastHeartbeat: Date = incomingRequest.incomingRequestReceivedAt; + const checkedAt: Date = + incomingRequest.checkedAt || OneUptimeDate.getCurrentDate(); + + const minutesSinceHeartbeat: number = OneUptimeDate.getDifferenceInMinutes( + lastHeartbeat, + checkedAt, + ); + + const formattedMinutes: string | null = + MonitorCriteriaMessageFormatter.formatNumber(minutesSinceHeartbeat, { + maximumFractionDigits: 2, + }); + + return `Last incoming request was ${ + formattedMinutes ?? minutesSinceHeartbeat + } minutes ago (checked at ${OneUptimeDate.getDateAsLocalFormattedString( + checkedAt, + )}).`; + } + + private static describeRequestBodyObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const incomingRequest: IncomingMonitorRequest | null = + MonitorCriteriaDataExtractor.getIncomingMonitorRequest( + input.dataToProcess, + ); + + if (!incomingRequest) { + return null; + } + + const requestBody: string | JSONObject | undefined = + incomingRequest.requestBody; + + if (!requestBody) { + return "Request body was empty."; + } + + let requestBodyAsString: string; + + if (typeof requestBody === Typeof.Object) { + try { + requestBodyAsString = JSON.stringify(requestBody); + } catch (err) { + logger.error(err); + requestBodyAsString = "[object]"; + } + } else { + requestBodyAsString = requestBody as string; + } + + return `Request body sample: ${MonitorCriteriaMessageFormatter.formatSnippet(requestBodyAsString)}.`; + } + + private static describeRequestHeaderObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const incomingRequest: IncomingMonitorRequest | null = + MonitorCriteriaDataExtractor.getIncomingMonitorRequest( + input.dataToProcess, + ); + + if (!incomingRequest) { + return null; + } + + const headers: Array = Object.keys( + incomingRequest.requestHeaders || {}, + ).map((header: string) => { + return header.toLowerCase(); + }); + + if (!headers.length) { + return "Request headers were empty."; + } + + return `Request headers present: ${MonitorCriteriaMessageFormatter.formatList(headers)}.`; + } + + private static describeRequestHeaderValueObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const incomingRequest: IncomingMonitorRequest | null = + MonitorCriteriaDataExtractor.getIncomingMonitorRequest( + input.dataToProcess, + ); + + if (!incomingRequest) { + return null; + } + + const headerValues: Array = Object.values( + incomingRequest.requestHeaders || {}, + ).map((value: string) => { + return value.toLowerCase(); + }); + + if (!headerValues.length) { + return "Request header values were empty."; + } + + return `Request header values: ${MonitorCriteriaMessageFormatter.formatList(headerValues)}.`; + } + + private static describeJavaScriptExpressionObservation(input: { + criteriaFilter: CriteriaFilter; + }): string | null { + if (!input.criteriaFilter.value) { + return "JavaScript expression evaluated to false."; + } + + return `JavaScript expression "${input.criteriaFilter.value}" evaluated to false.`; + } + + private static describeCpuUsageObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const serverResponse: ServerMonitorResponse | null = + MonitorCriteriaDataExtractor.getServerMonitorResponse( + input.dataToProcess, + ); + + if (!serverResponse) { + return null; + } + + const cpuMetrics: BasicInfrastructureMetrics | undefined = + serverResponse.basicInfrastructureMetrics; + + if (!cpuMetrics || !cpuMetrics.cpuMetrics) { + return "CPU usage metrics were unavailable."; + } + + const cpuPercent: string | null = + MonitorCriteriaMessageFormatter.formatPercentage( + cpuMetrics.cpuMetrics.percentUsed, + ); + + const coreInfo: string = cpuMetrics.cpuMetrics.cores + ? ` across ${cpuMetrics.cpuMetrics.cores} core${ + cpuMetrics.cpuMetrics.cores > 1 ? "s" : "" + }` + : ""; + + return `CPU Usage (in %) was ${cpuPercent ?? "unavailable"}${coreInfo}.`; + } + + private static describeMemoryUsageObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const serverResponse: ServerMonitorResponse | null = + MonitorCriteriaDataExtractor.getServerMonitorResponse( + input.dataToProcess, + ); + + if (!serverResponse) { + return null; + } + + const memoryMetrics: BasicInfrastructureMetrics | undefined = + serverResponse.basicInfrastructureMetrics; + + if (!memoryMetrics || !memoryMetrics.memoryMetrics) { + return "Memory usage metrics were unavailable."; + } + + const percentUsed: string | null = + MonitorCriteriaMessageFormatter.formatPercentage( + memoryMetrics.memoryMetrics.percentUsed, + ); + + const used: string | null = MonitorCriteriaMessageFormatter.formatBytes( + memoryMetrics.memoryMetrics.used, + ); + const total: string | null = MonitorCriteriaMessageFormatter.formatBytes( + memoryMetrics.memoryMetrics.total, + ); + + return `Memory Usage (in %) was ${percentUsed ?? "unavailable"} (${used ?? "?"} used of ${total ?? "?"}).`; + } + + private static describeDiskUsageObservation(input: { + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + }): string | null { + const serverResponse: ServerMonitorResponse | null = + MonitorCriteriaDataExtractor.getServerMonitorResponse( + input.dataToProcess, + ); + + if (!serverResponse) { + return null; + } + + const diskPath: string = + input.criteriaFilter.serverMonitorOptions?.diskPath || "/"; + + const diskMetric: BasicInfrastructureMetrics | undefined = + serverResponse.basicInfrastructureMetrics; + + if (!diskMetric || !diskMetric.diskMetrics?.length) { + return `Disk metrics for path ${diskPath} were unavailable.`; + } + + const matchedDisk: BasicDiskMetrics | undefined = + diskMetric.diskMetrics.find((disk: BasicDiskMetrics) => { + return ( + disk.diskPath.trim().toLowerCase() === diskPath.trim().toLowerCase() + ); + }); + + if (!matchedDisk) { + return `Disk metrics did not include path ${diskPath}.`; + } + + const percentUsedValue: number | null = + MonitorCriteriaMessageFormatter.computeDiskUsagePercent(matchedDisk); + const percentUsed: string | null = + MonitorCriteriaMessageFormatter.formatPercentage( + percentUsedValue ?? undefined, + ); + + const used: string | null = MonitorCriteriaMessageFormatter.formatBytes( + matchedDisk.used, + ); + const total: string | null = MonitorCriteriaMessageFormatter.formatBytes( + matchedDisk.total, + ); + const free: string | null = MonitorCriteriaMessageFormatter.formatBytes( + matchedDisk.free, + ); + + return `Disk Usage (in %) on disk ${diskPath} was ${ + percentUsed ?? "unavailable" + } (${used ?? "?"} used of ${total ?? "?"}, free ${free ?? "?"}).`; + } + + private static describeServerProcessNameObservation(input: { + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + }): string | null { + const serverResponse: ServerMonitorResponse | null = + MonitorCriteriaDataExtractor.getServerMonitorResponse( + input.dataToProcess, + ); + + if (!serverResponse) { + return null; + } + + const thresholdName: string = (input.criteriaFilter.value ?? "") + .toString() + .trim() + .toLowerCase(); + + const processes: Array = serverResponse.processes || []; + + const matchingProcesses: Array = processes.filter( + (process: ServerProcess) => { + return process.name.trim().toLowerCase() === thresholdName; + }, + ); + + if (matchingProcesses.length > 0) { + const summary: string = matchingProcesses + .map((process: ServerProcess) => { + return `${process.name} (pid ${process.pid})`; + }) + .join(", "); + + return `Process ${input.criteriaFilter.value} is running (${summary}).`; + } + + const processSummary: string | null = + MonitorCriteriaMessageFormatter.describeProcesses(processes); + + if (processSummary) { + return `Process ${input.criteriaFilter.value} was not running. Active processes: ${processSummary}.`; + } + + return `Process ${input.criteriaFilter.value} was not running.`; + } + + private static describeServerProcessPidObservation(input: { + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + }): string | null { + const serverResponse: ServerMonitorResponse | null = + MonitorCriteriaDataExtractor.getServerMonitorResponse( + input.dataToProcess, + ); + + if (!serverResponse) { + return null; + } + + const thresholdPid: string = (input.criteriaFilter.value ?? "") + .toString() + .trim() + .toLowerCase(); + + const processes: Array = serverResponse.processes || []; + + const matchingProcesses: Array = processes.filter( + (process: ServerProcess) => { + return process.pid.toString().trim().toLowerCase() === thresholdPid; + }, + ); + + if (matchingProcesses.length > 0) { + const summary: string = matchingProcesses + .map((process: ServerProcess) => { + return `${process.name} (pid ${process.pid})`; + }) + .join(", "); + + return `Process with PID ${input.criteriaFilter.value} is running (${summary}).`; + } + + const processSummary: string | null = + MonitorCriteriaMessageFormatter.describeProcesses(processes); + + if (processSummary) { + return `Process with PID ${input.criteriaFilter.value} was not running. Active processes: ${processSummary}.`; + } + + return `Process with PID ${input.criteriaFilter.value} was not running.`; + } + + private static describeServerProcessCommandObservation(input: { + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + }): string | null { + const serverResponse: ServerMonitorResponse | null = + MonitorCriteriaDataExtractor.getServerMonitorResponse( + input.dataToProcess, + ); + + if (!serverResponse) { + return null; + } + + const thresholdCommand: string = (input.criteriaFilter.value ?? "") + .toString() + .trim() + .toLowerCase(); + + const processes: Array = serverResponse.processes || []; + + const matchingProcesses: Array = processes.filter( + (process: ServerProcess) => { + return process.command.trim().toLowerCase() === thresholdCommand; + }, + ); + + if (matchingProcesses.length > 0) { + const summary: string = matchingProcesses + .map((process: ServerProcess) => { + return `${process.command} (pid ${process.pid})`; + }) + .join(", "); + + return `Process with command ${input.criteriaFilter.value} is running (${summary}).`; + } + + const processSummary: string | null = + MonitorCriteriaMessageFormatter.describeProcesses(processes); + + if (processSummary) { + return `Process with command ${input.criteriaFilter.value} was not running. Active processes: ${processSummary}.`; + } + + return `Process with command ${input.criteriaFilter.value} was not running.`; + } + + private static describeCertificateExpiresInHoursObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const sslResponse: SslMonitorResponse | null = + MonitorCriteriaDataExtractor.getSslResponse(input.dataToProcess); + + if (!sslResponse || !sslResponse.expiresAt) { + return "SSL certificate expiration time was unavailable."; + } + + const hoursRemaining: number = OneUptimeDate.getHoursBetweenTwoDates( + OneUptimeDate.getCurrentDate(), + sslResponse.expiresAt, + ); + + const formattedHours: string | null = + MonitorCriteriaMessageFormatter.formatNumber(hoursRemaining, { + maximumFractionDigits: 2, + }); + + return `SSL certificate expires at ${OneUptimeDate.getDateAsLocalFormattedString( + sslResponse.expiresAt, + )} (${formattedHours ?? hoursRemaining} hours remaining).`; + } + + private static describeCertificateExpiresInDaysObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const sslResponse: SslMonitorResponse | null = + MonitorCriteriaDataExtractor.getSslResponse(input.dataToProcess); + + if (!sslResponse || !sslResponse.expiresAt) { + return "SSL certificate expiration time was unavailable."; + } + + const daysRemaining: number = OneUptimeDate.getDaysBetweenTwoDates( + OneUptimeDate.getCurrentDate(), + sslResponse.expiresAt, + ); + + const formattedDays: string | null = + MonitorCriteriaMessageFormatter.formatNumber(daysRemaining, { + maximumFractionDigits: 2, + }); + + return `SSL certificate expires at ${OneUptimeDate.getDateAsLocalFormattedString( + sslResponse.expiresAt, + )} (${formattedDays ?? daysRemaining} days remaining).`; + } + + private static describeIsSelfSignedObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const sslResponse: SslMonitorResponse | null = + MonitorCriteriaDataExtractor.getSslResponse(input.dataToProcess); + + if (!sslResponse || sslResponse.isSelfSigned === undefined) { + return "SSL certificate self-signed status was unavailable."; + } + + return sslResponse.isSelfSigned + ? "SSL certificate is self signed." + : "SSL certificate is not self signed."; + } + + private static describeIsExpiredObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const sslResponse: SslMonitorResponse | null = + MonitorCriteriaDataExtractor.getSslResponse(input.dataToProcess); + + if (!sslResponse || !sslResponse.expiresAt) { + return "SSL certificate expiration time was unavailable."; + } + + const isExpired: boolean = OneUptimeDate.isBefore( + sslResponse.expiresAt, + OneUptimeDate.getCurrentDate(), + ); + + return isExpired + ? "SSL certificate is expired." + : "SSL certificate is not expired."; + } + + private static describeIsValidObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(input.dataToProcess); + + const sslResponse: SslMonitorResponse | undefined = + probeResponse?.sslResponse; + + const isValid: boolean = Boolean( + sslResponse && + probeResponse?.isOnline && + sslResponse.expiresAt && + !sslResponse.isSelfSigned && + OneUptimeDate.isAfter( + sslResponse.expiresAt, + OneUptimeDate.getCurrentDate(), + ), + ); + + if (!sslResponse) { + return "SSL certificate details were unavailable."; + } + + return isValid + ? "SSL certificate is valid." + : "SSL certificate is not valid."; + } + + private static describeIsInvalidObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const probeResponse: ProbeMonitorResponse | null = + MonitorCriteriaDataExtractor.getProbeMonitorResponse(input.dataToProcess); + + const sslResponse: SslMonitorResponse | undefined = + probeResponse?.sslResponse; + + const isInvalid: boolean = + !sslResponse || + !probeResponse?.isOnline || + Boolean( + sslResponse && + sslResponse.expiresAt && + (sslResponse.isSelfSigned || + OneUptimeDate.isBefore( + sslResponse.expiresAt, + OneUptimeDate.getCurrentDate(), + )), + ); + + if (!sslResponse) { + return "SSL certificate details were unavailable."; + } + + return isInvalid + ? "SSL certificate is not valid." + : "SSL certificate is valid."; + } + + private static describeExecutionTimeObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const syntheticResponses: Array = + MonitorCriteriaDataExtractor.getSyntheticMonitorResponses( + input.dataToProcess, + ); + + const executionTimes: Array = syntheticResponses + .map((response: SyntheticMonitorResponse) => { + return response.executionTimeInMS; + }) + .filter((value: number) => { + return typeof value === "number" && !isNaN(value); + }); + + if (executionTimes.length > 0) { + const summary: string | null = + MonitorCriteriaMessageFormatter.summarizeNumericSeries(executionTimes); + + if (summary) { + return `Execution Time (in ms) recorded ${summary}.`; + } + } + + const customCodeResponse: CustomCodeMonitorResponse | null = + MonitorCriteriaDataExtractor.getCustomCodeMonitorResponse( + input.dataToProcess, + ); + + if (customCodeResponse) { + const formatted: string | null = + MonitorCriteriaMessageFormatter.formatNumber( + customCodeResponse.executionTimeInMS, + { maximumFractionDigits: 2 }, + ); + + return `Execution Time (in ms) was ${ + formatted ?? customCodeResponse.executionTimeInMS + } ms.`; + } + + return "Execution time was unavailable."; + } + + private static describeResultValueObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const syntheticResponses: Array = + MonitorCriteriaDataExtractor.getSyntheticMonitorResponses( + input.dataToProcess, + ); + + const resultValues: Array = syntheticResponses + .map((response: SyntheticMonitorResponse) => { + return MonitorCriteriaMessageFormatter.formatResultValue( + response.result, + ); + }) + .filter((value: string) => { + return value !== "undefined"; + }); + + if (resultValues.length > 0) { + const uniqueResults: Array = Array.from(new Set(resultValues)); + + return `Result Value samples: ${MonitorCriteriaMessageFormatter.formatList(uniqueResults)}.`; + } + + const customCodeResponse: CustomCodeMonitorResponse | null = + MonitorCriteriaDataExtractor.getCustomCodeMonitorResponse( + input.dataToProcess, + ); + + if (customCodeResponse && customCodeResponse.result !== undefined) { + const formatted: string = + MonitorCriteriaMessageFormatter.formatResultValue( + customCodeResponse.result, + ); + + return `Result Value was ${MonitorCriteriaMessageFormatter.formatSnippet(formatted)}.`; + } + + return "Result value was unavailable."; + } + + private static describeErrorObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const syntheticResponses: Array = + MonitorCriteriaDataExtractor.getSyntheticMonitorResponses( + input.dataToProcess, + ); + + const errors: Array = syntheticResponses + .map((response: SyntheticMonitorResponse) => { + return response.scriptError; + }) + .filter((value: string | undefined): value is string => { + return Boolean(value); + }) + .map((error: string) => { + return MonitorCriteriaMessageFormatter.formatSnippet(error, 80); + }); + + if (errors.length > 0) { + return `Script errors: ${MonitorCriteriaMessageFormatter.formatList(errors)}.`; + } + + const customCodeResponse: CustomCodeMonitorResponse | null = + MonitorCriteriaDataExtractor.getCustomCodeMonitorResponse( + input.dataToProcess, + ); + + if (customCodeResponse?.scriptError) { + return `Script error: ${MonitorCriteriaMessageFormatter.formatSnippet(customCodeResponse.scriptError, 80)}.`; + } + + if (customCodeResponse?.logMessages?.length) { + return `Script log messages: ${MonitorCriteriaMessageFormatter.formatList(customCodeResponse.logMessages)}.`; + } + + return "No script errors were reported."; + } + + private static describeScreenSizeObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const syntheticResponses: Array = + MonitorCriteriaDataExtractor.getSyntheticMonitorResponses( + input.dataToProcess, + ); + + if (!syntheticResponses.length) { + return "Synthetic monitor results were unavailable."; + } + + const screenSizes: Array = Array.from( + new Set( + syntheticResponses.map((response: SyntheticMonitorResponse) => { + return response.screenSizeType; + }), + ), + ); + + return `Synthetic monitor screen sizes: ${MonitorCriteriaMessageFormatter.formatList(screenSizes)}.`; + } + + private static describeBrowserObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const syntheticResponses: Array = + MonitorCriteriaDataExtractor.getSyntheticMonitorResponses( + input.dataToProcess, + ); + + if (!syntheticResponses.length) { + return "Synthetic monitor results were unavailable."; + } + + const browsers: Array = Array.from( + new Set( + syntheticResponses.map((response: SyntheticMonitorResponse) => { + return response.browserType; + }), + ), + ); + + return `Synthetic monitor browsers: ${MonitorCriteriaMessageFormatter.formatList(browsers)}.`; + } + + private static describeLogCountObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const logResponse: LogMonitorResponse | null = + MonitorCriteriaDataExtractor.getLogMonitorResponse(input.dataToProcess); + + if (!logResponse) { + return null; + } + + return `Log count was ${logResponse.logCount}.`; + } + + private static describeSpanCountObservation(input: { + dataToProcess: DataToProcess; + }): string | null { + const traceResponse: TraceMonitorResponse | null = + MonitorCriteriaDataExtractor.getTraceMonitorResponse(input.dataToProcess); + + if (!traceResponse) { + return null; + } + + return `Span count was ${traceResponse.spanCount}.`; + } + + private static describeMetricValueObservation(input: { + criteriaFilter: CriteriaFilter; + dataToProcess: DataToProcess; + monitorStep: MonitorStep; + }): string | null { + const metricValues: { + alias: string | null; + values: Array; + } | null = MonitorCriteriaDataExtractor.extractMetricValues({ + criteriaFilter: input.criteriaFilter, + dataToProcess: input.dataToProcess, + monitorStep: input.monitorStep, + }); + + if (!metricValues) { + return null; + } + + if (!metricValues.values.length) { + return `Metric Value${ + metricValues.alias ? ` (${metricValues.alias})` : "" + } returned no data points.`; + } + + const summary: string | null = + MonitorCriteriaMessageFormatter.summarizeNumericSeries( + metricValues.values, + ); + + if (!summary) { + return null; + } + + return `Metric Value${ + metricValues.alias ? ` (${metricValues.alias})` : "" + } recorded ${summary}.`; + } } diff --git a/Common/Server/Utils/Monitor/MonitorMetricUtil.ts b/Common/Server/Utils/Monitor/MonitorMetricUtil.ts index 4644620ec5..a211b61b3e 100644 --- a/Common/Server/Utils/Monitor/MonitorMetricUtil.ts +++ b/Common/Server/Utils/Monitor/MonitorMetricUtil.ts @@ -3,7 +3,10 @@ import CaptureSpan from "../Telemetry/CaptureSpan"; import TelemetryUtil from "../Telemetry/Telemetry"; import MetricService from "../../Services/MetricService"; import DataToProcess from "./DataToProcess"; -import { MetricPointType, ServiceType } from "../../../Models/AnalyticsModels/Metric"; +import { + MetricPointType, + ServiceType, +} from "../../../Models/AnalyticsModels/Metric"; import MetricType from "../../../Models/DatabaseModels/MetricType"; import BasicInfrastructureMetrics from "../../../Types/Infrastructure/BasicMetrics"; import Dictionary from "../../../Types/Dictionary"; @@ -17,464 +20,463 @@ import ObjectID from "../../../Types/ObjectID"; import OneUptimeDate from "../../../Types/Date"; export default class MonitorMetricUtil { - private static buildMonitorMetricAttributes(data: { - monitorId: ObjectID; - projectId: ObjectID; - monitorName?: string | undefined; - probeName?: string | undefined; - extraAttributes?: JSONObject; - }): JSONObject { - const attributes: JSONObject = { - monitorId: data.monitorId.toString(), - projectId: data.projectId.toString(), - }; - - if (data.extraAttributes) { - Object.assign(attributes, data.extraAttributes); - } - - if (data.monitorName) { - attributes["monitorName"] = data.monitorName; - } - - if (data.probeName) { - attributes["probeName"] = data.probeName; - } - - return attributes; - } - - private static buildMonitorMetricRow(data: { - projectId: ObjectID; - monitorId: ObjectID; - metricName: string; - value: number | null | undefined; - attributes: JSONObject; - metricPointType?: MetricPointType; - }): JSONObject { - const ingestionDate: Date = OneUptimeDate.getCurrentDate(); - const ingestionTimestamp: string = - OneUptimeDate.toClickhouseDateTime(ingestionDate); - const timeUnixNano: string = - OneUptimeDate.toUnixNano(ingestionDate).toString(); - - const attributes: JSONObject = { ...data.attributes }; - const attributeKeys: Array = - TelemetryUtil.getAttributeKeys(attributes); - - return { - _id: ObjectID.generate().toString(), - createdAt: ingestionTimestamp, - updatedAt: ingestionTimestamp, - projectId: data.projectId.toString(), - serviceId: data.monitorId.toString(), - serviceType: ServiceType.Monitor, - name: data.metricName, - aggregationTemporality: null, - metricPointType: data.metricPointType || MetricPointType.Sum, - time: ingestionTimestamp, - startTime: null, - timeUnixNano: timeUnixNano, - startTimeUnixNano: null, - attributes: attributes, - attributeKeys: attributeKeys, - isMonotonic: null, - count: null, - sum: null, - min: null, - max: null, - bucketCounts: [], - explicitBounds: [], - value: data.value ?? null, - } as JSONObject; - } - - @CaptureSpan() - public static async saveMonitorMetrics(data: { - monitorId: ObjectID; - projectId: ObjectID; - dataToProcess: DataToProcess; - probeName: string | undefined; - monitorName: string | undefined; - }): Promise { - if (!data.monitorId) { - return; - } - - if (!data.projectId) { - return; - } - - if (!data.dataToProcess) { - return; - } - - const metricRows: Array = []; - - /* - * Metric name to serviceId map - * example: "cpu.usage" -> [serviceId1, serviceId2] - * since these are monitor metrics. They dont belong to any service so we can keep the array empty. - */ - const metricNameServiceNameMap: Dictionary = {}; - - if ( - (data.dataToProcess as ServerMonitorResponse).basicInfrastructureMetrics - ) { - // store cpu, memory, disk metrics. - - if ((data.dataToProcess as ServerMonitorResponse).requestReceivedAt) { - let isOnline: boolean = true; - - const differenceInMinutes: number = - OneUptimeDate.getDifferenceInMinutes( - (data.dataToProcess as ServerMonitorResponse).requestReceivedAt, - OneUptimeDate.getCurrentDate(), - ); - - if (differenceInMinutes > 2) { - isOnline = false; - } - - const attributes: JSONObject = this.buildMonitorMetricAttributes({ - monitorId: data.monitorId, - projectId: data.projectId, - monitorName: data.monitorName, - probeName: data.probeName, - }); - - const metricRow: JSONObject = this.buildMonitorMetricRow({ - projectId: data.projectId, - monitorId: data.monitorId, - metricName: MonitorMetricType.IsOnline, - value: isOnline ? 1 : 0, - attributes: attributes, - metricPointType: MetricPointType.Sum, - }); - - metricRows.push(metricRow); - - // add MetricType - const metricType: MetricType = new MetricType(); - metricType.name = MonitorMetricType.IsOnline; - metricType.description = CheckOn.IsOnline + " status for monitor"; - metricType.unit = ""; - - // add to map - metricNameServiceNameMap[MonitorMetricType.IsOnline] = metricType; - } - - const basicMetrics: BasicInfrastructureMetrics | undefined = ( - data.dataToProcess as ServerMonitorResponse - ).basicInfrastructureMetrics; - - if (!basicMetrics) { - return; - } - - if (basicMetrics.cpuMetrics) { - const attributes: JSONObject = this.buildMonitorMetricAttributes({ - monitorId: data.monitorId, - projectId: data.projectId, - monitorName: data.monitorName, - probeName: data.probeName, - }); - - const metricRow: JSONObject = this.buildMonitorMetricRow({ - projectId: data.projectId, - monitorId: data.monitorId, - metricName: MonitorMetricType.CPUUsagePercent, - value: basicMetrics.cpuMetrics.percentUsed ?? null, - attributes: attributes, - metricPointType: MetricPointType.Sum, - }); - - metricRows.push(metricRow); - - const metricType: MetricType = new MetricType(); - metricType.name = MonitorMetricType.CPUUsagePercent; - metricType.description = CheckOn.CPUUsagePercent + " of Server/VM"; - metricType.unit = "%"; - - metricNameServiceNameMap[MonitorMetricType.CPUUsagePercent] = - metricType; - } - - if (basicMetrics.memoryMetrics) { - const attributes: JSONObject = this.buildMonitorMetricAttributes({ - monitorId: data.monitorId, - projectId: data.projectId, - monitorName: data.monitorName, - probeName: data.probeName, - }); - - const metricRow: JSONObject = this.buildMonitorMetricRow({ - projectId: data.projectId, - monitorId: data.monitorId, - metricName: MonitorMetricType.MemoryUsagePercent, - value: basicMetrics.memoryMetrics.percentUsed ?? null, - attributes: attributes, - metricPointType: MetricPointType.Sum, - }); - - metricRows.push(metricRow); - - const metricType: MetricType = new MetricType(); - metricType.name = MonitorMetricType.MemoryUsagePercent; - metricType.description = CheckOn.MemoryUsagePercent + " of Server/VM"; - metricType.unit = "%"; - - metricNameServiceNameMap[MonitorMetricType.MemoryUsagePercent] = - metricType; - } - - if (basicMetrics.diskMetrics && basicMetrics.diskMetrics.length > 0) { - for (const diskMetric of basicMetrics.diskMetrics) { - const extraAttributes: JSONObject = {}; - - if (diskMetric.diskPath) { - extraAttributes["diskPath"] = diskMetric.diskPath; - } - - const attributes: JSONObject = this.buildMonitorMetricAttributes({ - monitorId: data.monitorId, - projectId: data.projectId, - monitorName: data.monitorName, - probeName: data.probeName, - extraAttributes: extraAttributes, - }); - - const metricRow: JSONObject = this.buildMonitorMetricRow({ - projectId: data.projectId, - monitorId: data.monitorId, - metricName: MonitorMetricType.DiskUsagePercent, - value: diskMetric.percentUsed ?? null, - attributes: attributes, - metricPointType: MetricPointType.Sum, - }); - - metricRows.push(metricRow); - - const metricType: MetricType = new MetricType(); - metricType.name = MonitorMetricType.DiskUsagePercent; - metricType.description = CheckOn.DiskUsagePercent + " of Server/VM"; - metricType.unit = "%"; - - metricNameServiceNameMap[MonitorMetricType.DiskUsagePercent] = - metricType; - } - } - } - - if ( - (data.dataToProcess as ProbeMonitorResponse).customCodeMonitorResponse - ?.executionTimeInMS - ) { - const extraAttributes: JSONObject = { - probeId: ( - data.dataToProcess as ProbeMonitorResponse - ).probeId.toString(), - }; - - const attributes: JSONObject = this.buildMonitorMetricAttributes({ - monitorId: data.monitorId, - projectId: data.projectId, - extraAttributes: extraAttributes, - }); - - const metricRow: JSONObject = this.buildMonitorMetricRow({ - projectId: data.projectId, - monitorId: data.monitorId, - metricName: MonitorMetricType.ExecutionTime, - value: - (data.dataToProcess as ProbeMonitorResponse).customCodeMonitorResponse - ?.executionTimeInMS ?? null, - attributes: attributes, - metricPointType: MetricPointType.Sum, - }); - - metricRows.push(metricRow); - - const metricType: MetricType = new MetricType(); - metricType.name = MonitorMetricType.ExecutionTime; - metricType.description = CheckOn.ExecutionTime + " of this monitor"; - metricType.unit = "ms"; - - metricNameServiceNameMap[MonitorMetricType.ExecutionTime] = metricType; - } - - if ( - (data.dataToProcess as ProbeMonitorResponse) && - (data.dataToProcess as ProbeMonitorResponse).syntheticMonitorResponse && - ( - (data.dataToProcess as ProbeMonitorResponse).syntheticMonitorResponse || - [] - ).length > 0 - ) { - const syntheticResponses: Array = - (data.dataToProcess as ProbeMonitorResponse).syntheticMonitorResponse || - []; - - for (const syntheticMonitorResponse of syntheticResponses) { - const extraAttributes: JSONObject = { - probeId: ( - data.dataToProcess as ProbeMonitorResponse - ).probeId.toString(), - }; - - if (syntheticMonitorResponse.browserType) { - extraAttributes["browserType"] = syntheticMonitorResponse.browserType; - } - - if (syntheticMonitorResponse.screenSizeType) { - extraAttributes["screenSizeType"] = - syntheticMonitorResponse.screenSizeType; - } - - const attributes: JSONObject = this.buildMonitorMetricAttributes({ - monitorId: data.monitorId, - projectId: data.projectId, - monitorName: data.monitorName, - probeName: data.probeName, - extraAttributes: extraAttributes, - }); - - const metricRow: JSONObject = this.buildMonitorMetricRow({ - projectId: data.projectId, - monitorId: data.monitorId, - metricName: MonitorMetricType.ExecutionTime, - value: syntheticMonitorResponse.executionTimeInMS ?? null, - attributes: attributes, - metricPointType: MetricPointType.Sum, - }); - - metricRows.push(metricRow); - - const metricType: MetricType = new MetricType(); - metricType.name = MonitorMetricType.ExecutionTime; - metricType.description = CheckOn.ExecutionTime + " of this monitor"; - metricType.unit = "ms"; - - metricNameServiceNameMap[MonitorMetricType.ExecutionTime] = metricType; - } - } - - if ((data.dataToProcess as ProbeMonitorResponse).responseTimeInMs) { - const extraAttributes: JSONObject = { - probeId: ( - data.dataToProcess as ProbeMonitorResponse - ).probeId.toString(), - }; - - const attributes: JSONObject = this.buildMonitorMetricAttributes({ - monitorId: data.monitorId, - projectId: data.projectId, - monitorName: data.monitorName, - probeName: data.probeName, - extraAttributes: extraAttributes, - }); - - const metricRow: JSONObject = this.buildMonitorMetricRow({ - projectId: data.projectId, - monitorId: data.monitorId, - metricName: MonitorMetricType.ResponseTime, - value: - (data.dataToProcess as ProbeMonitorResponse).responseTimeInMs ?? null, - attributes: attributes, - metricPointType: MetricPointType.Sum, - }); - - metricRows.push(metricRow); - - const metricType: MetricType = new MetricType(); - metricType.name = MonitorMetricType.ResponseTime; - metricType.description = CheckOn.ResponseTime + " of this monitor"; - metricType.unit = "ms"; - - metricNameServiceNameMap[MonitorMetricType.ResponseTime] = metricType; - } - - if ((data.dataToProcess as ProbeMonitorResponse).isOnline !== undefined) { - const extraAttributes: JSONObject = { - probeId: ( - data.dataToProcess as ProbeMonitorResponse - ).probeId.toString(), - }; - - const attributes: JSONObject = this.buildMonitorMetricAttributes({ - monitorId: data.monitorId, - projectId: data.projectId, - monitorName: data.monitorName, - probeName: data.probeName, - extraAttributes: extraAttributes, - }); - - const metricRow: JSONObject = this.buildMonitorMetricRow({ - projectId: data.projectId, - monitorId: data.monitorId, - metricName: MonitorMetricType.IsOnline, - value: (data.dataToProcess as ProbeMonitorResponse).isOnline ? 1 : 0, - attributes: attributes, - metricPointType: MetricPointType.Sum, - }); - - metricRows.push(metricRow); - - const metricType: MetricType = new MetricType(); - metricType.name = MonitorMetricType.IsOnline; - metricType.description = CheckOn.IsOnline + " status for monitor"; - metricType.unit = ""; - - metricNameServiceNameMap[MonitorMetricType.IsOnline] = metricType; - } - - if ((data.dataToProcess as ProbeMonitorResponse).responseCode) { - const extraAttributes: JSONObject = { - probeId: ( - data.dataToProcess as ProbeMonitorResponse - ).probeId.toString(), - }; - - const attributes: JSONObject = this.buildMonitorMetricAttributes({ - monitorId: data.monitorId, - projectId: data.projectId, - monitorName: data.monitorName, - probeName: data.probeName, - extraAttributes: extraAttributes, - }); - - const metricRow: JSONObject = this.buildMonitorMetricRow({ - projectId: data.projectId, - monitorId: data.monitorId, - metricName: MonitorMetricType.ResponseStatusCode, - value: - (data.dataToProcess as ProbeMonitorResponse).responseCode ?? null, - attributes: attributes, - metricPointType: MetricPointType.Sum, - }); - - metricRows.push(metricRow); - - const metricType: MetricType = new MetricType(); - metricType.name = MonitorMetricType.ResponseStatusCode; - metricType.description = CheckOn.ResponseStatusCode + - " for this monitor"; - metricType.unit = "Status Code"; - - metricNameServiceNameMap[MonitorMetricType.ResponseStatusCode] = - metricType; - } - - if (metricRows.length > 0) { - await MetricService.insertJsonRows(metricRows); - } - - // index metrics - TelemetryUtil.indexMetricNameServiceNameMap({ - projectId: data.projectId, - metricNameServiceNameMap: metricNameServiceNameMap, - }).catch((err: Error) => { - logger.error(err); - }); - } + private static buildMonitorMetricAttributes(data: { + monitorId: ObjectID; + projectId: ObjectID; + monitorName?: string | undefined; + probeName?: string | undefined; + extraAttributes?: JSONObject; + }): JSONObject { + const attributes: JSONObject = { + monitorId: data.monitorId.toString(), + projectId: data.projectId.toString(), + }; + + if (data.extraAttributes) { + Object.assign(attributes, data.extraAttributes); + } + + if (data.monitorName) { + attributes["monitorName"] = data.monitorName; + } + + if (data.probeName) { + attributes["probeName"] = data.probeName; + } + + return attributes; + } + + private static buildMonitorMetricRow(data: { + projectId: ObjectID; + monitorId: ObjectID; + metricName: string; + value: number | null | undefined; + attributes: JSONObject; + metricPointType?: MetricPointType; + }): JSONObject { + const ingestionDate: Date = OneUptimeDate.getCurrentDate(); + const ingestionTimestamp: string = + OneUptimeDate.toClickhouseDateTime(ingestionDate); + const timeUnixNano: string = + OneUptimeDate.toUnixNano(ingestionDate).toString(); + + const attributes: JSONObject = { ...data.attributes }; + const attributeKeys: Array = + TelemetryUtil.getAttributeKeys(attributes); + + return { + _id: ObjectID.generate().toString(), + createdAt: ingestionTimestamp, + updatedAt: ingestionTimestamp, + projectId: data.projectId.toString(), + serviceId: data.monitorId.toString(), + serviceType: ServiceType.Monitor, + name: data.metricName, + aggregationTemporality: null, + metricPointType: data.metricPointType || MetricPointType.Sum, + time: ingestionTimestamp, + startTime: null, + timeUnixNano: timeUnixNano, + startTimeUnixNano: null, + attributes: attributes, + attributeKeys: attributeKeys, + isMonotonic: null, + count: null, + sum: null, + min: null, + max: null, + bucketCounts: [], + explicitBounds: [], + value: data.value ?? null, + } as JSONObject; + } + + @CaptureSpan() + public static async saveMonitorMetrics(data: { + monitorId: ObjectID; + projectId: ObjectID; + dataToProcess: DataToProcess; + probeName: string | undefined; + monitorName: string | undefined; + }): Promise { + if (!data.monitorId) { + return; + } + + if (!data.projectId) { + return; + } + + if (!data.dataToProcess) { + return; + } + + const metricRows: Array = []; + + /* + * Metric name to serviceId map + * example: "cpu.usage" -> [serviceId1, serviceId2] + * since these are monitor metrics. They dont belong to any service so we can keep the array empty. + */ + const metricNameServiceNameMap: Dictionary = {}; + + if ( + (data.dataToProcess as ServerMonitorResponse).basicInfrastructureMetrics + ) { + // store cpu, memory, disk metrics. + + if ((data.dataToProcess as ServerMonitorResponse).requestReceivedAt) { + let isOnline: boolean = true; + + const differenceInMinutes: number = + OneUptimeDate.getDifferenceInMinutes( + (data.dataToProcess as ServerMonitorResponse).requestReceivedAt, + OneUptimeDate.getCurrentDate(), + ); + + if (differenceInMinutes > 2) { + isOnline = false; + } + + const attributes: JSONObject = this.buildMonitorMetricAttributes({ + monitorId: data.monitorId, + projectId: data.projectId, + monitorName: data.monitorName, + probeName: data.probeName, + }); + + const metricRow: JSONObject = this.buildMonitorMetricRow({ + projectId: data.projectId, + monitorId: data.monitorId, + metricName: MonitorMetricType.IsOnline, + value: isOnline ? 1 : 0, + attributes: attributes, + metricPointType: MetricPointType.Sum, + }); + + metricRows.push(metricRow); + + // add MetricType + const metricType: MetricType = new MetricType(); + metricType.name = MonitorMetricType.IsOnline; + metricType.description = CheckOn.IsOnline + " status for monitor"; + metricType.unit = ""; + + // add to map + metricNameServiceNameMap[MonitorMetricType.IsOnline] = metricType; + } + + const basicMetrics: BasicInfrastructureMetrics | undefined = ( + data.dataToProcess as ServerMonitorResponse + ).basicInfrastructureMetrics; + + if (!basicMetrics) { + return; + } + + if (basicMetrics.cpuMetrics) { + const attributes: JSONObject = this.buildMonitorMetricAttributes({ + monitorId: data.monitorId, + projectId: data.projectId, + monitorName: data.monitorName, + probeName: data.probeName, + }); + + const metricRow: JSONObject = this.buildMonitorMetricRow({ + projectId: data.projectId, + monitorId: data.monitorId, + metricName: MonitorMetricType.CPUUsagePercent, + value: basicMetrics.cpuMetrics.percentUsed ?? null, + attributes: attributes, + metricPointType: MetricPointType.Sum, + }); + + metricRows.push(metricRow); + + const metricType: MetricType = new MetricType(); + metricType.name = MonitorMetricType.CPUUsagePercent; + metricType.description = CheckOn.CPUUsagePercent + " of Server/VM"; + metricType.unit = "%"; + + metricNameServiceNameMap[MonitorMetricType.CPUUsagePercent] = + metricType; + } + + if (basicMetrics.memoryMetrics) { + const attributes: JSONObject = this.buildMonitorMetricAttributes({ + monitorId: data.monitorId, + projectId: data.projectId, + monitorName: data.monitorName, + probeName: data.probeName, + }); + + const metricRow: JSONObject = this.buildMonitorMetricRow({ + projectId: data.projectId, + monitorId: data.monitorId, + metricName: MonitorMetricType.MemoryUsagePercent, + value: basicMetrics.memoryMetrics.percentUsed ?? null, + attributes: attributes, + metricPointType: MetricPointType.Sum, + }); + + metricRows.push(metricRow); + + const metricType: MetricType = new MetricType(); + metricType.name = MonitorMetricType.MemoryUsagePercent; + metricType.description = CheckOn.MemoryUsagePercent + " of Server/VM"; + metricType.unit = "%"; + + metricNameServiceNameMap[MonitorMetricType.MemoryUsagePercent] = + metricType; + } + + if (basicMetrics.diskMetrics && basicMetrics.diskMetrics.length > 0) { + for (const diskMetric of basicMetrics.diskMetrics) { + const extraAttributes: JSONObject = {}; + + if (diskMetric.diskPath) { + extraAttributes["diskPath"] = diskMetric.diskPath; + } + + const attributes: JSONObject = this.buildMonitorMetricAttributes({ + monitorId: data.monitorId, + projectId: data.projectId, + monitorName: data.monitorName, + probeName: data.probeName, + extraAttributes: extraAttributes, + }); + + const metricRow: JSONObject = this.buildMonitorMetricRow({ + projectId: data.projectId, + monitorId: data.monitorId, + metricName: MonitorMetricType.DiskUsagePercent, + value: diskMetric.percentUsed ?? null, + attributes: attributes, + metricPointType: MetricPointType.Sum, + }); + + metricRows.push(metricRow); + + const metricType: MetricType = new MetricType(); + metricType.name = MonitorMetricType.DiskUsagePercent; + metricType.description = CheckOn.DiskUsagePercent + " of Server/VM"; + metricType.unit = "%"; + + metricNameServiceNameMap[MonitorMetricType.DiskUsagePercent] = + metricType; + } + } + } + + if ( + (data.dataToProcess as ProbeMonitorResponse).customCodeMonitorResponse + ?.executionTimeInMS + ) { + const extraAttributes: JSONObject = { + probeId: ( + data.dataToProcess as ProbeMonitorResponse + ).probeId.toString(), + }; + + const attributes: JSONObject = this.buildMonitorMetricAttributes({ + monitorId: data.monitorId, + projectId: data.projectId, + extraAttributes: extraAttributes, + }); + + const metricRow: JSONObject = this.buildMonitorMetricRow({ + projectId: data.projectId, + monitorId: data.monitorId, + metricName: MonitorMetricType.ExecutionTime, + value: + (data.dataToProcess as ProbeMonitorResponse).customCodeMonitorResponse + ?.executionTimeInMS ?? null, + attributes: attributes, + metricPointType: MetricPointType.Sum, + }); + + metricRows.push(metricRow); + + const metricType: MetricType = new MetricType(); + metricType.name = MonitorMetricType.ExecutionTime; + metricType.description = CheckOn.ExecutionTime + " of this monitor"; + metricType.unit = "ms"; + + metricNameServiceNameMap[MonitorMetricType.ExecutionTime] = metricType; + } + + if ( + (data.dataToProcess as ProbeMonitorResponse) && + (data.dataToProcess as ProbeMonitorResponse).syntheticMonitorResponse && + ( + (data.dataToProcess as ProbeMonitorResponse).syntheticMonitorResponse || + [] + ).length > 0 + ) { + const syntheticResponses: Array = + (data.dataToProcess as ProbeMonitorResponse).syntheticMonitorResponse || + []; + + for (const syntheticMonitorResponse of syntheticResponses) { + const extraAttributes: JSONObject = { + probeId: ( + data.dataToProcess as ProbeMonitorResponse + ).probeId.toString(), + }; + + if (syntheticMonitorResponse.browserType) { + extraAttributes["browserType"] = syntheticMonitorResponse.browserType; + } + + if (syntheticMonitorResponse.screenSizeType) { + extraAttributes["screenSizeType"] = + syntheticMonitorResponse.screenSizeType; + } + + const attributes: JSONObject = this.buildMonitorMetricAttributes({ + monitorId: data.monitorId, + projectId: data.projectId, + monitorName: data.monitorName, + probeName: data.probeName, + extraAttributes: extraAttributes, + }); + + const metricRow: JSONObject = this.buildMonitorMetricRow({ + projectId: data.projectId, + monitorId: data.monitorId, + metricName: MonitorMetricType.ExecutionTime, + value: syntheticMonitorResponse.executionTimeInMS ?? null, + attributes: attributes, + metricPointType: MetricPointType.Sum, + }); + + metricRows.push(metricRow); + + const metricType: MetricType = new MetricType(); + metricType.name = MonitorMetricType.ExecutionTime; + metricType.description = CheckOn.ExecutionTime + " of this monitor"; + metricType.unit = "ms"; + + metricNameServiceNameMap[MonitorMetricType.ExecutionTime] = metricType; + } + } + + if ((data.dataToProcess as ProbeMonitorResponse).responseTimeInMs) { + const extraAttributes: JSONObject = { + probeId: ( + data.dataToProcess as ProbeMonitorResponse + ).probeId.toString(), + }; + + const attributes: JSONObject = this.buildMonitorMetricAttributes({ + monitorId: data.monitorId, + projectId: data.projectId, + monitorName: data.monitorName, + probeName: data.probeName, + extraAttributes: extraAttributes, + }); + + const metricRow: JSONObject = this.buildMonitorMetricRow({ + projectId: data.projectId, + monitorId: data.monitorId, + metricName: MonitorMetricType.ResponseTime, + value: + (data.dataToProcess as ProbeMonitorResponse).responseTimeInMs ?? null, + attributes: attributes, + metricPointType: MetricPointType.Sum, + }); + + metricRows.push(metricRow); + + const metricType: MetricType = new MetricType(); + metricType.name = MonitorMetricType.ResponseTime; + metricType.description = CheckOn.ResponseTime + " of this monitor"; + metricType.unit = "ms"; + + metricNameServiceNameMap[MonitorMetricType.ResponseTime] = metricType; + } + + if ((data.dataToProcess as ProbeMonitorResponse).isOnline !== undefined) { + const extraAttributes: JSONObject = { + probeId: ( + data.dataToProcess as ProbeMonitorResponse + ).probeId.toString(), + }; + + const attributes: JSONObject = this.buildMonitorMetricAttributes({ + monitorId: data.monitorId, + projectId: data.projectId, + monitorName: data.monitorName, + probeName: data.probeName, + extraAttributes: extraAttributes, + }); + + const metricRow: JSONObject = this.buildMonitorMetricRow({ + projectId: data.projectId, + monitorId: data.monitorId, + metricName: MonitorMetricType.IsOnline, + value: (data.dataToProcess as ProbeMonitorResponse).isOnline ? 1 : 0, + attributes: attributes, + metricPointType: MetricPointType.Sum, + }); + + metricRows.push(metricRow); + + const metricType: MetricType = new MetricType(); + metricType.name = MonitorMetricType.IsOnline; + metricType.description = CheckOn.IsOnline + " status for monitor"; + metricType.unit = ""; + + metricNameServiceNameMap[MonitorMetricType.IsOnline] = metricType; + } + + if ((data.dataToProcess as ProbeMonitorResponse).responseCode) { + const extraAttributes: JSONObject = { + probeId: ( + data.dataToProcess as ProbeMonitorResponse + ).probeId.toString(), + }; + + const attributes: JSONObject = this.buildMonitorMetricAttributes({ + monitorId: data.monitorId, + projectId: data.projectId, + monitorName: data.monitorName, + probeName: data.probeName, + extraAttributes: extraAttributes, + }); + + const metricRow: JSONObject = this.buildMonitorMetricRow({ + projectId: data.projectId, + monitorId: data.monitorId, + metricName: MonitorMetricType.ResponseStatusCode, + value: + (data.dataToProcess as ProbeMonitorResponse).responseCode ?? null, + attributes: attributes, + metricPointType: MetricPointType.Sum, + }); + + metricRows.push(metricRow); + + const metricType: MetricType = new MetricType(); + metricType.name = MonitorMetricType.ResponseStatusCode; + metricType.description = CheckOn.ResponseStatusCode + " for this monitor"; + metricType.unit = "Status Code"; + + metricNameServiceNameMap[MonitorMetricType.ResponseStatusCode] = + metricType; + } + + if (metricRows.length > 0) { + await MetricService.insertJsonRows(metricRows); + } + + // index metrics + TelemetryUtil.indexMetricNameServiceNameMap({ + projectId: data.projectId, + metricNameServiceNameMap: metricNameServiceNameMap, + }).catch((err: Error) => { + logger.error(err); + }); + } } diff --git a/Common/Server/Utils/Monitor/MonitorResource.ts b/Common/Server/Utils/Monitor/MonitorResource.ts index cf843ccace..2ca89dc88c 100644 --- a/Common/Server/Utils/Monitor/MonitorResource.ts +++ b/Common/Server/Utils/Monitor/MonitorResource.ts @@ -24,6 +24,7 @@ import ProbeApiIngestResponse from "../../../Types/Probe/ProbeApiIngestResponse" import ProbeMonitorResponse from "../../../Types/Probe/ProbeMonitorResponse"; import Monitor from "../../../Models/DatabaseModels/Monitor"; import MonitorProbe from "../../../Models/DatabaseModels/MonitorProbe"; +import MonitorStatus from "../../../Models/DatabaseModels/MonitorStatus"; import MonitorStatusTimeline from "../../../Models/DatabaseModels/MonitorStatusTimeline"; import OneUptimeDate from "../../../Types/Date"; import LogMonitorResponse from "../../../Types/Monitor/LogMonitor/LogMonitorResponse"; @@ -87,17 +88,18 @@ export default class MonitorResourceUtil { return monitorStatusNameCache[cacheKey]; } - const monitorStatus = await MonitorStatusService.findOneBy({ - query: { - _id: statusId, - }, - select: { - name: true, - }, - props: { - isRoot: true, - }, - }); + const monitorStatus: MonitorStatus | null = + await MonitorStatusService.findOneBy({ + query: { + _id: statusId, + }, + select: { + name: true, + }, + props: { + isRoot: true, + }, + }); const statusName: string | null = monitorStatus?.name || null; monitorStatusNameCache[cacheKey] = statusName; diff --git a/Dashboard/src/Components/Monitor/SummaryView/EvaluationLogList.tsx b/Dashboard/src/Components/Monitor/SummaryView/EvaluationLogList.tsx index d2c2fd6d65..e7175d36a9 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/EvaluationLogList.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/EvaluationLogList.tsx @@ -116,8 +116,7 @@ const EvaluationLogList: FunctionComponent = ( const renderEventAction: () => ReactElement | null = () => { if ( event.relatedIncidentId && - (event.type === "incident-created" || - event.type === "incident-skipped") + (event.type === "incident-created" || event.type === "incident-skipped") ) { const incidentRoute: Route = RouteUtil.populateRouteParams( RouteMap[PageMap.INCIDENT_VIEW] as Route, @@ -140,8 +139,7 @@ const EvaluationLogList: FunctionComponent = ( if ( event.relatedAlertId && - (event.type === "alert-created" || - event.type === "alert-skipped") + (event.type === "alert-created" || event.type === "alert-skipped") ) { const alertRoute: Route = RouteUtil.populateRouteParams( RouteMap[PageMap.ALERT_VIEW] as Route, diff --git a/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx b/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx index 3de8fb79ea..3e43a048fa 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx @@ -118,11 +118,9 @@ const SummaryInfo: FunctionComponent = ( const probableMonitorEvaluationSummary: MonitorEvaluationSummary | undefined = props.evaluationSummary || - props.probeMonitorResponses?.find( - (response: ProbeMonitorResponse) => { - return Boolean(response.evaluationSummary); - }, - )?.evaluationSummary; + props.probeMonitorResponses?.find((response: ProbeMonitorResponse) => { + return Boolean(response.evaluationSummary); + })?.evaluationSummary; if ( MonitorTypeHelper.isProbableMonitor(props.monitorType) &&