Skip to content

Commit c9265b0

Browse files
authored
feat(metrics): rename method purgeStoredMetrics to publishStoredMetrics (#377)
* feat(metrics): rename method purgeStoredMetrics to publishStoredMetrics * add e2e tests for standard function
1 parent 6107725 commit c9265b0

File tree

9 files changed

+134
-73
lines changed

9 files changed

+134
-73
lines changed

Diff for: docs/core/metrics.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ export class MyFunction {
341341

342342
#### Manually
343343

344-
You can manually flush the metrics with `purgeStoredMetrics` as follows:
344+
You can manually flush the metrics with `publishStoredMetrics` as follows:
345345

346346
!!! warning
347347
Metrics, dimensions and namespace validation still applies.
@@ -354,7 +354,7 @@ const metrics = new Metrics();
354354
const lambdaHandler: Handler = async () => {
355355
metrics.addMetric('test-metric', MetricUnits.Count, 10);
356356
const metricsObject = metrics.serializeMetrics();
357-
metrics.purgeStoredMetrics();
357+
metrics.publishStoredMetrics();
358358
console.log(JSON.stringify(metricsObject));
359359
};
360360
```

Diff for: packages/metrics/examples/decorator/manual-flushing.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const metrics = new Metrics();
1515
const lambdaHandler: Handler = async () => {
1616

1717
metrics.addMetric('test-metric', MetricUnits.Count, 10);
18-
metrics.purgeStoredMetrics();
18+
metrics.publishStoredMetrics();
1919
//Metrics will be published and cleared
2020

2121
return {

Diff for: packages/metrics/examples/manual-flushing.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const metrics = new Metrics();
1414

1515
const lambdaHandler = async (): Promise<void> => {
1616
metrics.addMetric('test-metric', MetricUnits.Count, 10);
17-
metrics.purgeStoredMetrics();
17+
metrics.publishStoredMetrics();
1818
};
1919

2020
const handlerWithMiddleware = middy(lambdaHandler)

Diff for: packages/metrics/src/Metrics.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const DEFAULT_NAMESPACE = 'default_namespace';
7474
* export const handler = async (_event: any, _context: any) => {
7575
* metrics.captureColdStart();
7676
* metrics.addMetric('test-metric', MetricUnits.Count, 10);
77-
* metrics.purgeStoredMetrics();
77+
* metrics.publishStoredMetrics();
7878
* };
7979
* ```
8080
*/
@@ -146,7 +146,7 @@ class Metrics implements MetricsInterface {
146146
*/
147147
public addMetric(name: string, unit: MetricUnit, value: number): void {
148148
this.storeMetric(name, unit, value);
149-
if (this.isSingleMetric) this.purgeStoredMetrics();
149+
if (this.isSingleMetric) this.publishStoredMetrics();
150150
}
151151

152152
/**
@@ -245,14 +245,15 @@ class Metrics implements MetricsInterface {
245245

246246
return result;
247247
} finally {
248-
this.purgeStoredMetrics();
248+
this.publishStoredMetrics();
249249
}
250250
};
251251
};
252252
}
253253

254254
/**
255255
* Synchronous function to actually publish your metrics. (Not needed if using logMetrics decorator).
256+
* It will create a new EMF blob and log it to standard output to be then ingested by Cloudwatch logs and processed automatically for metrics creation.
256257
*
257258
* @example
258259
*
@@ -263,11 +264,11 @@ class Metrics implements MetricsInterface {
263264
*
264265
* export const handler = async (_event: any, _context: any) => {
265266
* metrics.addMetric('test-metric', MetricUnits.Count, 10);
266-
* metrics.purgeStoredMetrics();
267+
* metrics.publishStoredMetrics();
267268
* };
268269
* ```
269270
*/
270-
public purgeStoredMetrics(): void {
271+
public publishStoredMetrics(): void {
271272
const target = this.serializeMetrics();
272273
console.log(JSON.stringify(target));
273274
this.storedMetrics = {};
@@ -286,7 +287,7 @@ class Metrics implements MetricsInterface {
286287
*
287288
* export const handler = async (event: any, context: Context) => {
288289
* metrics.raiseOnEmptyMetrics();
289-
* metrics.purgeStoredMetrics(); // will throw since no metrics added.
290+
* metrics.publishStoredMetrics(); // will throw since no metrics added.
290291
* }
291292
* ```
292293
*/
@@ -357,7 +358,7 @@ class Metrics implements MetricsInterface {
357358
/**
358359
* CloudWatch EMF uses the same dimensions across all your metrics. Use singleMetric if you have a metric that should have different dimensions.
359360
*
360-
* You don't need to call purgeStoredMetrics() after calling addMetric for a singleMetrics, they will be flushed directly.
361+
* You don't need to call publishStoredMetrics() after calling addMetric for a singleMetrics, they will be flushed directly.
361362
*
362363
* @example
363364
*
@@ -428,7 +429,7 @@ class Metrics implements MetricsInterface {
428429

429430
private storeMetric(name: string, unit: MetricUnit, value: number): void {
430431
if (Object.keys(this.storedMetrics).length >= MAX_METRICS_SIZE) {
431-
this.purgeStoredMetrics();
432+
this.publishStoredMetrics();
432433
}
433434
this.storedMetrics[name] = {
434435
unit,

Diff for: packages/metrics/src/MetricsInterface.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface MetricsInterface {
1111
clearMetrics(): void
1212
clearDefaultDimensions(): void
1313
logMetrics(options?: MetricsOptions): HandlerMethodDecorator
14-
purgeStoredMetrics(): void
14+
publishStoredMetrics(): void
1515
serializeMetrics(): EmfOutput
1616
setDefaultDimensions(dimensions: Dimensions | undefined): void
1717
singleMetric(): Metrics

Diff for: packages/metrics/src/middleware/middy.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const logMetrics = (target: Metrics | Metrics[], options: ExtraOptions = {}): mi
2424

2525
const logMetricsAfterOrError = async (): Promise<void> => {
2626
metricsInstances.forEach((metrics: Metrics) => {
27-
metrics.purgeStoredMetrics();
27+
metrics.publishStoredMetrics();
2828
});
2929
};
3030

Diff for: packages/metrics/tests/e2e/standardFunctions.test.MyFunction.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@ const namespace = process.env.EXPECTED_NAMESPACE ?? 'CDKExample';
55
const serviceName = process.env.EXPECTED_SERVICE_NAME ?? 'MyFunctionWithStandardHandler';
66
const metricName = process.env.EXPECTED_METRIC_NAME ?? 'MyMetric';
77
const metricUnit = (process.env.EXPECTED_METRIC_UNIT as MetricUnits) ?? MetricUnits.Count;
8-
const metricValue = process.env.EXPECTED_METRIC_VALUE ?? 1;
8+
const metricValue = process.env.EXPECTED_METRIC_VALUE ?? '1';
99
const defaultDimensions = process.env.EXPECTED_DEFAULT_DIMENSIONS ?? '{"MyDimension":"MyValue"}';
1010
const extraDimension = process.env.EXPECTED_EXTRA_DIMENSION ?? '{"MyExtraDimension":"MyExtraValue"}';
1111
const singleMetricDimension = process.env.EXPECTED_SINGLE_METRIC_DIMENSION ?? '{"MySingleMetricDim":"MySingleValue"}';
1212
const singleMetricName = process.env.EXPECTED_SINGLE_METRIC_NAME ?? 'MySingleMetric';
1313
const singleMetricUnit = (process.env.EXPECTED_SINGLE_METRIC_UNIT as MetricUnits) ?? MetricUnits.Percent;
14-
const singleMetricValue = process.env.EXPECTED_SINGLE_METRIC_VALUE ?? 2;
14+
const singleMetricValue = process.env.EXPECTED_SINGLE_METRIC_VALUE ?? '2';
1515

1616
const metrics = new Metrics({ namespace: namespace, service: serviceName });
1717

1818
export const handler = async (_event: unknown, _context: Context): Promise<void> => {
1919
metrics.captureColdStartMetric();
2020
metrics.raiseOnEmptyMetrics();
2121
metrics.setDefaultDimensions(JSON.parse(defaultDimensions));
22-
metrics.addMetric(metricName, metricUnit, metricValue as number);
22+
metrics.addMetric(metricName, metricUnit, parseInt(metricValue));
2323
metrics.addDimension(
2424
Object.entries(JSON.parse(extraDimension))[0][0],
2525
Object.entries(JSON.parse(extraDimension))[0][1] as string,
@@ -30,8 +30,8 @@ export const handler = async (_event: unknown, _context: Context): Promise<void>
3030
Object.entries(JSON.parse(singleMetricDimension))[0][0],
3131
Object.entries(JSON.parse(singleMetricDimension))[0][1] as string,
3232
);
33-
metricWithItsOwnDimensions.addMetric(singleMetricName, singleMetricUnit, singleMetricValue as number);
33+
metricWithItsOwnDimensions.addMetric(singleMetricName, singleMetricUnit, parseInt(singleMetricValue));
3434

35-
metrics.purgeStoredMetrics();
35+
metrics.publishStoredMetrics();
3636
metrics.raiseOnEmptyMetrics();
3737
};

Diff for: packages/metrics/tests/e2e/standardFunctions.test.ts

+112-52
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
// SPDX-License-Identifier: MIT-0
33

44
/**
5-
* Test metrics decorator
5+
* Test metrics standard functions
66
*
7-
* @group e2e/metrics/decorator
7+
* @group e2e/metrics/standardFunctions
88
*/
99

1010
import { randomUUID } from 'crypto';
@@ -19,43 +19,44 @@ const cloudwatchClient = new AWS.CloudWatch();
1919
const lambdaClient = new AWS.Lambda();
2020

2121
const integTestApp = new App();
22-
const stack = new Stack(integTestApp, 'ExampleIntegTest');
22+
const stack = new Stack(integTestApp, 'MetricsE2EStandardFunctionsStack');
2323

24-
describe('coldstart', () => {
25-
it('can be deploy succcessfully', async () => {
26-
// GIVEN
27-
const startTime = new Date();
28-
const expectedNamespace = randomUUID(); // to easily find metrics back at assert phase
29-
const expectedServiceName = 'MyFunctionWithStandardHandler';
30-
const expectedMetricName = 'MyMetric';
31-
const expectedMetricUnit = MetricUnits.Count;
32-
const expectedMetricValue = '1';
33-
const expectedDefaultDimensions = { MyDimension: 'MyValue' };
34-
const expectedExtraDimension = { MyExtraDimension: 'MyExtraValue' };
35-
const expectedSingleMetricDimension = { MySingleMetricDim: 'MySingleValue' };
36-
const expectedSingleMetricName = 'MySingleMetric';
37-
const expectedSingleMetricUnit = MetricUnits.Percent;
38-
const expectedSingleMetricValue = '2';
39-
const functionName = 'MyFunctionWithStandardHandler';
40-
new lambda.NodejsFunction(stack, 'MyFunction', {
41-
functionName: functionName,
42-
environment: {
43-
EXPECTED_NAMESPACE: expectedNamespace,
44-
EXPECTED_SERVICE_NAME: expectedServiceName,
45-
EXPECTED_METRIC_NAME: expectedMetricName,
46-
EXPECTED_METRIC_UNIT: expectedMetricUnit,
47-
EXPECTED_METRIC_VALUE: expectedMetricValue,
48-
EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions),
49-
EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension),
50-
EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension),
51-
EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName,
52-
EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit,
53-
EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue,
54-
},
55-
});
24+
// GIVEN
25+
const startTime = new Date();
26+
const expectedNamespace = randomUUID(); // to easily find metrics back at assert phase
27+
const expectedServiceName = 'MyFunctionWithStandardHandler';
28+
const expectedMetricName = 'MyMetric';
29+
const expectedMetricUnit = MetricUnits.Count;
30+
const expectedMetricValue = '1';
31+
const expectedDefaultDimensions = { MyDimension: 'MyValue' };
32+
const expectedExtraDimension = { MyExtraDimension: 'MyExtraValue' };
33+
const expectedSingleMetricDimension = { MySingleMetricDim: 'MySingleValue' };
34+
const expectedSingleMetricName = 'MySingleMetric';
35+
const expectedSingleMetricUnit = MetricUnits.Percent;
36+
const expectedSingleMetricValue = '2';
37+
const functionName = 'MyFunctionWithStandardHandler';
38+
new lambda.NodejsFunction(stack, 'MyFunction', {
39+
functionName: functionName,
40+
tracing: Tracing.ACTIVE,
41+
environment: {
42+
EXPECTED_NAMESPACE: expectedNamespace,
43+
EXPECTED_SERVICE_NAME: expectedServiceName,
44+
EXPECTED_METRIC_NAME: expectedMetricName,
45+
EXPECTED_METRIC_UNIT: expectedMetricUnit,
46+
EXPECTED_METRIC_VALUE: expectedMetricValue,
47+
EXPECTED_DEFAULT_DIMENSIONS: JSON.stringify(expectedDefaultDimensions),
48+
EXPECTED_EXTRA_DIMENSION: JSON.stringify(expectedExtraDimension),
49+
EXPECTED_SINGLE_METRIC_DIMENSION: JSON.stringify(expectedSingleMetricDimension),
50+
EXPECTED_SINGLE_METRIC_NAME: expectedSingleMetricName,
51+
EXPECTED_SINGLE_METRIC_UNIT: expectedSingleMetricUnit,
52+
EXPECTED_SINGLE_METRIC_VALUE: expectedSingleMetricValue,
53+
},
54+
});
5655

57-
const stackArtifact = integTestApp.synth().getStackByName(stack.stackName);
56+
const stackArtifact = integTestApp.synth().getStackByName(stack.stackName);
5857

58+
describe('happy cases', () => {
59+
beforeAll(async () => {
5960
const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({
6061
profile: process.env.AWS_PROFILE,
6162
});
@@ -66,7 +67,11 @@ describe('coldstart', () => {
6667
await cloudFormation.deployStack({
6768
stack: stackArtifact,
6869
});
69-
// and invoked
70+
}, 200000);
71+
72+
it('capture ColdStart Metric', async () => {
73+
// WHEN
74+
// invoked
7075
await lambdaClient
7176
.invoke({
7277
FunctionName: functionName,
@@ -106,27 +111,82 @@ describe('coldstart', () => {
106111
MetricName: 'ColdStart',
107112
Statistics: ['Sum'],
108113
},
109-
undefined,
114+
undefined
110115
)
111116
.promise();
112117

113118
// Despite lambda has been called twice, coldstart metric sum should only be 1
114119
const singleDataPoint = coldStartMetricStat.Datapoints ? coldStartMetricStat.Datapoints[0] : {};
115120
expect(singleDataPoint.Sum).toBe(1);
116-
}, 9000000);
117-
});
121+
}, 15000);
118122

119-
afterAll(async () => {
120-
if (!process.env.DISABLE_TEARDOWN) {
121-
const stackArtifact = integTestApp.synth().getStackByName(stack.stackName);
123+
it('produce added Metric with the default and extra one dimensions', async () => {
124+
// GIVEN
125+
const invocationCount = 2;
122126

123-
const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({
124-
profile: process.env.AWS_PROFILE,
125-
});
126-
const cloudFormation = new CloudFormationDeployments({ sdkProvider });
127+
// WHEN
128+
// invoked
129+
for (let i = 0; i < invocationCount; i++) {
130+
await lambdaClient
131+
.invoke({
132+
FunctionName: functionName,
133+
})
134+
.promise();
135+
}
127136

128-
await cloudFormation.destroyStack({
129-
stack: stackArtifact,
130-
});
131-
}
132-
}, 9000000);
137+
// THEN
138+
// sleep to allow metrics to be collected
139+
await new Promise((resolve) => setTimeout(resolve, 10000));
140+
141+
// Check metric dimensions
142+
const metrics = await cloudwatchClient
143+
.listMetrics({
144+
Namespace: expectedNamespace,
145+
MetricName: expectedMetricName,
146+
})
147+
.promise();
148+
expect(metrics.Metrics?.length).toBe(1);
149+
const metric = metrics.Metrics?.[0];
150+
const expectedDimensions = [
151+
{ Name: 'service', Value: expectedServiceName },
152+
{ Name: Object.keys(expectedDefaultDimensions)[0], Value: expectedDefaultDimensions.MyDimension },
153+
{ Name: Object.keys(expectedExtraDimension)[0], Value: expectedExtraDimension.MyExtraDimension },
154+
];
155+
expect(metric?.Dimensions).toStrictEqual(expectedDimensions);
156+
157+
// Check coldstart metric value
158+
const metricStat = await cloudwatchClient
159+
.getMetricStatistics(
160+
{
161+
Namespace: expectedNamespace,
162+
StartTime: new Date(startTime.getTime() - 60 * 1000), // minus 1 minute,
163+
Dimensions: expectedDimensions,
164+
EndTime: new Date(new Date().getTime() + 60 * 1000),
165+
Period: 60,
166+
MetricName: expectedMetricName,
167+
Statistics: ['Sum'],
168+
},
169+
undefined
170+
)
171+
.promise();
172+
173+
// Since lambda has been called twice in this test and potentially more in others, metric sum should be at least of expectedMetricValue * invocationCount
174+
const singleDataPoint = metricStat.Datapoints ? metricStat.Datapoints[0] : {};
175+
expect(singleDataPoint.Sum).toBeGreaterThanOrEqual(parseInt(expectedMetricValue) * invocationCount);
176+
}, 15000);
177+
178+
afterAll(async () => {
179+
if (!process.env.DISABLE_TEARDOWN) {
180+
const stackArtifact = integTestApp.synth().getStackByName(stack.stackName);
181+
182+
const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({
183+
profile: process.env.AWS_PROFILE,
184+
});
185+
const cloudFormation = new CloudFormationDeployments({ sdkProvider });
186+
187+
await cloudFormation.destroyStack({
188+
stack: stackArtifact,
189+
});
190+
}
191+
}, 200000);
192+
});

Diff for: packages/metrics/tests/unit/Metrics.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ describe('Class: Metrics', () => {
378378
const handler = async (_event: DummyEvent, _context: Context): Promise<void> => {
379379
metrics.raiseOnEmptyMetrics();
380380
// Logic goes here
381-
metrics.purgeStoredMetrics();
381+
metrics.publishStoredMetrics();
382382
};
383383

384384
try {
@@ -474,7 +474,7 @@ describe('Class: Metrics', () => {
474474
_callback: Callback<TResult>,
475475
): void | Promise<TResult> {
476476
metrics.addMetric('test_name_1', MetricUnits.Count, 1);
477-
metrics.purgeStoredMetrics();
477+
metrics.publishStoredMetrics();
478478
}
479479
}
480480

0 commit comments

Comments
 (0)