Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cloudwatch): parse all metrics statistics and support long format #23095

Merged
merged 27 commits into from Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2aea37e
Correctly parse metric statistic
rigwild Nov 26, 2022
d1e5845
Update usages
rigwild Nov 26, 2022
45b20f0
Add statistics parsing tests
rigwild Nov 26, 2022
c2dc018
Explain statistics in README
rigwild Nov 26, 2022
0d3882b
Fix build fail
rigwild Nov 26, 2022
d5bd361
Lint errors
rigwild Nov 26, 2022
eb18f5e
Lint errors lmeow
rigwild Nov 26, 2022
019733a
Remove breaking change, allow case insensitive
rigwild Nov 26, 2022
1b90c57
make it work lmeow
rigwild Nov 27, 2022
1bbbf35
Add integration tests
rigwild Nov 27, 2022
be2e11e
Add missing IQM
rigwild Dec 1, 2022
d356ea6
Use `Stats` instead of private `Statistic` - no breaking changes
rigwild Dec 1, 2022
70a2418
Add stats class alarm tests
rigwild Dec 1, 2022
b77e5a6
Add integration tests cdk.out (it disappeared somehow)
rigwild Dec 1, 2022
7e7c42a
Log warning message if unrecognized stat
rigwild Dec 1, 2022
e52fc5e
Remove readme trailing comma
rigwild Dec 1, 2022
997be52
Fix tests
rigwild Dec 1, 2022
729d145
Merge branch 'main' into main
rigwild Jan 19, 2023
6e7f950
Merge branch 'main' into main
rigwild Feb 4, 2023
6654d01
Merge branch 'main' of github.com:aws/aws-cdk into main
rigwild Feb 13, 2023
34843f9
Use proper warning
rigwild Feb 13, 2023
3b4dc19
Add warning check test
rigwild Feb 13, 2023
79b0bea
Merge branch 'main' of github.com:rigwild/aws-cdk into main
rigwild Feb 13, 2023
7c57780
Merge branch 'main' into main
rigwild Feb 13, 2023
699261d
Update packages/@aws-cdk/aws-cloudwatch/lib/metric.ts
rigwild Feb 20, 2023
05a16a8
Update packages/@aws-cdk/aws-cloudwatch/lib/metric.ts
rigwild Feb 20, 2023
2391b6a
Merge branch 'main' into main
TheRealAmazonKendra Feb 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 54 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/README.md
Expand Up @@ -166,6 +166,60 @@ below).
> happen to know the Metric you want to alarm on makes sense as a rate
> (`Average`) you can always choose to change the statistic.

### Available Aggregation Statistics

For your metrics aggregation, you can use the following statistics:

| Statistic | Short format | Long format | Factory name |
| ------------------------ | :-----------------: | :------------------------------------------: | -------------------- |
| SampleCount (n) | ❌ | ❌ | `Stats.SAMPLE_COUNT` |
| Average (avg) | ❌ | ❌ | `Stats.AVERAGE` |
| Sum | ❌ | ❌ | `Stats.SUM` |
| Minimum (min) | ❌ | ❌ | `Stats.MINIMUM` |
| Maximum (max) | ❌ | ❌ | `Stats.MAXIMUM` |
| Interquartile mean (IQM) | ❌ | ❌ | `Stats.IQM` |
| Percentile (p) | `p99` | ❌ | `Stats.p(99)` |
| Winsorized mean (WM) | `wm99` = `WM(:99%)` | `WM(x:y) \| WM(x%:y%) \| WM(x%:) \| WM(:y%)` | `Stats.wm(10, 90)` |
| Trimmed count (TC) | `tc99` = `TC(:99%)` | `TC(x:y) \| TC(x%:y%) \| TC(x%:) \| TC(:y%)` | `Stats.tc(10, 90)` |
| Trimmed sum (TS) | `ts99` = `TS(:99%)` | `TS(x:y) \| TS(x%:y%) \| TS(x%:) \| TS(:y%)` | `Stats.ts(10, 90)` |
| Percentile rank (PR) | ❌ | `PR(x:y) \| PR(x:) \| PR(:y)` | `Stats.pr(10, 5000)` |

The most common values are provided in the `cloudwatch.Stats` class. You can provide any string if your statistic is not in the class.

Read more at [CloudWatch statistics definitions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Statistics-definitions.html).

```ts
new cloudwatch.Metric({
namespace: 'AWS/Route53',
metricName: 'DNSQueries',
dimensionsMap: {
HostedZoneId: hostedZone.hostedZoneId
},
statistic: cloudwatch.Stats.SAMPLE_COUNT,
period: cloudwatch.Duration.minutes(5)
});

new cloudwatch.Metric({
namespace: 'AWS/Route53',
metricName: 'DNSQueries',
dimensionsMap: {
HostedZoneId: hostedZone.hostedZoneId
},
statistic: cloudwatch.Stats.p(99),
period: cloudwatch.Duration.minutes(5)
});

new cloudwatch.Metric({
namespace: 'AWS/Route53',
metricName: 'DNSQueries',
dimensionsMap: {
HostedZoneId: hostedZone.hostedZoneId
},
statistic: 'TS(7.5%:90%)',
period: cloudwatch.Duration.minutes(5)
});
```

### Labels

Metric labels are displayed in the legend of graphs that include the metrics.
Expand Down
13 changes: 4 additions & 9 deletions packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts
Expand Up @@ -9,7 +9,7 @@ import { IMetric, MetricExpressionConfig, MetricStatConfig } from './metric-type
import { dispatchMetric, metricPeriod } from './private/metric-util';
import { dropUndefined } from './private/object';
import { MetricSet } from './private/rendering';
import { parseStatistic } from './private/statistic';
import { normalizeStatistic, parseStatistic } from './private/statistic';

