Skip to content

Commit

Permalink
feat: add probe identification for identical probes
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
syamsudotdev committed Dec 15, 2024
1 parent ba0c88f commit 2f6d348
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 1 deletion.
133 changes: 133 additions & 0 deletions src/components/config/identical-probes.ts
Original file line number Diff line number Diff line change
@@ -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<Set<string>[]> {
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<Set<string>> {
const identicalProbeIds: Set<string> = 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
}
17 changes: 16 additions & 1 deletion src/components/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -115,7 +117,20 @@ export async function updateConfig(config: Config): Promise<void> {
log.info('Config changes. Updating config...')
}

setContext({ config: sanitizedConfig })
const identicalProbeIds = await identifyIdenticalProbes(sanitizedConfig)
const initialProbeCache: Map<
Set<string>,
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)
}
Expand Down
7 changes: 7 additions & 0 deletions src/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -34,13 +35,18 @@ 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
incidents: Incident[]
isTest: boolean
config?: Omit<ValidatedConfig, 'probes'>
flags: MonikaFlags
probeResultCache: {
iterationId: number
cache: Map<Set<ProbeID>, ProbeRequestResponse | undefined>
}
}

type NewContext = Partial<Context>
Expand All @@ -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
Expand Down

0 comments on commit 2f6d348

Please sign in to comment.