1
1
import assert from "assert" ;
2
2
import { createHistogram , performance , RecordableHistogram } from "perf_hooks" ;
3
+ import { Configuration , createMetricsLogger , MetricsLogger , StorageResolution , Unit } from "aws-embedded-metrics" ;
4
+
5
+ export interface TestMetadata {
6
+ /**
7
+ * The test name will be used as a metric dimension for reporting.
8
+ */
9
+ name : string ;
10
+ }
3
11
4
12
export interface Test {
13
+ metadata ( ) : TestMetadata ;
14
+
5
15
setup ( ) : Promise < void > ;
6
16
7
17
teardown ( ) : Promise < void > ;
@@ -19,6 +29,12 @@ export interface Test {
19
29
}
20
30
21
31
export abstract class AbstractBaseTest implements Test {
32
+ metadata ( ) {
33
+ return {
34
+ name : this . constructor . name ,
35
+ } ;
36
+ }
37
+
22
38
async setup ( ) : Promise < void > {
23
39
}
24
40
@@ -36,16 +52,25 @@ export abstract class AbstractBaseTest implements Test {
36
52
}
37
53
}
38
54
55
+ export const METRIC_NAMESPACE = "AccountingDB" ;
56
+ Configuration . namespace = METRIC_NAMESPACE ;
57
+
58
+ export type MetricNames = "Success" // overall success (1) / failure (0) of a single batch
59
+ | "Latency" // elapsed time from batch arrival time to commit
60
+ | "ServiceTime" // elapsed time from batch processing start time to commit
61
+ | "BatchSize" ; // number of successfully processed items in a single batch; not emitted for failed transactions
62
+
63
+ const METRICS_RESOLUTION = StorageResolution . High ;
64
+
39
65
export class LoadTestDriver {
40
66
private readonly concurrency : number ;
41
67
private readonly targetRps : number ;
42
68
private readonly workerCycleTimeMs : number ;
43
69
private readonly arrivalIntervalTimeMs : number ;
44
70
private readonly test : Test ;
71
+ private readonly name : string ;
45
72
private readonly overallDurationMs : number ;
46
73
private readonly warmupDurationMs : number ;
47
- private readonly requestLatencyMicros : RecordableHistogram ;
48
- private readonly serviceTimeMicros : RecordableHistogram ;
49
74
private readonly timeoutValueMs : number ;
50
75
private completedIterationsCount : number = 0 ;
51
76
private scheduledIterationsCount : number = 0 ;
@@ -59,6 +84,10 @@ export class LoadTestDriver {
59
84
private workerRunTime : number = 0 ;
60
85
private workerBackoffTime : number = 0 ;
61
86
private workerBehindScheduleTime : number = 0 ;
87
+ // We track metrics internally, and optionally post them to CloudWatch. The latter is great for distributed use.
88
+ private readonly requestLatencyMicros : RecordableHistogram ;
89
+ private readonly serviceTimeMicros : RecordableHistogram ;
90
+ private readonly metrics : MetricsLogger ;
62
91
63
92
constructor (
64
93
test : Test ,
@@ -85,10 +114,12 @@ export class LoadTestDriver {
85
114
this . test = test ;
86
115
this . requestLatencyMicros = createHistogram ( ) ;
87
116
this . serviceTimeMicros = createHistogram ( ) ;
117
+ this . metrics = createMetricsLogger ( ) ;
118
+ this . name = test . metadata ( ) . name ;
88
119
}
89
120
90
121
async run ( ) : Promise < any > {
91
- if ( this . targetRps = = 0 ) {
122
+ if ( this . targetRps < = 0 ) {
92
123
return ;
93
124
}
94
125
@@ -105,11 +136,16 @@ export class LoadTestDriver {
105
136
106
137
while ( nextRequestTime < endTime ) {
107
138
const cutoffTime = performance . now ( ) - this . timeoutValueMs ;
139
+ // Prune expired-in-queue requests from the work queue and record timeouts:
108
140
while ( this . workQueue . length > 0 && this . workQueue [ 0 ] < cutoffTime ) {
109
141
assert ( this . workQueue . shift ( ) !== undefined ) ;
142
+ this . metrics . putDimensions ( { Name : this . name } ) ;
143
+ this . metrics . putMetric ( $m ( "Latency" ) , this . timeoutValueMs , Unit . Milliseconds , METRICS_RESOLUTION ) ;
144
+ this . metrics . putMetric ( $m ( "Success" ) , 0 , Unit . None , METRICS_RESOLUTION ) ;
110
145
this . missedIterations += 1 ;
111
146
this . recordDuration ( this . requestLatencyMicros , this . timeoutValueMs * 1000 ) ;
112
147
}
148
+ await this . metrics . flush ( ) ;
113
149
114
150
while ( this . workQueue . length < this . concurrency * 2 && nextRequestTime < endTime ) {
115
151
this . workQueue . push ( nextRequestTime ) ;
@@ -129,14 +165,18 @@ export class LoadTestDriver {
129
165
// Skip over any scheduled iterations that have already timed out in-queue
130
166
const cutoffTime = workerLoopStart - this . timeoutValueMs ;
131
167
let arrivalTime = this . workQueue . shift ( ) ;
168
+ this . metrics . putDimensions ( { Name : this . name } ) ;
132
169
for ( ; arrivalTime !== undefined && arrivalTime < cutoffTime ; arrivalTime = this . workQueue . shift ( ) ) {
133
170
// Only record timeouts post-warmup
134
171
if ( arrivalTime > measurementStartTime ) {
135
172
this . missedIterations += 1 ;
136
173
this . recordDuration ( this . requestLatencyMicros , Math . round ( this . timeoutValueMs * 1000 ) ) ;
174
+ this . metrics . putMetric ( $m ( "Latency" ) , this . timeoutValueMs , Unit . Milliseconds , METRICS_RESOLUTION ) ;
175
+ this . metrics . putMetric ( $m ( "Success" ) , 0 , Unit . None , METRICS_RESOLUTION ) ;
137
176
arrivalTime = this . workQueue . shift ( ) ;
138
177
}
139
178
}
179
+ await this . metrics . flush ( ) ;
140
180
141
181
// No more work for this worker to do
142
182
if ( arrivalTime === undefined ) {
@@ -181,6 +221,13 @@ export class LoadTestDriver {
181
221
this . completedIterationsCount += 1 ;
182
222
this . requestCount += this . requestsPerIteration ;
183
223
this . workerRunTime += serviceTimeMillis ;
224
+
225
+ this . metrics . putDimensions ( { Name : this . name } ) ;
226
+ this . metrics . putMetric ( $m ( "Latency" ) , iterationDurationMillis , Unit . Milliseconds , METRICS_RESOLUTION ) ;
227
+ this . metrics . putMetric ( $m ( "ServiceTime" ) , serviceTimeMillis , Unit . Milliseconds , METRICS_RESOLUTION ) ;
228
+ this . metrics . putMetric ( $m ( "BatchSize" ) , this . requestsPerIteration , Unit . Count , METRICS_RESOLUTION ) ;
229
+ this . metrics . putMetric ( $m ( "Success" ) , 1 , Unit . None , METRICS_RESOLUTION ) ;
230
+ await this . metrics . flush ( ) ;
184
231
}
185
232
} while ( true ) ;
186
233
} ;
@@ -264,3 +311,7 @@ export class LoadTestDriver {
264
311
export async function sleep ( ms : number ) {
265
312
await new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
266
313
}
314
+
315
+ function $m ( metric : MetricNames ) {
316
+ return metric ;
317
+ }
0 commit comments