/**
* Properties for Alarms
Expand Down Expand Up @@ -413,7 +413,7 @@ function renderIfSimpleStatistic(statistic?: string): string | undefined {

const parsed = parseStatistic(statistic);
if (parsed.type === 'simple') {
return parsed.statistic;
return normalizeStatistic(parsed);
}
return undefined;
}
Expand All @@ -422,14 +422,9 @@ function renderIfExtendedStatistic(statistic?: string): string | undefined {
if (statistic === undefined) { return undefined; }

const parsed = parseStatistic(statistic);
if (parsed.type === 'percentile') {
// Already percentile. Avoid parsing because we might get into
// floating point rounding issues, return as-is but lowercase the p.
return statistic.toLowerCase();
} else if (parsed.type === 'generic') {
return statistic;
if (parsed.type === 'single' || parsed.type === 'pair') {
return normalizeStatistic(parsed);
}

return undefined;
}

Expand Down
58 changes: 40 additions & 18 deletions packages/@aws-cdk/aws-cloudwatch/lib/metric.ts
Expand Up @@ -2,11 +2,12 @@ import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';
import { Construct, IConstruct } from 'constructs';
import { Alarm, ComparisonOperator, TreatMissingData } from './alarm';
import { Dimension, IMetric, MetricAlarmConfig, MetricConfig, MetricGraphConfig, Unit } from './metric-types';
import { Dimension, IMetric, MetricAlarmConfig, MetricConfig, MetricGraphConfig, Statistic, Unit } from './metric-types';
import { dispatchMetric, metricKey } from './private/metric-util';
import { normalizeStatistic, parseStatistic } from './private/statistic';
import { normalizeStatistic, pairStatisticToString, parseStatistic, singleStatisticToString } from './private/statistic';
import { Stats } from './stats';

export type DimensionHash = {[dim: string]: any};
export type DimensionHash = { [dim: string]: any };

export type DimensionsMap = { [dim: string]: string };

Expand All @@ -24,6 +25,8 @@ export interface CommonMetricOptions {
/**
* What function to use for aggregating.
*
* Use the `aws_cloudwatch.Stats` helper class to construct valid input strings.
*
* Can be one of the following:
*
* - "Minimum" | "min"
Expand All @@ -38,8 +41,6 @@ export interface CommonMetricOptions {
* - "tcNN.NN" | "tc(NN.NN%:NN.NN%)"
* - "tsNN.NN" | "ts(NN.NN%:NN.NN%)"
*
* Use the factory functions on the `Stats` object to construct valid input strings.
*
* @default Average
*/
readonly statistic?: string;
Expand Down Expand Up @@ -197,13 +198,13 @@ export interface MathExpressionOptions {
readonly searchAccount?: string;

/**
* Region to evaluate search expressions within.
*
* Specifying a searchRegion has no effect to the region used
* for metrics within the expression (passed via usingMetrics).
*
* @default - Deployment region.
*/
* Region to evaluate search expressions within.
*
* Specifying a searchRegion has no effect to the region used
* for metrics within the expression (passed via usingMetrics).
*
* @default - Deployment region.
*/
readonly searchRegion?: string;
}

Expand Down Expand Up @@ -291,17 +292,30 @@ export class Metric implements IMetric {
if (periodSec !== 1 && periodSec !== 5 && periodSec !== 10 && periodSec !== 30 && periodSec % 60 !== 0) {
throw new Error(`'period' must be 1, 5, 10, 30, or a multiple of 60 seconds, received ${periodSec}`);
}

this.warnings = undefined;
this.dimensions = this.validateDimensions(props.dimensionsMap ?? props.dimensions);
this.namespace = props.namespace;
this.metricName = props.metricName;
// Try parsing, this will throw if it's not a valid stat
this.statistic = normalizeStatistic(props.statistic || 'Average');

const parsedStat = parseStatistic(props.statistic || Stats.AVERAGE);
if (parsedStat.type === 'generic') {
// Unrecognized statistic, do not throw, just warn
// There may be a new statistic that this lib does not support yet
const label = props.label ? `, label "${props.label}"`: '';
this.warnings = [
`Unrecognized statistic "${props.statistic}" for metric with namespace "${props.namespace}"${label} and metric name "${props.metricName}".` +
' Preferably use the `aws_cloudwatch.Stats` helper class to specify a statistic.' +
' You can ignore this warning if your statistic is valid but not yet supported by the `aws_cloudwatch.Stats` helper class.',
];
}
this.statistic = normalizeStatistic(parsedStat);

this.label = props.label;
this.color = props.color;
this.unit = props.unit;
this.account = props.account;
this.region = props.region;
this.warnings = undefined;
}

/**
Expand Down Expand Up @@ -389,14 +403,22 @@ export class Metric implements IMetric {
throw new Error('Using a math expression is not supported here. Pass a \'Metric\' object instead');
}

const stat = parseStatistic(metricConfig.metricStat.statistic);
const parsed = parseStatistic(metricConfig.metricStat.statistic);

let extendedStatistic: string | undefined = undefined;
if (parsed.type === 'single') {
extendedStatistic = singleStatisticToString(parsed);
} else if (parsed.type === 'pair') {
extendedStatistic = pairStatisticToString(parsed);
}

return {
dimensions: metricConfig.metricStat.dimensions,
namespace: metricConfig.metricStat.namespace,
metricName: metricConfig.metricStat.metricName,
period: metricConfig.metricStat.period.toSeconds(),
statistic: stat.type === 'simple' ? stat.statistic : undefined,
extendedStatistic: stat.type === 'percentile' ? 'p' + stat.percentile : undefined,
statistic: parsed.type === 'simple' ? parsed.statistic as Statistic : undefined,
extendedStatistic,
unit: this.unit,
};
}
Expand Down