Skip to content

Commit 6195488

Browse files
authored
feat(docdb): add monitoring for Document DB (#174)
Fixes #167 --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
1 parent f5cdd7d commit 6195488

17 files changed

+3512
-267
lines changed

API.md

Lines changed: 650 additions & 5 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ You can also browse the documentation at https://constructs.dev/packages/cdk-mon
7474
| AWS CloudFront (`.monitorCloudFrontDistribution()`) | TPS, traffic, latency, errors | Error rate, low/high TPS | |
7575
| AWS CloudWatch Synthetics Canary (`.monitorSyntheticsCanary()`) | Latency, error count/rate | Error count/rate, latency | |
7676
| AWS CodeBuild (`.monitorCodeBuildProject()`) | Build counts (total, successful, failed), failed rate, duration | Failed build count/rate, duration | |
77+
| AWS DocumentDB (`.monitorDocumentDbCluster()`) | CPU, throttling, read/write latency, transactions, cursors | CPU | |
7778
| AWS DynamoDB (`.monitorDynamoTable()`) | Read and write capacity provisioned / used | Consumed capacity, throttling, latency, errors | |
7879
| AWS DynamoDB Global Secondary Index (`.monitorDynamoTableGlobalSecondaryIndex()`) | Read and write capacity, indexing progress, throttled events | | |
7980
| AWS EC2 (`.monitorEC2Instances()`) | CPU, disk operations, network | | |

lib/common/url/AwsConsoleUrlFactory.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ export class AwsConsoleUrlFactory {
143143
}
144144
}
145145

