|
| 1 | +/*! |
| 2 | + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 3 | + * SPDX-License-Identifier: Apache-2.0 |
| 4 | + */ |
| 5 | + |
| 6 | +import assert from 'assert' |
| 7 | +import { getLogger } from '../logger' |
| 8 | +import { isWeb } from '../extensionGlobals' |
| 9 | + |
| 10 | +interface PerformanceMetrics { |
| 11 | + cpuUsage: number |
| 12 | + heapTotal: number |
| 13 | + duration: number |
| 14 | +} |
| 15 | + |
| 16 | +interface TestOptions { |
| 17 | + darwin?: Partial<PerformanceMetrics> |
| 18 | + win32?: Partial<PerformanceMetrics> |
| 19 | + linux?: Partial<PerformanceMetrics> |
| 20 | +} |
| 21 | + |
| 22 | +export interface PerformanceSpan<T> { |
| 23 | + value: T |
| 24 | + performance: PerformanceMetrics |
| 25 | +} |
| 26 | + |
| 27 | +export class PerformanceTracker { |
| 28 | + #startPerformance: |
| 29 | + | { |
| 30 | + cpuUsage: NodeJS.CpuUsage |
| 31 | + memory: number |
| 32 | + duration: [number, number] |
| 33 | + } |
| 34 | + | undefined |
| 35 | + |
| 36 | + constructor(private readonly name: string) {} |
| 37 | + |
| 38 | + static enabled(name: string, trackPerformance?: boolean): boolean { |
| 39 | + return name === 'function_call' && (trackPerformance ?? false) && !isWeb() |
| 40 | + } |
| 41 | + |
| 42 | + start() { |
| 43 | + this.#startPerformance = { |
| 44 | + cpuUsage: process.cpuUsage(), |
| 45 | + memory: process.memoryUsage().heapTotal, |
| 46 | + duration: process.hrtime(), |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + stop(): PerformanceMetrics | undefined { |
| 51 | + if (this.#startPerformance) { |
| 52 | + const endCpuUsage = process.cpuUsage(this.#startPerformance?.cpuUsage) |
| 53 | + const userCpuUsage = endCpuUsage.user / 1000000 |
| 54 | + const systemCpuUsage = endCpuUsage.system / 1000000 |
| 55 | + |
| 56 | + const elapsedTime = process.hrtime(this.#startPerformance.duration) |
| 57 | + const duration = elapsedTime[0] + elapsedTime[1] / 1e9 // convert microseconds to seconds |
| 58 | + const usage = ((userCpuUsage + systemCpuUsage) / duration) * 100 // convert to percentage |
| 59 | + |
| 60 | + const endMemoryUsage = process.memoryUsage().heapTotal - this.#startPerformance?.memory |
| 61 | + const endMemoryUsageInMB = endMemoryUsage / (1024 * 1024) // converting bytes to MB |
| 62 | + |
| 63 | + return { |
| 64 | + cpuUsage: usage, |
| 65 | + heapTotal: endMemoryUsageInMB, |
| 66 | + duration, |
| 67 | + } |
| 68 | + } else { |
| 69 | + getLogger().debug(`PerformanceTracker: start performance not defined for ${this.name}`) |
| 70 | + } |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +export function performanceTest(options: TestOptions, name: string, fn: () => Promise<void>): Mocha.Test |
| 75 | +export function performanceTest(options: TestOptions, name: string, fn: () => void): Mocha.Test |
| 76 | +export function performanceTest(options: TestOptions, name: string, fn: () => void | Promise<void>) { |
| 77 | + const testOption = options[process.platform as 'linux' | 'darwin' | 'win32'] |
| 78 | + |
| 79 | + const performanceTracker = new PerformanceTracker(name) |
| 80 | + |
| 81 | + return it(name, async () => { |
| 82 | + performanceTracker.start() |
| 83 | + await fn() |
| 84 | + const metrics = performanceTracker.stop() |
| 85 | + if (!metrics) { |
| 86 | + assert.fail('Performance metrics not found') |
| 87 | + } |
| 88 | + assertPerformanceMetrics(metrics, name, testOption) |
| 89 | + }) |
| 90 | +} |
| 91 | + |
| 92 | +function assertPerformanceMetrics( |
| 93 | + performanceMetrics: PerformanceMetrics, |
| 94 | + name: string, |
| 95 | + testOption?: Partial<PerformanceMetrics> |
| 96 | +) { |
| 97 | + const expectedCPUUsage = testOption?.cpuUsage ?? 50 |
| 98 | + const foundCPUUsage = performanceMetrics.cpuUsage |
| 99 | + |
| 100 | + assert( |
| 101 | + foundCPUUsage < expectedCPUUsage, |
| 102 | + `Expected total CPU usage for ${name} to be less than ${expectedCPUUsage}. Actual CPU usage was ${foundCPUUsage}` |
| 103 | + ) |
| 104 | + |
| 105 | + const expectedMemoryUsage = testOption?.heapTotal ?? 400 |
| 106 | + const foundMemoryUsage = performanceMetrics.heapTotal |
| 107 | + assert( |
| 108 | + foundMemoryUsage < expectedMemoryUsage, |
| 109 | + `Expected total memory usage for ${name} to be less than ${expectedMemoryUsage}. Actual memory usage was ${foundMemoryUsage}` |
| 110 | + ) |
| 111 | + |
| 112 | + const expectedDuration = testOption?.duration ?? 5 |
| 113 | + const foundDuration = performanceMetrics.duration |
| 114 | + assert( |
| 115 | + foundDuration < expectedDuration, |
| 116 | + `Expected total duration for ${name} to be less than ${expectedDuration}. Actual duration was ${foundDuration}` |
| 117 | + ) |
| 118 | +} |
0 commit comments