11import assert from "assert" ;
22import { 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+ }
311
412export interface Test {
13+ metadata ( ) : TestMetadata ;
14+
515 setup ( ) : Promise < void > ;
616
717 teardown ( ) : Promise < void > ;
@@ -19,6 +29,12 @@ export interface Test {
1929}
2030
2131export abstract class AbstractBaseTest implements Test {
32+ metadata ( ) {
33+ return {
34+ name : this . constructor . name ,
35+ } ;
36+ }
37+
2238 async setup ( ) : Promise < void > {
2339 }
2440
@@ -36,16 +52,25 @@ export abstract class AbstractBaseTest implements Test {
3652 }
3753}
3854
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+
3965export class LoadTestDriver {
4066 private readonly concurrency : number ;
4167 private readonly targetRps : number ;
4268 private readonly workerCycleTimeMs : number ;
4369 private readonly arrivalIntervalTimeMs : number ;
4470 private readonly test : Test ;
71+ private readonly name : string ;
4572 private readonly overallDurationMs : number ;
4673 private readonly warmupDurationMs : number ;
47- private readonly requestLatencyMicros : RecordableHistogram ;
48- private readonly serviceTimeMicros : RecordableHistogram ;
4974 private readonly timeoutValueMs : number ;
5075 private completedIterationsCount : number = 0 ;
5176 private scheduledIterationsCount : number = 0 ;
@@ -59,6 +84,10 @@ export class LoadTestDriver {
5984 private workerRunTime : number = 0 ;
6085 private workerBackoffTime : number = 0 ;
6186 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 ;
6291
6392 constructor (
6493 test : Test ,
@@ -85,10 +114,12 @@ export class LoadTestDriver {
85114 this . test = test ;
86115 this . requestLatencyMicros = createHistogram ( ) ;
87116 this . serviceTimeMicros = createHistogram ( ) ;
117+ this . metrics = createMetricsLogger ( ) ;
118+ this . name = test . metadata ( ) . name ;
88119 }
89120
90121 async run ( ) : Promise < any > {
91- if ( this . targetRps = = 0 ) {
122+ if ( this . targetRps < = 0 ) {
92123 return ;
93124 }
94125
@@ -105,11 +136,16 @@ export class LoadTestDriver {
105136
106137 while ( nextRequestTime < endTime ) {
107138 const cutoffTime = performance . now ( ) - this . timeoutValueMs ;
139+ // Prune expired-in-queue requests from the work queue and record timeouts:
108140 while ( this . workQueue . length > 0 && this . workQueue [ 0 ] < cutoffTime ) {
109141 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 ) ;
110145 this . missedIterations += 1 ;
111146 this . recordDuration ( this . requestLatencyMicros , this . timeoutValueMs * 1000 ) ;
112147 }
148+ await this . metrics . flush ( ) ;
113149
114150 while ( this . workQueue . length < this . concurrency * 2 && nextRequestTime < endTime ) {
115151 this . workQueue . push ( nextRequestTime ) ;
@@ -129,14 +165,18 @@ export class LoadTestDriver {
129165 // Skip over any scheduled iterations that have already timed out in-queue
130166 const cutoffTime = workerLoopStart - this . timeoutValueMs ;
131167 let arrivalTime = this . workQueue . shift ( ) ;
168+ this . metrics . putDimensions ( { Name : this . name } ) ;
132169 for ( ; arrivalTime !== undefined && arrivalTime < cutoffTime ; arrivalTime = this . workQueue . shift ( ) ) {
133170 // Only record timeouts post-warmup
134171 if ( arrivalTime > measurementStartTime ) {
135172 this . missedIterations += 1 ;
136173 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 ) ;
137176 arrivalTime = this . workQueue . shift ( ) ;
138177 }
139178 }
179+ await this . metrics . flush ( ) ;
140180
141181 // No more work for this worker to do
142182 if ( arrivalTime === undefined ) {
@@ -181,6 +221,13 @@ export class LoadTestDriver {
181221 this . completedIterationsCount += 1 ;
182222 this . requestCount += this . requestsPerIteration ;
183223 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 ( ) ;
184231 }
185232 } while ( true ) ;
186233 } ;
@@ -264,3 +311,7 @@ export class LoadTestDriver {
264311export async function sleep ( ms : number ) {
265312 await new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
266313}
314+
315+ function $m ( metric : MetricNames ) {
316+ return metric ;
317+ }
0 commit comments