146+
getDocumentDbClusterUrl(clusterId: string): string | undefined {
147+
const region = this.awsAccountRegion;
148+
const destinationUrl = `https://${region}.console.aws.amazon.com/docdb/home?region=${region}#cluster-details/${clusterId}`;
149+
return this.getAwsConsoleUrl(destinationUrl);
150+
}
151+
146152
/**
147153
* Resolves a destination URL within a resolution context.
148154
* @param context The resolution context.

lib/facade/MonitoringAspect.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as autoscaling from "aws-cdk-lib/aws-autoscaling";
88
import * as acm from "aws-cdk-lib/aws-certificatemanager";
99
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
1010
import * as codebuild from "aws-cdk-lib/aws-codebuild";
11+
import * as docdb from "aws-cdk-lib/aws-docdb";
1112
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
1213
import * as elasticsearch from "aws-cdk-lib/aws-elasticsearch";
1314
import * as glue from "aws-cdk-lib/aws-glue";
@@ -51,6 +52,7 @@ export class MonitoringAspect implements IAspect {
5152
this.monitorAutoScalingGroup(node);
5253
this.monitorCloudFront(node);
5354
this.monitorCodeBuild(node);
55+
this.monitorDocumentDb(node);
5456
this.monitorDynamoDb(node);
5557
this.monitorGlue(node);
5658
this.monitorKinesisAnalytics(node);
@@ -171,6 +173,17 @@ export class MonitoringAspect implements IAspect {
171173
}
172174
}
173175

176+
private monitorDocumentDb(node: IConstruct) {
177+
const [isEnabled, props] = this.getMonitoringDetails(this.props.documentDb);
178+
if (isEnabled && node instanceof docdb.DatabaseCluster) {
179+
this.monitoringFacade.monitorDocumentDbCluster({
180+
cluster: node,
181+
alarmFriendlyName: node.node.path,
182+
...props,
183+
});
184+
}
185+
}
186+
174187
private monitorDynamoDb(node: IConstruct) {
175188
const [isEnabled, props] = this.getMonitoringDetails(this.props.dynamoDB);
176189
if (isEnabled && node instanceof dynamodb.Table) {
@@ -285,7 +298,7 @@ export class MonitoringAspect implements IAspect {
285298
const [isEnabled, props] = this.getMonitoringDetails(this.props.rds);
286299
if (isEnabled && node instanceof rds.DatabaseCluster) {
287300
this.monitoringFacade.monitorRdsCluster({
288-
clusterIdentifier: node.clusterIdentifier,
301+
cluster: node,
289302
alarmFriendlyName: node.node.path,
290303
...props,
291304
});

lib/facade/MonitoringFacade.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ import {
4343
CodeBuildProjectMonitoringProps,
4444
CustomMonitoring,
4545
CustomMonitoringProps,
46+
DocumentDbMonitoring,
47+
DocumentDbMonitoringProps,
4648
DynamoTableGlobalSecondaryIndexMonitoring,
4749
DynamoTableGlobalSecondaryIndexMonitoringProps,
4850
DynamoTableMonitoring,
@@ -390,6 +392,12 @@ export class MonitoringFacade extends MonitoringScope {
390392
return this;
391393
}
392394

395+
monitorDocumentDbCluster(props: DocumentDbMonitoringProps) {
396+
const segment = new DocumentDbMonitoring(this, props);
397+
this.addSegment(segment, props);
398+
return this;
399+
}
400+
393401
monitorDynamoTable(props: DynamoTableMonitoringProps) {
394402
const segment = new DynamoTableMonitoring(this, props);
395403
this.addSegment(segment, props);

lib/facade/aspect-types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CertificateManagerMonitoringOptions,
88
CloudFrontDistributionMonitoringOptions,
99
CodeBuildProjectMonitoringOptions,
10+
DocumentDbMonitoringOptions,
1011
DynamoTableMonitoringOptions,
1112
EC2MonitoringOptions,
1213
ElastiCacheClusterMonitoringOptions,
@@ -50,6 +51,7 @@ export interface MonitoringAspectProps {
5051
readonly billing?: MonitoringAspectType<BillingMonitoringOptions>;
5152
readonly cloudFront?: MonitoringAspectType<CloudFrontDistributionMonitoringOptions>;
5253
readonly codeBuild?: MonitoringAspectType<CodeBuildProjectMonitoringOptions>;
54+
readonly documentDb?: MonitoringAspectType<DocumentDbMonitoringOptions>;
5355
readonly dynamoDB?: MonitoringAspectType<DynamoTableMonitoringOptions>;
5456
readonly ec2?: MonitoringAspectType<EC2MonitoringOptions>;
5557
readonly elasticCache?: MonitoringAspectType<ElastiCacheClusterMonitoringOptions>;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { DimensionsMap } from "aws-cdk-lib/aws-cloudwatch";
2+
import { IDatabaseCluster } from "aws-cdk-lib/aws-docdb";
3+
4+
import {
5+
getLatencyTypeLabel,
6+
getLatencyTypeStatistic,
7+
LatencyType,
8+
MetricFactory,
9+
MetricStatistic,
10+
} from "../../common";
11+
12+
const DocumentDbNamespace = "AWS/DocDB";
13+
14+
export interface DocumentDbMetricFactoryProps {
15+
/**
16+
* database cluster
17+
*/
18+
readonly cluster: IDatabaseCluster;
19+
}
20+
21+
export class DocumentDbMetricFactory {
22+
readonly clusterIdentifier: string;
23+
protected readonly metricFactory: MetricFactory;
24+
protected readonly dimensionsMap: DimensionsMap;
25+
26+
constructor(
27+
metricFactory: MetricFactory,
28+
props: DocumentDbMetricFactoryProps
29+
) {
30+
this.metricFactory = metricFactory;
31+
this.clusterIdentifier = props.cluster.clusterIdentifier;
32+
this.dimensionsMap = { DBClusterIdentifier: this.clusterIdentifier };
33+
}
34+
35+
metricAverageCpuUsageInPercent() {
36+
return this.metric("CPUUtilization", MetricStatistic.AVERAGE, "CPU Usage");
37+
}
38+
39+
metricMaxConnectionCount() {
40+
return this.metric(
41+
"DatabaseConnectionsMax",
42+
MetricStatistic.MAX,
43+
"Connections"
44+
);
45+
}
46+
47+
metricMaxCursorCount() {
48+
return this.metric("DatabaseCursorsMax", MetricStatistic.MAX, "Cursors");
49+
}
50+
51+
metricMaxTransactionOpenCount() {
52+
return this.metric(
53+
"TransactionsOpenMax",
54+
MetricStatistic.MAX,
55+
"Transactions"
56+
);
57+
}
58+
59+
metricOperationsThrottledDueLowMemoryCount() {
60+
return this.metric(
61+
"LowMemNumOperationsThrottled",
62+
MetricStatistic.SUM,
63+
"Operations throttled (low mem)"
64+
);
65+
}
66+
67+
metricReadLatencyInMillis(latencyType: LatencyType) {
68+
const label = "Read " + getLatencyTypeLabel(latencyType);
69+
return this.metric(
70+
"ReadLatency",
71+
getLatencyTypeStatistic(latencyType),
72+
label
73+
);
74+
}
75+
76+
metricWriteLatencyInMillis(latencyType: LatencyType) {
77+
const label = "Write " + getLatencyTypeLabel(latencyType);
78+
return this.metric(
79+
"WriteLatency",
80+
getLatencyTypeStatistic(latencyType),
81+
label
82+
);
83+
}
84+
85+
private metric(
86+
metricName: string,
87+
statistic: MetricStatistic,
88+
label: string
89+
) {
90+
return this.metricFactory.createMetric(
91+
metricName,
92+
statistic,
93+
label,
94+
this.dimensionsMap,
95+
undefined,
96+
DocumentDbNamespace
97+
);
98+
}
99+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import {
2+
GraphWidget,
3+
HorizontalAnnotation,
4+
IWidget,
5+
} from "aws-cdk-lib/aws-cloudwatch";
6+
import {
7+
BaseMonitoringProps,
8+
CountAxisFromZero,
9+
DefaultGraphWidgetHeight,
10+
DefaultSummaryWidgetHeight,
11+
LatencyType,
12+
MetricWithAlarmSupport,
13+
Monitoring,
14+
MonitoringScope,
15+
PercentageAxisFromZeroToHundred,
16+
QuarterWidth,
17+
ThirdWidth,
18+
TimeAxisMillisFromZero,
19+
UsageAlarmFactory,
20+
UsageThreshold,
21+
} from "../../common";
22+
import {
23+
MonitoringHeaderWidget,
24+
MonitoringNamingStrategy,
25+
} from "../../dashboard";
26+
import {
27+
DocumentDbMetricFactory,
28+
DocumentDbMetricFactoryProps,
29+
} from "./DocumentDbMetricFactory";
30+
31+
export interface DocumentDbMonitoringOptions extends BaseMonitoringProps {
32+
readonly addCpuUsageAlarm?: Record<string, UsageThreshold>;
33+
}
34+
35+
export interface DocumentDbMonitoringProps
36+
extends DocumentDbMetricFactoryProps,
37+
DocumentDbMonitoringOptions {}
38+
39+
export class DocumentDbMonitoring extends Monitoring {
40+
protected readonly title: string;
41+
protected readonly url?: string;
42+
43+
protected readonly usageAlarmFactory: UsageAlarmFactory;
44+
protected readonly usageAnnotations: HorizontalAnnotation[];
45+
46+
protected readonly cpuUsageMetric: MetricWithAlarmSupport;
47+
protected readonly readLatencyMetric: MetricWithAlarmSupport;
48+
protected readonly writeLatencyMetric: MetricWithAlarmSupport;
49+
protected readonly connectionsMetric: MetricWithAlarmSupport;
50+
protected readonly cursorsMetric: MetricWithAlarmSupport;
51+
protected readonly transactionsMetric: MetricWithAlarmSupport;
52+
protected readonly throttledMetric: MetricWithAlarmSupport;
53+
54+
constructor(scope: MonitoringScope, props: DocumentDbMonitoringProps) {
55+
super(scope, props);
56+
57+
const metricFactory = new DocumentDbMetricFactory(
58+
scope.createMetricFactory(),
59+
props
60+
);
61+
this.cpuUsageMetric = metricFactory.metricAverageCpuUsageInPercent();
62+
this.readLatencyMetric = metricFactory.metricReadLatencyInMillis(
63+
LatencyType.P90
64+
);
65+
this.writeLatencyMetric = metricFactory.metricWriteLatencyInMillis(
66+
LatencyType.P90
67+
);
68+
this.connectionsMetric = metricFactory.metricMaxConnectionCount();
69+
this.cursorsMetric = metricFactory.metricMaxCursorCount();
70+
this.transactionsMetric = metricFactory.metricMaxTransactionOpenCount();
71+
this.throttledMetric =
72+
metricFactory.metricOperationsThrottledDueLowMemoryCount();
73+
74+
const namingStrategy = new MonitoringNamingStrategy({
75+
...props,
76+
fallbackConstructName: metricFactory.clusterIdentifier,
77+
namedConstruct: props.cluster,
78+
});
79+
this.title = namingStrategy.resolveHumanReadableName();
80+
this.url = scope
81+
.createAwsConsoleUrlFactory()
82+
.getDocumentDbClusterUrl(metricFactory.clusterIdentifier);
83+
const alarmFactory = this.createAlarmFactory(
84+
namingStrategy.resolveAlarmFriendlyName()
85+
);
86+
87+
this.usageAlarmFactory = new UsageAlarmFactory(alarmFactory);
88+
this.usageAnnotations = [];
89+
90+
for (const disambiguator in props.addCpuUsageAlarm) {
91+
const alarmProps = props.addCpuUsageAlarm[disambiguator];
92+
const createdAlarm = this.usageAlarmFactory.addMaxCpuUsagePercentAlarm(
93+
this.cpuUsageMetric,
94+
alarmProps,
95+
disambiguator
96+
);
97+
this.usageAnnotations.push(createdAlarm.annotation);
98+
this.addAlarm(createdAlarm);
99+
}
100+
101+
props.useCreatedAlarms?.consume(this.createdAlarms());
102+
}
103+
104+
summaryWidgets(): IWidget[] {
105+
return [
106+
this.createTitleWidget(),
107+
this.createResourceUsageWidget(ThirdWidth, DefaultSummaryWidgetHeight),
108+
this.createConnectionsWidget(ThirdWidth, DefaultSummaryWidgetHeight),
109+
this.createLatencyWidget(ThirdWidth, DefaultSummaryWidgetHeight),
110+
];
111+
}
112+
113+
widgets(): IWidget[] {
114+
return [
115+
this.createTitleWidget(),
116+
this.createResourceUsageWidget(QuarterWidth, DefaultGraphWidgetHeight),
117+
this.createConnectionsWidget(QuarterWidth, DefaultGraphWidgetHeight),
118+
this.createTransactionsWidget(QuarterWidth, DefaultGraphWidgetHeight),
119+
this.createLatencyWidget(QuarterWidth, DefaultGraphWidgetHeight),
120+
];
121+
}
122+
123+
protected createTitleWidget() {
124+
return new MonitoringHeaderWidget({
125+
family: "DocumentDB",
126+
title: this.title,
127+
goToLinkUrl: this.url,
128+
});
129+
}
130+
131+
protected createResourceUsageWidget(width: number, height: number) {
132+
return new GraphWidget({
133+
width,
134+
height,
135+
title: "CPU Usage",
136+
left: [this.cpuUsageMetric],
137+
leftYAxis: PercentageAxisFromZeroToHundred,
138+
leftAnnotations: this.usageAnnotations,
139+
});
140+
}
141+
142+
protected createConnectionsWidget(width: number, height: number) {
143+
return new GraphWidget({
144+
width,
145+
height,
146+
title: "Connections",
147+
left: [this.connectionsMetric],
148+
leftYAxis: CountAxisFromZero,
149+
});
150+
}
151+
152+
protected createTransactionsWidget(width: number, height: number) {
153+
return new GraphWidget({
154+
width,
155+
height,
156+
title: "Transactions",
157+
left: [this.transactionsMetric, this.cursorsMetric],
158+
leftYAxis: CountAxisFromZero,
159+
});
160+
}
161+
162+
protected createLatencyWidget(width: number, height: number) {
163+
return new GraphWidget({
164+
width,
165+
height,
166+
title: "Latency",
167+
left: [this.readLatencyMetric, this.writeLatencyMetric],
168+
leftYAxis: TimeAxisMillisFromZero,
169+
});
170+
}
171+
}

lib/monitoring/aws-docdb/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./DocumentDbMetricFactory";
2+
export * from "./DocumentDbMonitoring";

0 commit comments

Comments
 (0)