Skip to content

Commit 7b3d344

Browse files
Merge master into feature/dynamodb
2 parents cda19e0 + a5050f1 commit 7b3d344

File tree

8 files changed

+430
-112
lines changed

8 files changed

+430
-112
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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+
}

packages/core/src/shared/telemetry/spans.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
getTelemetryResult,
2424
} from '../errors'
2525
import { entries, NumericKeys } from '../utilities/tsUtils'
26+
import { PerformanceTracker } from '../performance/performance'
2627

2728
const AsyncLocalStorage: typeof AsyncLocalStorageClass =
2829
require('async_hooks').AsyncLocalStorage ??
@@ -97,6 +98,9 @@ export type SpanOptions = {
9798
/** True if this span should emit its telemetry events. Defaults to true if undefined. */
9899
emit?: boolean
99100

101+
/** True if this span should emit performance metrics acquired when running the function. Defaults to false if undefined */
102+
trackPerformance?: boolean
103+
100104
/**
101105
* Adds a function entry to the span stack.
102106
*
@@ -134,6 +138,7 @@ export type SpanOptions = {
134138
export class TelemetrySpan<T extends MetricBase = MetricBase> {
135139
#startTime?: Date
136140
#options: SpanOptions
141+
#performance?: PerformanceTracker
137142

138143
private readonly state: Partial<T> = {}
139144
private readonly definition = definitions[this.name] ?? {
@@ -157,6 +162,7 @@ export class TelemetrySpan<T extends MetricBase = MetricBase> {
157162
// do emit by default
158163
emit: options?.emit === undefined ? true : options.emit,
159164
functionId: options?.functionId,
165+
trackPerformance: PerformanceTracker.enabled(this.name, options?.trackPerformance),
160166
}
161167
}
162168

@@ -195,6 +201,10 @@ export class TelemetrySpan<T extends MetricBase = MetricBase> {
195201
*/
196202
public start(): this {
197203
this.#startTime = new globals.clock.Date()
204+
if (this.#options.trackPerformance) {
205+
;(this.#performance ??= new PerformanceTracker(this.name)).start()
206+
}
207+
198208
return this
199209
}
200210

@@ -206,6 +216,19 @@ export class TelemetrySpan<T extends MetricBase = MetricBase> {
206216
public stop(err?: unknown): void {
207217
const duration = this.startTime !== undefined ? globals.clock.Date.now() - this.startTime.getTime() : undefined
208218

219+
if (this.#options.trackPerformance) {
220+
// TODO add these to the global metrics, right now it just forces them in the telemetry and ignores the type
221+
// if someone enables this action
222+
const performanceMetrics = this.#performance?.stop()
223+
if (performanceMetrics) {
224+
this.record({
225+
cpuUsage: performanceMetrics.cpuUsage,
226+
heapTotal: performanceMetrics.heapTotal,
227+
functionName: this.#options.functionId?.name ?? this.name,
228+
} as any)
229+
}
230+
}
231+
209232
if (this.#options.emit) {
210233
this.emit({
211234
duration,
@@ -307,11 +330,7 @@ export class TelemetryTracer extends TelemetryBase {
307330
* All changes made to {@link attributes} (via {@link record}) during the execution are
308331
* reverted after the execution completes.
309332
*/
310-
public run<T, U extends MetricName>(
311-
name: U,
312-
fn: (span: Metric<MetricShapes[U]>) => T,
313-
options?: SpanOptions | undefined
314-
): T {
333+
public run<T, U extends MetricName>(name: U, fn: (span: Metric<MetricShapes[U]>) => T, options?: SpanOptions): T {
315334
const span = this.createSpan(name, options).start()
316335
const frame = this.switchContext(span)
317336

0 commit comments

Comments
 (0)