Skip to content

Commit

Permalink
feat(CloudFront): add basic alarms (tps, errors) (#63)
Browse files Browse the repository at this point in the history
Based on the internal contribution by Oriol Petit Rojo.

---

_By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
  • Loading branch information
voho authored Mar 8, 2022
1 parent c4bb4ed commit 3ea6ace
Show file tree
Hide file tree
Showing 6 changed files with 454 additions and 10 deletions.
44 changes: 44 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -7249,6 +7249,10 @@ const cloudFrontDistributionMonitoringProps: CloudFrontDistributionMonitoringPro
| <code><a href="#cdk-monitoring-constructs.CloudFrontDistributionMonitoringProps.property.addToDetailDashboard">addToDetailDashboard</a></code> | <code>boolean</code> | Flag indicating if the widgets should be added to detailed dashboard. |
| <code><a href="#cdk-monitoring-constructs.CloudFrontDistributionMonitoringProps.property.addToSummaryDashboard">addToSummaryDashboard</a></code> | <code>boolean</code> | Flag indicating if the widgets should be added to summary dashboard. |
| <code><a href="#cdk-monitoring-constructs.CloudFrontDistributionMonitoringProps.property.useCreatedAlarms">useCreatedAlarms</a></code> | <code><a href="#cdk-monitoring-constructs.IAlarmConsumer">IAlarmConsumer</a></code> | Calls provided function to process all alarms created. |
| <code><a href="#cdk-monitoring-constructs.CloudFrontDistributionMonitoringProps.property.addError4xxRate">addError4xxRate</a></code> | <code>{[ key: string ]: <a href="#cdk-monitoring-constructs.ErrorRateThreshold">ErrorRateThreshold</a>}</code> | *No description.* |
| <code><a href="#cdk-monitoring-constructs.CloudFrontDistributionMonitoringProps.property.addFault5xxRate">addFault5xxRate</a></code> | <code>{[ key: string ]: <a href="#cdk-monitoring-constructs.ErrorRateThreshold">ErrorRateThreshold</a>}</code> | *No description.* |
| <code><a href="#cdk-monitoring-constructs.CloudFrontDistributionMonitoringProps.property.addHighTpsAlarm">addHighTpsAlarm</a></code> | <code>{[ key: string ]: <a href="#cdk-monitoring-constructs.HighTpsThreshold">HighTpsThreshold</a>}</code> | *No description.* |
| <code><a href="#cdk-monitoring-constructs.CloudFrontDistributionMonitoringProps.property.addLowTpsAlarm">addLowTpsAlarm</a></code> | <code>{[ key: string ]: <a href="#cdk-monitoring-constructs.LowTpsThreshold">LowTpsThreshold</a>}</code> | *No description.* |

---

Expand Down Expand Up @@ -7370,6 +7374,46 @@ Calls provided function to process all alarms created.

---

##### `addError4xxRate`<sup>Optional</sup> <a name="addError4xxRate" id="cdk-monitoring-constructs.CloudFrontDistributionMonitoringProps.property.addError4xxRate"></a>

```typescript
public readonly addError4xxRate: {[ key: string ]: ErrorRateThreshold};
```

- *Type:* {[ key: string ]: <a href="#cdk-monitoring-constructs.ErrorRateThreshold">ErrorRateThreshold</a>}

---

##### `addFault5xxRate`<sup>Optional</sup> <a name="addFault5xxRate" id="cdk-monitoring-constructs.CloudFrontDistributionMonitoringProps.property.addFault5xxRate"></a>

```typescript
public readonly addFault5xxRate: {[ key: string ]: ErrorRateThreshold};
```

- *Type:* {[ key: string ]: <a href="#cdk-monitoring-constructs.ErrorRateThreshold">ErrorRateThreshold</a>}

---

##### `addHighTpsAlarm`<sup>Optional</sup> <a name="addHighTpsAlarm" id="cdk-monitoring-constructs.CloudFrontDistributionMonitoringProps.property.addHighTpsAlarm"></a>

```typescript
public readonly addHighTpsAlarm: {[ key: string ]: HighTpsThreshold};
```

- *Type:* {[ key: string ]: <a href="#cdk-monitoring-constructs.HighTpsThreshold">HighTpsThreshold</a>}

---

##### `addLowTpsAlarm`<sup>Optional</sup> <a name="addLowTpsAlarm" id="cdk-monitoring-constructs.CloudFrontDistributionMonitoringProps.property.addLowTpsAlarm"></a>

```typescript
public readonly addLowTpsAlarm: {[ key: string ]: LowTpsThreshold};
```

- *Type:* {[ key: string ]: <a href="#cdk-monitoring-constructs.LowTpsThreshold">LowTpsThreshold</a>}

---

### CodeBuildProjectMetricFactoryProps <a name="CodeBuildProjectMetricFactoryProps" id="cdk-monitoring-constructs.CodeBuildProjectMetricFactoryProps"></a>

#### Initializer <a name="Initializer" id="cdk-monitoring-constructs.CodeBuildProjectMetricFactoryProps.Initializer"></a>
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ You can also browse the documentation at https://constructs.dev/packages/cdk-mon
| AWS AppSync (GraphQL API) (`.monitorAppSyncApi()`) | TPS, latency, errors | Latency, error count/rate, low/high TPS | |
| AWS Billing (`.monitorBilling()`) | AWS account cost | | [Requires enabling](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/gs_monitor_estimated_charges_with_cloudwatch.html#gs_turning_on_billing_metrics) the **Receive Billing Alerts** option in AWS Console / Billing Preferences |
| AWS Certificate Manager (`.monitorCertificate()`) | Certificate expiration | Days until expiration | |
| AWS CloudFront (`.monitorCloudFrontDistribution()`) | TPS, traffic, latency, errors | | |
| AWS CloudFront (`.monitorCloudFrontDistribution()`) | TPS, traffic, latency, errors | Error rate, low/high TPS | |
| AWS CodeBuild (`.monitorCodeBuildProject()`) | Build counts (total, successful, failed), failed rate, duration | Failed build count/rate, duration | |
| AWS DynamoDB (`.monitorDynamoTable()`) | Read and write capacity provisioned / used | Consumed capacity, throttling, latency, errors | |
| AWS DynamoDB Global Secondary Index (`.monitorDynamoTableGlobalSecondaryIndex()`) | Read and write capacity, indexing progress, throttled events | | |
Expand Down
90 changes: 86 additions & 4 deletions lib/monitoring/aws-cloudfront/CloudFrontDistributionMonitoring.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import { GraphWidget, IWidget } from "monocdk/aws-cloudwatch";
import {
GraphWidget,
HorizontalAnnotation,
IWidget,
} from "monocdk/aws-cloudwatch";

import {
AlarmFactory,
BaseMonitoringProps,
CountAxisFromZero,
DefaultGraphWidgetHeight,
DefaultSummaryWidgetHeight,
ErrorAlarmFactory,
ErrorRateThreshold,
ErrorType,
HalfWidth,
HighTpsThreshold,
LowTpsThreshold,
MetricWithAlarmSupport,
Monitoring,
MonitoringScope,
PercentageAxisFromZeroToHundred,
QuarterWidth,
RateAxisFromZero,
SizeAxisBytesFromZero,
TpsAlarmFactory,
} from "../../common";
import {
MonitoringHeaderWidget,
Expand All @@ -28,13 +40,24 @@ export interface CloudFrontDistributionMonitoringOptions
export interface CloudFrontDistributionMonitoringProps
extends CloudFrontDistributionMetricFactoryProps,
CloudFrontDistributionMonitoringOptions {
// empty
readonly addLowTpsAlarm?: Record<string, LowTpsThreshold>;
readonly addHighTpsAlarm?: Record<string, HighTpsThreshold>;
readonly addError4xxRate?: Record<string, ErrorRateThreshold>;
readonly addFault5xxRate?: Record<string, ErrorRateThreshold>;
}

export class CloudFrontDistributionMonitoring extends Monitoring {
private readonly title: string;
private readonly distributionUrl?: string;

protected readonly namingStrategy: MonitoringNamingStrategy;
protected readonly alarmFactory: AlarmFactory;
protected readonly errorAlarmFactory: ErrorAlarmFactory;
protected readonly tpsAlarmFactory: TpsAlarmFactory;

protected readonly errorRateAnnotations: HorizontalAnnotation[];
protected readonly tpsAnnotations: HorizontalAnnotation[];

protected readonly tpsMetric: MetricWithAlarmSupport;
protected readonly downloadedBytesMetric: MetricWithAlarmSupport;
protected readonly uploadedBytesMetric: MetricWithAlarmSupport;
Expand All @@ -51,16 +74,25 @@ export class CloudFrontDistributionMonitoring extends Monitoring {
const namedConstruct = props.distribution;
const fallbackConstructName = namedConstruct.distributionId;

const namingStrategy = new MonitoringNamingStrategy({
this.namingStrategy = new MonitoringNamingStrategy({
...props,
namedConstruct,
fallbackConstructName,
});
this.title = namingStrategy.resolveHumanReadableName();
this.title = this.namingStrategy.resolveHumanReadableName();
this.distributionUrl = scope
.createAwsConsoleUrlFactory()
.getCloudFrontDistributionUrl(namedConstruct.distributionId);

this.alarmFactory = this.createAlarmFactory(
this.namingStrategy.resolveAlarmFriendlyName()
);
this.errorAlarmFactory = new ErrorAlarmFactory(this.alarmFactory);
this.tpsAlarmFactory = new TpsAlarmFactory(this.alarmFactory);

this.errorRateAnnotations = [];
this.tpsAnnotations = [];

const metricFactory = new CloudFrontDistributionMetricFactory(
scope.createMetricFactory(),
props
Expand All @@ -71,6 +103,53 @@ export class CloudFrontDistributionMonitoring extends Monitoring {
this.cacheHitRate = metricFactory.metricCacheHitRateAverageInPercent();
this.error4xxRate = metricFactory.metric4xxErrorRateAverage();
this.error5xxRate = metricFactory.metric5xxErrorRateAverage();

for (const disambiguator in props.addLowTpsAlarm) {
const alarmProps = props.addLowTpsAlarm[disambiguator];
const createdAlarm = this.tpsAlarmFactory.addMinTpsAlarm(
this.tpsMetric,
alarmProps,
disambiguator
);
this.tpsAnnotations.push(createdAlarm.annotation);
this.addAlarm(createdAlarm);
}
for (const disambiguator in props.addHighTpsAlarm) {
const alarmProps = props.addHighTpsAlarm[disambiguator];
const createdAlarm = this.tpsAlarmFactory.addMaxTpsAlarm(
this.tpsMetric,
alarmProps,
disambiguator
);
this.tpsAnnotations.push(createdAlarm.annotation);
this.addAlarm(createdAlarm);
}
for (const disambiguator in props.addError4xxRate) {
const alarmProps = props.addError4xxRate[disambiguator];
const createdAlarm = this.errorAlarmFactory.addErrorRateAlarm(
this.error4xxRate,
ErrorType.ERROR,
alarmProps,
disambiguator
);
this.errorRateAnnotations.push(createdAlarm.annotation);
this.addAlarm(createdAlarm);
}
for (const disambiguator in props.addFault5xxRate) {
const alarmProps = props.addFault5xxRate[disambiguator];
const createdAlarm = this.errorAlarmFactory.addErrorRateAlarm(
this.error5xxRate,
ErrorType.FAULT,
alarmProps,
disambiguator
);
this.errorRateAnnotations.push(createdAlarm.annotation);
this.addAlarm(createdAlarm);
}

if (props.useCreatedAlarms) {
props.useCreatedAlarms.consume(this.createdAlarms());
}
}

summaryWidgets(): IWidget[] {
Expand Down Expand Up @@ -106,6 +185,7 @@ export class CloudFrontDistributionMonitoring extends Monitoring {
title: "TPS",
left: [this.tpsMetric],
leftYAxis: CountAxisFromZero,
leftAnnotations: this.tpsAnnotations,
});
}

Expand Down Expand Up @@ -135,6 +215,8 @@ export class CloudFrontDistributionMonitoring extends Monitoring {
height,
title: "Errors (rate)",
left: [this.error4xxRate, this.error5xxRate],
leftAnnotations: this.errorRateAnnotations,
leftYAxis: RateAxisFromZero,
});
}
}
4 changes: 2 additions & 2 deletions test/facade/__snapshots__/MonitoringAspect.test.ts.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 51 additions & 2 deletions test/monitoring/aws-cloudfront/CloudFrontDistribution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import { Distribution } from "monocdk/aws-cloudfront";
import { S3Origin } from "monocdk/aws-cloudfront-origins";
import { Bucket } from "monocdk/aws-s3";

import { CloudFrontDistributionMonitoring } from "../../../lib";
import {
AlarmWithAnnotation,
CloudFrontDistributionMonitoring,
} from "../../../lib";
import { TestMonitoringScope } from "../TestMonitoringScope";

test("snapshot test", () => {
test("snapshot test: no alarms", () => {
const stack = new Stack();
const bucket = new Bucket(stack, "Bucket");
const distribution = new Distribution(stack, "Distribution", {
Expand All @@ -22,3 +25,49 @@ test("snapshot test", () => {

expect(Template.fromStack(stack)).toMatchSnapshot();
});

test("snapshot test: all alarms", () => {
const stack = new Stack();
const bucket = new Bucket(stack, "Bucket");
const distribution = new Distribution(stack, "Distribution", {
defaultBehavior: {
origin: new S3Origin(bucket),
},
});

const scope = new TestMonitoringScope(stack, "Scope");

let numAlarmsCreated = 0;

new CloudFrontDistributionMonitoring(scope, {
distribution,
addLowTpsAlarm: {
Warning: {
minTps: 10,
},
},
addHighTpsAlarm: {
Warning: {
maxTps: 20,
},
},
addError4xxRate: {
Warning: {
maxErrorRate: 0.5,
},
},
addFault5xxRate: {
Warning: {
maxErrorRate: 0.8,
},
},
useCreatedAlarms: {
consume(alarms: AlarmWithAnnotation[]) {
numAlarmsCreated = alarms.length;
},
},
});

expect(numAlarmsCreated).toStrictEqual(4);
expect(Template.fromStack(stack)).toMatchSnapshot();
});
Loading

0 comments on commit 3ea6ace

Please sign in to comment.