Skip to content

Commit fc379f9

Browse files
committed
feat(metrics): refactoring cost improvement website docs and code comments
1 parent b80798f commit fc379f9

File tree

8 files changed

+124
-39
lines changed

8 files changed

+124
-39
lines changed

docs/core/metrics.md

+20-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ If you're new to Amazon CloudWatch, there are two terminologies you must be awar
2727

2828
* **Namespace**. It's the highest level container that will group multiple metrics from multiple services for a given application, for example `ServerlessEcommerce`.
2929
* **Dimensions**. Metrics metadata in key-value format. They help you slice and dice metrics visualization, for example `ColdStart` metric by Payment `service`.
30+
* **Metric**. It's the name of the metric, for example: SuccessfulBooking or UpdatedBooking.
31+
* **Unit**. It's a value representing the unit of measure for the corresponding metric, for example: Count or Seconds.
32+
* **Resolution**. It's a value representing the storage resolution for the corresponding metric. Metrics can be either Standard or High resolution. Read more [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Resolution_definition).
3033

3134
<figure>
3235
<img src="../../media/metrics_terminology.png" />
@@ -117,7 +120,23 @@ You can create metrics using the `addMetric` method, and you can create dimensio
117120
CloudWatch EMF supports a max of 100 metrics per batch. Metrics will automatically propagate all the metrics when adding the 100th metric. Subsequent metrics, e.g. 101th, will be aggregated into a new EMF object, for your convenience.
118121

119122
!!! warning "Do not create metrics or dimensions outside the handler"
120-
Metrics or dimensions added in the global scope will only be added during cold start. Disregard if that's the intended behaviour.
123+
Metrics or dimensions added in the global scope will only be added during cold start. Disregard if that's the intended behavior.
124+
125+
### Adding high-resolution metrics
126+
127+
You can create [high-resolution metrics](https://aws.amazon.com/about-aws/whats-new/2023/02/amazon-cloudwatch-high-resolution-metric-extraction-structured-logs/) passing `resolution` as parameter to `addMetric`.
128+
129+
!!! tip "When is it useful?"
130+
High-resolution metrics are data with a granularity of one second and are very useful in several situations such as telemetry, time series, real-time incident management, and others.
131+
132+
=== "Metrics with high resolution"
133+
134+
```typescript hl_lines="6"
135+
--8<-- "docs/snippets/metrics/addHighResolutionMetric.ts"
136+
```
137+
138+
!!! tip "Autocomplete Metric Resolutions"
139+
Use the `MetricResolution` type to easily find a supported metric resolution by CloudWatch. Alternatively, you can pass the allowed values of 1 or 60 as an integer.
121140

122141
### Adding multi-value metrics
123142

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Metrics, MetricUnits, MetricResolution } from '@aws-lambda-powertools/metrics';
2+
3+
const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' });
4+
5+
export const handler = async (_event: unknown, _context: unknown): Promise<void> => {
6+
metrics.addMetric('successfulBooking', MetricUnits.Count, 1, MetricResolution.High);
7+
};

docs/snippets/metrics/basicUsage.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
22

33
const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' });
44

5-
export const handler = async (_event, _context): Promise<void> => {
5+
export const handler = async (_event: unknown, _context: unknown): Promise<void> => {
66
metrics.addMetric('successfulBooking', MetricUnits.Count, 1);
77
};

docs/snippets/metrics/tsconfig.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"compilerOptions": {
3+
"noEmit": true,
4+
"strict": true,
5+
"noImplicitAny": true,
6+
"experimentalDecorators": true,
7+
"moduleResolution": "node",
8+
"skipLibCheck": true,
9+
"pretty": true,
10+
"resolveJsonModule": true,
11+
"baseUrl": ".",
12+
"paths": {
13+
"@aws-lambda-powertools/metrics": ["../../../packages/metrics/src"]
14+
}
15+
},
16+
"exclude": ["./node_modules"],
17+
"lib": ["ES2020"],
18+
"types": ["node"]
19+
}

packages/metrics/src/Metrics.ts

+53-19
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
MetricUnit,
1313
MetricUnits,
1414
MetricResolution,
15+
MetricDefinition,
16+
StoredMetric,
1517
} from './types';
1618

