-// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
+// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
-import { type PerformanceEntry, PerformanceObserver, performance } from 'node:perf_hooks'
+import { performance, type PerformanceEntry, PerformanceObserver } from 'node:perf_hooks'
import type { URL } from 'node:url'
import { parentPort } from 'node:worker_threads'
import { secondsToMilliseconds } from 'date-fns'
+import { CircularBuffer } from 'mnemonist'
+import { is, mean, median } from 'rambda'
import { BaseError } from '../exception/index.js'
import {
ConfigurationSection,
type IncomingRequestCommand,
type LogConfiguration,
+ MapStringifyFormat,
MessageType,
type RequestCommand,
type Statistics,
+ type StatisticsData,
type StorageConfiguration,
type TimestampedData
} from '../types/index.js'
import {
- CircularArray,
+ buildPerformanceStatisticsMessage,
Configuration,
Constants,
- JSONStringifyWithMapSupport,
- average,
- buildPerformanceStatisticsMessage,
extractTimeSeriesValues,
formatDurationSeconds,
generateUUID,
- logPrefix,
+ JSONStringify,
logger,
+ logPrefix,
max,
- median,
min,
nthPercentile,
stdDeviation
objName: string | undefined,
uri: URL | undefined
): PerformanceStatistics | undefined {
- const logPfx = logPrefix(' Performance statistics')
if (objId == null) {
const errMsg = 'Cannot get performance statistics instance without specifying object id'
- logger.error(`${logPfx} ${errMsg}`)
+ logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`)
throw new BaseError(errMsg)
}
if (objName == null) {
const errMsg = 'Cannot get performance statistics instance without specifying object name'
- logger.error(`${logPfx} ${errMsg}`)
+ logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`)
throw new BaseError(errMsg)
}
if (uri == null) {
const errMsg = 'Cannot get performance statistics instance without specifying object uri'
- logger.error(`${logPfx} ${errMsg}`)
+ logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`)
throw new BaseError(errMsg)
}
if (!PerformanceStatistics.instances.has(objId)) {
return PerformanceStatistics.instances.get(objId)
}
+ public static deleteInstance (objId: string | undefined): boolean {
+ if (objId == null) {
+ const errMsg = 'Cannot delete performance statistics instance without specifying object id'
+ logger.error(`${PerformanceStatistics.logPrefix()} ${errMsg}`)
+ throw new BaseError(errMsg)
+ }
+ return PerformanceStatistics.instances.delete(objId)
+ }
+
public static beginMeasure (id: string): string {
const markId = `${id.charAt(0).toUpperCase()}${id.slice(1)}~${generateUUID()}`
performance.mark(markId)
try {
performance.measure(name, markId)
} catch (error) {
- if (error instanceof Error && error.message.includes('performance mark has not been set')) {
+ if (is(Error, error) && error.message.includes('performance mark has not been set')) {
/* Ignore */
} else {
throw error
)
if (performanceStorageConfiguration.enabled === true) {
logger.info(
- `${this.logPrefix()} storage enabled: type ${performanceStorageConfiguration.type}, uri: ${
- performanceStorageConfiguration.uri
- }`
+ `${this.logPrefix()} storage enabled: type ${
+ performanceStorageConfiguration.type
+ }, uri: ${performanceStorageConfiguration.uri}`
)
}
}
}
private initializePerformanceObserver (): void {
- this.performanceObserver = new PerformanceObserver((performanceObserverList) => {
+ this.performanceObserver = new PerformanceObserver(performanceObserverList => {
const lastPerformanceEntry = performanceObserverList.getEntries()[0]
// logger.debug(
// `${this.logPrefix()} '${lastPerformanceEntry.name}' performance entry: %j`,
private logStatistics (): void {
logger.info(this.logPrefix(), {
...this.statistics,
- statisticsData: JSONStringifyWithMapSupport(this.statistics.statisticsData)
+ statisticsData: JSON.parse(
+ JSONStringify(this.statistics.statisticsData, undefined, MapStringifyFormat.object)
+ ) as Map<string | RequestCommand | IncomingRequestCommand, StatisticsData>
})
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.statistics.statisticsData.get(entry.name)!.minTimeMeasurement = min(
entry.duration,
- this.statistics.statisticsData.get(entry.name)?.minTimeMeasurement ?? Infinity
+ this.statistics.statisticsData.get(entry.name)?.minTimeMeasurement ?? Number.POSITIVE_INFINITY
)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.statistics.statisticsData.get(entry.name)!.maxTimeMeasurement = max(
entry.duration,
- this.statistics.statisticsData.get(entry.name)?.maxTimeMeasurement ?? -Infinity
+ this.statistics.statisticsData.get(entry.name)?.maxTimeMeasurement ?? Number.NEGATIVE_INFINITY
)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.statistics.statisticsData.get(entry.name)!.totalTimeMeasurement =
(this.statistics.statisticsData.get(entry.name)?.totalTimeMeasurement ?? 0) + entry.duration
- this.statistics.statisticsData.get(entry.name)?.measurementTimeSeries instanceof CircularArray
- ? this.statistics.statisticsData
- .get(entry.name)
- ?.measurementTimeSeries?.push({ timestamp: entry.startTime, value: entry.duration })
- : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- (this.statistics.statisticsData.get(entry.name)!.measurementTimeSeries =
- new CircularArray<TimestampedData>(Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY, {
- timestamp: entry.startTime,
- value: entry.duration
- }))
+ if (
+ !(
+ this.statistics.statisticsData.get(entry.name)?.measurementTimeSeries instanceof
+ CircularBuffer
+ )
+ ) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ this.statistics.statisticsData.get(entry.name)!.measurementTimeSeries =
+ new CircularBuffer<TimestampedData>(
+ Array<TimestampedData>,
+ Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY
+ )
+ }
+ this.statistics.statisticsData.get(entry.name)?.measurementTimeSeries?.push({
+ timestamp: entry.startTime,
+ value: entry.duration
+ })
const timeMeasurementValues = extractTimeSeriesValues(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- this.statistics.statisticsData.get(entry.name)!.measurementTimeSeries!
+ this.statistics.statisticsData.get(entry.name)!
+ .measurementTimeSeries as CircularBuffer<TimestampedData>
)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- this.statistics.statisticsData.get(entry.name)!.avgTimeMeasurement =
- average(timeMeasurementValues)
+ this.statistics.statisticsData.get(entry.name)!.avgTimeMeasurement = mean(timeMeasurementValues)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.statistics.statisticsData.get(entry.name)!.medTimeMeasurement =
median(timeMeasurementValues)
}
}
+ private static readonly logPrefix = (): string => {
+ return logPrefix(' Performance statistics')
+ }
+
private readonly logPrefix = (): string => {
return logPrefix(` ${this.objName} | Performance statistics`)
}