From 2f6d3484df9a0367eed7f1423703b8aef5209d56 Mon Sep 17 00:00:00 2001 From: Mochamad Noor Syamsu Date: Mon, 23 Sep 2024 00:00:09 +0700 Subject: [PATCH] feat: add probe identification for identical probes - Added 'src/components/config/identical-probes.ts' to identify identical probes. - Updated 'src/components/config/index.ts' to utilize identical probe identification in config updates. - Enhanced 'src/context/index.ts' with probe result caching capabilities. --- src/components/config/identical-probes.ts | 133 ++++++++++++++++++++++ src/components/config/index.ts | 17 ++- src/context/index.ts | 7 ++ 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/components/config/identical-probes.ts diff --git a/src/components/config/identical-probes.ts b/src/components/config/identical-probes.ts new file mode 100644 index 000000000..dd66d559e --- /dev/null +++ b/src/components/config/identical-probes.ts @@ -0,0 +1,133 @@ +/********************************************************************************** + * MIT License * + * * + * Copyright (c) 2021 Hyperjump Technology * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in all * + * copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * + * SOFTWARE. * + **********************************************************************************/ + +import lodash from 'lodash' +import type { Probe } from '../../interfaces/probe' +import type { ValidatedConfig } from '../../interfaces/config' + +// Function to identify identical probes based on specific fields +export async function identifyIdenticalProbes( + config: ValidatedConfig +): Promise[]> { + const { probes } = config + // Define fields that are used to determine if probes are identical + const httpIdentifiers: (keyof Probe)[] = ['interval', 'requests'] + const socketIdentifiers: (keyof Probe)[] = ['interval', 'socket'] + const redisIdentiers: (keyof Probe)[] = ['interval', 'redis'] + const mongoIdentiers: (keyof Probe)[] = ['interval', 'mongo'] + const mariadbIdentiers: (keyof Probe)[] = ['interval', 'mariadb'] + const mysqlIdentiers: (keyof Probe)[] = ['interval', 'mysql'] + const postgresIdentiers: (keyof Probe)[] = ['interval', 'postgres'] + const pingIdentiers: (keyof Probe)[] = ['interval', 'ping'] + + return Promise.all([ + internalIdentification(httpIdentifiers, probes), + internalIdentification(socketIdentifiers, probes), + internalIdentification(redisIdentiers, probes), + internalIdentification(mongoIdentiers, probes), + internalIdentification(mariadbIdentiers, probes), + internalIdentification(mysqlIdentiers, probes), + internalIdentification(postgresIdentiers, probes), + internalIdentification(pingIdentiers, probes), + ]) +} + +async function internalIdentification( + fieldIdentifiers: (keyof Probe)[], + probes: Probe[] +): Promise> { + const identicalProbeIds: Set = new Set() + + // Iterate through each probe + // eslint-disable-next-line guard-for-in + for (const index in probes) { + const outerProbe = probes[index] + // Skip probes without requests + if (!outerProbe.requests?.length) continue + + // Create an identifier for the outer probe + const outerIdentifier = fieldIdentifiers + // Get values from each field + .map((i) => outerProbe[i]) + // Set 'alerts' fields to undefined for comparison + .map((configValue) => { + if ( + typeof configValue === 'object' && + Object.keys(configValue).includes('alerts') + ) { + return { ...configValue, alerts: undefined } + } + + if (Array.isArray(configValue)) { + return configValue.map((c) => ({ ...c, alerts: undefined })) + } + + return configValue + }) + + // Get all probes except the current one + const innerProbes = probes.filter((p) => p.id !== outerProbe.id) + + // Compare the outer probe with each inner probe + // eslint-disable-next-line guard-for-in + for (const jIndex in innerProbes) { + const innerProbe = innerProbes[jIndex] + // Skip probes without requests + if (!innerProbe.requests?.length) continue + + // Create an identifier for the inner probe + const innerIdentifier = fieldIdentifiers + // Get values from each field + .map((i) => innerProbe[i]) + // Set 'alerts' fields to undefined for comparison + .map((configValue) => { + if ( + typeof configValue === 'object' && + Object.keys(configValue).includes('alerts') + ) { + return { ...configValue, alerts: undefined } + } + + if (Array.isArray(configValue)) { + return configValue.map((c) => ({ ...c, alerts: undefined })) + } + + return configValue + }) + + // Compare outer and inner identifiers + if (lodash.isEqual(outerIdentifier, innerIdentifier)) { + // Add probe IDs to the set of identical probes + if (!identicalProbeIds.has(outerProbe.id)) { + identicalProbeIds.add(outerProbe.id) + } + + identicalProbeIds.add(innerProbe.id) + } + } + } + + // Return the set of identical probe IDs + return identicalProbeIds +} diff --git a/src/components/config/index.ts b/src/components/config/index.ts index 2d935037f..94393e0f2 100644 --- a/src/components/config/index.ts +++ b/src/components/config/index.ts @@ -35,6 +35,8 @@ import { getRawConfig } from './get' import { getProbes, setProbes } from './probe' import { sanitizeConfig } from './sanitize' import { validateConfig } from './validate' +import { identifyIdenticalProbes } from './identical-probes' +import { ProbeRequestResponse } from 'src/interfaces/request' export async function initConfig() { const { flags } = getContext() @@ -115,7 +117,20 @@ export async function updateConfig(config: Config): Promise { log.info('Config changes. Updating config...') } - setContext({ config: sanitizedConfig }) + const identicalProbeIds = await identifyIdenticalProbes(sanitizedConfig) + const initialProbeCache: Map< + Set, + ProbeRequestResponse | undefined + > = new Map() + + for (const id of identicalProbeIds) { + initialProbeCache.set(id, undefined) + } + + setContext({ + config: sanitizedConfig, + probeResultCache: { iterationId: 0, cache: initialProbeCache }, + }) setProbes(sanitizedConfig.probes) getEventEmitter().emit(events.config.updated) } diff --git a/src/context/index.ts b/src/context/index.ts index 37ea6cfa4..6e978230e 100644 --- a/src/context/index.ts +++ b/src/context/index.ts @@ -26,6 +26,7 @@ import type { ValidatedConfig } from '../interfaces/config' import type { ProbeAlert } from '../interfaces/probe' import { type MonikaFlags, monikaFlagsDefaultValue } from '../flag' +import { ProbeRequestResponse } from 'src/interfaces/request' export type Incident = { probeID: string @@ -34,6 +35,7 @@ export type Incident = { createdAt: Date } +type ProbeID = string type Context = { // userAgent example: @hyperjumptech/monika/1.2.3 linux-x64 node-14.17.0 userAgent: string @@ -41,6 +43,10 @@ type Context = { isTest: boolean config?: Omit flags: MonikaFlags + probeResultCache: { + iterationId: number + cache: Map, ProbeRequestResponse | undefined> + } } type NewContext = Partial @@ -50,6 +56,7 @@ const initialContext: Context = { incidents: [], isTest: process.env.NODE_ENV === 'test', flags: monikaFlagsDefaultValue, + probeResultCache: { iterationId: 0, cache: new Map() }, } let context: Context = initialContext