1719
const MAX_METRICS_SIZE = 100;
@@ -166,13 +168,32 @@ class Metrics extends Utility implements MetricsInterface {
166168

167169
/**
168170
* Add a metric to the metrics buffer.
169-
* @param name
170-
* @param unit
171-
* @param value
172-
* @param resolution
171+
*
172+
* @example
173+
*
174+
* Add Metric using MetricUnit Enum supported by Cloudwatch
175+
*
176+
* ```ts
177+
* metrics.addMetric('successfulBooking', MetricUnits.Count, 1);
178+
* ```
179+
*
180+
* @example
181+
*
182+
* Add Metric using MetricResolution type with resolutions High or Standard supported by cloudwatch
183+
*
184+
* ```ts
185+
* metrics.addMetric('successfulBooking', MetricUnits.Count, 1, MetricResolution.High);
186+
* ```
187+
*
188+
* @param name - The metric name
189+
* @param unit - The metric unit
190+
* @param value - The metric value
191+
* @param resolution - The metric resolution
192+
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Resolution_definition Amazon Cloudwatch Concepts Documentation
193+
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html#CloudWatch_Embedded_Metric_Format_Specification_structure_metricdefinition Metric Definition of Embedded Metric Format Specification
173194
*/
174195

175-
public addMetric(name: string, unit: MetricUnit, value: number, resolution?: MetricResolution): void {
196+
public addMetric(name: string, unit: MetricUnit, value: number, resolution: MetricResolution = MetricResolution.Standard): void {
176197
this.storeMetric(name, unit, value, resolution);
177198
if (this.isSingleMetric) this.publishStoredMetrics();
178199
}
@@ -317,19 +338,28 @@ class Metrics extends Utility implements MetricsInterface {
317338
}
318339

319340
/**
320-
* Function to create the right object compliant with Cloudwatch EMF (Event Metric Format).
341+
* Function to create the right object compliant with Cloudwatch EMF (Embedded Metric Format).
342+
*
343+
*
344+
* @returns metrics as JSON object compliant EMF Schema Specification
321345
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html for more details
322-
* @returns {string}
323346
*/
324347
public serializeMetrics(): EmfOutput {
325-
const metricDefinitions = Object.values(this.storedMetrics).map((metricDefinition) => metricDefinition.resolution ? ({
326-
Name: metricDefinition.name,
327-
Unit: metricDefinition.unit,
328-
StorageResolution: metricDefinition.resolution
329-
}): ({
330-
Name: metricDefinition.name,
331-
Unit: metricDefinition.unit
332-
}));
348+
// For high-resolution metrics, add StorageResolution property
349+
// Example: [ { "Name": "metric_name", "Unit": "Count", "StorageResolution": 1 } ]
350+
351+
// For standard resolution metrics, don't add StorageResolution property to avoid unnecessary ingestion of data into cloudwatch
352+
// Example: [ { "Name": "metric_name", "Unit": "Count"} ]
353+
const metricDefinitions: MetricDefinition[] = Object.values(this.storedMetrics).map((metricDefinition) =>
354+
this.isHigh(metricDefinition['resolution'])
355+
? ({
356+
Name: metricDefinition.name,
357+
Unit: metricDefinition.unit,
358+
StorageResolution: metricDefinition.resolution
359+
}): ({
360+
Name: metricDefinition.name,
361+
Unit: metricDefinition.unit,
362+
}));
333363

334364
if (metricDefinitions.length === 0 && this.shouldThrowOnEmptyMetrics) {
335365
throw new RangeError('The number of metrics recorded must be higher than zero');
@@ -437,6 +467,10 @@ class Metrics extends Utility implements MetricsInterface {
437467
return <EnvironmentVariablesService> this.envVarsService;
438468
}
439469

470+
private isHigh(resolution: StoredMetric['resolution']): resolution is 1 {
471+
return resolution === MetricResolution.High;
472+
}
473+
440474
private isNewMetric(name: string, unit: MetricUnit): boolean {
441475
if (this.storedMetrics[name]){
442476
// Inconsistent units indicates a bug or typos and we want to flag this to users early
@@ -491,7 +525,7 @@ class Metrics extends Utility implements MetricsInterface {
491525
name: string,
492526
unit: MetricUnit,
493527
value: number,
494-
resolution?: MetricResolution,
528+
resolution: MetricResolution,
495529
): void {
496530
if (Object.keys(this.storedMetrics).length >= MAX_METRICS_SIZE) {
497531
this.publishStoredMetrics();
@@ -501,10 +535,10 @@ class Metrics extends Utility implements MetricsInterface {
501535
this.storedMetrics[name] = {
502536
unit,
503537
value,
504-
name,
505-
resolution,
538+
name,
539+
resolution
506540
};
507-
541+
508542
} else {
509543
const storedMetric = this.storedMetrics[name];
510544
if (!Array.isArray(storedMetric.value)) {

packages/metrics/src/types/Metrics.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ type EmfOutput = {
2121
CloudWatchMetrics: {
2222
Namespace: string
2323
Dimensions: [string[]]
24-
Metrics: {
25-
Name: string
26-
Unit: MetricUnit
27-
StorageResolution?: MetricResolution
28-
}[]
24+
Metrics: MetricDefinition[]
2925
}[]
3026
}
3127
};
@@ -65,11 +61,17 @@ type StoredMetric = {
6561
name: string
6662
unit: MetricUnit
6763
value: number | number[]
68-
resolution?: MetricResolution
64+
resolution: MetricResolution
6965
};
7066

7167
type StoredMetrics = {
7268
[key: string]: StoredMetric
7369
};
7470

75-
export { MetricsOptions, Dimensions, EmfOutput, HandlerMethodDecorator, ExtraOptions, StoredMetrics };
71+
type MetricDefinition = {
72+
Name: string
73+
Unit: MetricUnit
74+
StorageResolution?: MetricResolution
75+
};
76+
77+
export { MetricsOptions, Dimensions, EmfOutput, HandlerMethodDecorator, ExtraOptions, StoredMetrics, StoredMetric, MetricDefinition };

packages/metrics/tests/unit/Metrics.test.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -576,25 +576,30 @@ describe('Class: Metrics', () => {
576576
expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).toContain('Unit');
577577

578578
});
579-
test('should set StorageResolution 60 if MetricResolution is set to `Standard`', () => {
579+
test('serialized metrics in EMF format should not contain `StorageResolution` as key if `Standard` is set', () => {
580580
const metrics = new Metrics();
581581
metrics.addMetric('test_name', MetricUnits.Seconds, 10, MetricResolution.Standard);
582582
const serializedMetrics = metrics.serializeMetrics();
583583

584-
expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(MetricResolution.Standard);
585-
expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(60);
584+
// expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(MetricResolution.Standard);
585+
// expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(60);
586+
587+
expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).not.toContain('StorageResolution');
588+
expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).toContain('Name');
589+
expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).toContain('Unit');
586590
});
587591

588-
test('Should be StorageResolution 60 if MetricResolution is set to `60`',()=>{
592+
test('serialized metrics in EMF format should not contain `StorageResolution` as key if `60` is set',()=>{
589593
const metrics = new Metrics();
590594
metrics.addMetric('test_name', MetricUnits.Seconds, 10, 60);
591595
const serializedMetrics = metrics.serializeMetrics();
592596

593-
expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(MetricResolution.Standard);
594-
expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(60);
597+
expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).not.toContain('StorageResolution');
598+
expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).toContain('Name');
599+
expect(Object.keys(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0])).toContain('Unit');
595600
});
596601

597-
test('Should be StorageResolution 1 if MetricResolution is set to `High`',()=>{
602+
test('Should be StorageResolution `1` if MetricResolution is set to `High`',()=>{
598603
const metrics = new Metrics();
599604
metrics.addMetric('test_name', MetricUnits.Seconds, 10, MetricResolution.High);
600605
const serializedMetrics = metrics.serializeMetrics();
@@ -603,7 +608,7 @@ describe('Class: Metrics', () => {
603608
expect(serializedMetrics._aws.CloudWatchMetrics[0].Metrics[0].StorageResolution).toBe(1);
604609
});
605610

606-
test('Should be StorageResolution 1 if MetricResolution is set to `1`',()=>{
611+
test('Should be StorageResolution `1` if MetricResolution is set to `1`',()=>{
607612
const metrics = new Metrics();
608613
metrics.addMetric('test_name', MetricUnits.Seconds, 10, 1);
609614
const serializedMetrics = metrics.serializeMetrics();

packages/metrics/tests/unit/middleware/middy.test.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ describe('Middy middleware', () => {
321321
});
322322
describe('Metrics resolution', () => {
323323

324-
test('should use metric resolution `Standard, 60` if `Standard` is set', async () => {
324+
test('serialized metrics in EMF format should not contain `StorageResolution` as key if `60` is set', async () => {
325325
// Prepare
326326
const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' });
327327

@@ -346,7 +346,6 @@ describe('Middy middleware', () => {
346346
Metrics: [{
347347
Name: 'successfulBooking',
348348
Unit: 'Count',
349-
StorageResolution: 60,
350349
}],
351350
},
352351
],
@@ -357,7 +356,7 @@ describe('Middy middleware', () => {
357356
);
358357
});
359358

360-
test('should use metric resolution `High, 1` if `High` is set', async () => {
359+
test('Should be StorageResolution `1` if MetricResolution is set to `High`', async () => {
361360
// Prepare
362361
const metrics = new Metrics({ namespace: 'serverlessAirline', serviceName: 'orders' });
363362

0 commit comments

Comments
 (0)