Skip to content

Commit

Permalink
feat(docdb): add monitoring for Document DB (#174)
Browse files Browse the repository at this point in the history
Fixes #167 

---

_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 Jun 27, 2022
1 parent f5cdd7d commit 6195488
Show file tree
Hide file tree
Showing 17 changed files with 3,512 additions and 267 deletions.
655 changes: 650 additions & 5 deletions API.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ You can also browse the documentation at https://constructs.dev/packages/cdk-mon
| AWS CloudFront (`.monitorCloudFrontDistribution()`) | TPS, traffic, latency, errors | Error rate, low/high TPS | |
| AWS CloudWatch Synthetics Canary (`.monitorSyntheticsCanary()`) | Latency, error count/rate | Error count/rate, latency | |
| AWS CodeBuild (`.monitorCodeBuildProject()`) | Build counts (total, successful, failed), failed rate, duration | Failed build count/rate, duration | |
| AWS DocumentDB (`.monitorDocumentDbCluster()`) | CPU, throttling, read/write latency, transactions, cursors | CPU | |
| 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 | | |
| AWS EC2 (`.monitorEC2Instances()`) | CPU, disk operations, network | | |
Expand Down
6 changes: 6 additions & 0 deletions lib/common/url/AwsConsoleUrlFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ export class AwsConsoleUrlFactory {
}
}

getDocumentDbClusterUrl(clusterId: string): string | undefined {
const region = this.awsAccountRegion;
const destinationUrl = `https://${region}.console.aws.amazon.com/docdb/home?region=${region}#cluster-details/${clusterId}`;
return this.getAwsConsoleUrl(destinationUrl);
}

/**
* Resolves a destination URL within a resolution context.
* @param context The resolution context.
Expand Down
15 changes: 14 additions & 1 deletion lib/facade/MonitoringAspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as autoscaling from "aws-cdk-lib/aws-autoscaling";
import * as acm from "aws-cdk-lib/aws-certificatemanager";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as codebuild from "aws-cdk-lib/aws-codebuild";
import * as docdb from "aws-cdk-lib/aws-docdb";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as elasticsearch from "aws-cdk-lib/aws-elasticsearch";
import * as glue from "aws-cdk-lib/aws-glue";
Expand Down Expand Up @@ -51,6 +52,7 @@ export class MonitoringAspect implements IAspect {
this.monitorAutoScalingGroup(node);
this.monitorCloudFront(node);
this.monitorCodeBuild(node);
this.monitorDocumentDb(node);
this.monitorDynamoDb(node);
this.monitorGlue(node);
this.monitorKinesisAnalytics(node);
Expand Down Expand Up @@ -171,6 +173,17 @@ export class MonitoringAspect implements IAspect {
}
}

private monitorDocumentDb(node: IConstruct) {
const [isEnabled, props] = this.getMonitoringDetails(this.props.documentDb);
if (isEnabled && node instanceof docdb.DatabaseCluster) {
this.monitoringFacade.monitorDocumentDbCluster({
cluster: node,
alarmFriendlyName: node.node.path,
...props,
});
}
}

private monitorDynamoDb(node: IConstruct) {
const [isEnabled, props] = this.getMonitoringDetails(this.props.dynamoDB);
if (isEnabled && node instanceof dynamodb.Table) {
Expand Down Expand Up @@ -285,7 +298,7 @@ export class MonitoringAspect implements IAspect {
const [isEnabled, props] = this.getMonitoringDetails(this.props.rds);
if (isEnabled && node instanceof rds.DatabaseCluster) {
this.monitoringFacade.monitorRdsCluster({
clusterIdentifier: node.clusterIdentifier,
cluster: node,
alarmFriendlyName: node.node.path,
...props,
});
Expand Down
8 changes: 8 additions & 0 deletions lib/facade/MonitoringFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import {
CodeBuildProjectMonitoringProps,
CustomMonitoring,
CustomMonitoringProps,
DocumentDbMonitoring,
DocumentDbMonitoringProps,
DynamoTableGlobalSecondaryIndexMonitoring,
DynamoTableGlobalSecondaryIndexMonitoringProps,
DynamoTableMonitoring,
Expand Down Expand Up @@ -390,6 +392,12 @@ export class MonitoringFacade extends MonitoringScope {
return this;
}

monitorDocumentDbCluster(props: DocumentDbMonitoringProps) {
const segment = new DocumentDbMonitoring(this, props);
this.addSegment(segment, props);
return this;
}

monitorDynamoTable(props: DynamoTableMonitoringProps) {
const segment = new DynamoTableMonitoring(this, props);
this.addSegment(segment, props);
Expand Down
2 changes: 2 additions & 0 deletions lib/facade/aspect-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CertificateManagerMonitoringOptions,
CloudFrontDistributionMonitoringOptions,
CodeBuildProjectMonitoringOptions,
DocumentDbMonitoringOptions,
DynamoTableMonitoringOptions,
EC2MonitoringOptions,
ElastiCacheClusterMonitoringOptions,
Expand Down Expand Up @@ -50,6 +51,7 @@ export interface MonitoringAspectProps {
readonly billing?: MonitoringAspectType<BillingMonitoringOptions>;
readonly cloudFront?: MonitoringAspectType<CloudFrontDistributionMonitoringOptions>;
readonly codeBuild?: MonitoringAspectType<CodeBuildProjectMonitoringOptions>;
readonly documentDb?: MonitoringAspectType<DocumentDbMonitoringOptions>;
readonly dynamoDB?: MonitoringAspectType<DynamoTableMonitoringOptions>;
readonly ec2?: MonitoringAspectType<EC2MonitoringOptions>;
readonly elasticCache?: MonitoringAspectType<ElastiCacheClusterMonitoringOptions>;
Expand Down
99 changes: 99 additions & 0 deletions lib/monitoring/aws-docdb/DocumentDbMetricFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { DimensionsMap } from "aws-cdk-lib/aws-cloudwatch";
import { IDatabaseCluster } from "aws-cdk-lib/aws-docdb";

import {
getLatencyTypeLabel,
getLatencyTypeStatistic,
LatencyType,
MetricFactory,
MetricStatistic,
} from "../../common";

const DocumentDbNamespace = "AWS/DocDB";

export interface DocumentDbMetricFactoryProps {
/**
* database cluster
*/
readonly cluster: IDatabaseCluster;
}

export class DocumentDbMetricFactory {
readonly clusterIdentifier: string;
protected readonly metricFactory: MetricFactory;
protected readonly dimensionsMap: DimensionsMap;

constructor(
metricFactory: MetricFactory,
props: DocumentDbMetricFactoryProps
) {
this.metricFactory = metricFactory;
this.clusterIdentifier = props.cluster.clusterIdentifier;
this.dimensionsMap = { DBClusterIdentifier: this.clusterIdentifier };
}

metricAverageCpuUsageInPercent() {
return this.metric("CPUUtilization", MetricStatistic.AVERAGE, "CPU Usage");
}

metricMaxConnectionCount() {
return this.metric(
"DatabaseConnectionsMax",
MetricStatistic.MAX,
"Connections"
);
}

metricMaxCursorCount() {
return this.metric("DatabaseCursorsMax", MetricStatistic.MAX, "Cursors");
}

metricMaxTransactionOpenCount() {
return this.metric(
"TransactionsOpenMax",
MetricStatistic.MAX,
"Transactions"
);
}

metricOperationsThrottledDueLowMemoryCount() {
return this.metric(
"LowMemNumOperationsThrottled",
MetricStatistic.SUM,
"Operations throttled (low mem)"
);
}

metricReadLatencyInMillis(latencyType: LatencyType) {
const label = "Read " + getLatencyTypeLabel(latencyType);
return this.metric(
"ReadLatency",
getLatencyTypeStatistic(latencyType),
label
);
}

metricWriteLatencyInMillis(latencyType: LatencyType) {
const label = "Write " + getLatencyTypeLabel(latencyType);
return this.metric(
"WriteLatency",
getLatencyTypeStatistic(latencyType),
label
);
}

private metric(
metricName: string,
statistic: MetricStatistic,
label: string
) {
return this.metricFactory.createMetric(
metricName,
statistic,
label,
this.dimensionsMap,
undefined,
DocumentDbNamespace
);
}
}
171 changes: 171 additions & 0 deletions lib/monitoring/aws-docdb/DocumentDbMonitoring.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import {
GraphWidget,
HorizontalAnnotation,
IWidget,
} from "aws-cdk-lib/aws-cloudwatch";
import {
BaseMonitoringProps,
CountAxisFromZero,
DefaultGraphWidgetHeight,
DefaultSummaryWidgetHeight,
LatencyType,
MetricWithAlarmSupport,
Monitoring,
MonitoringScope,
PercentageAxisFromZeroToHundred,
QuarterWidth,
ThirdWidth,
TimeAxisMillisFromZero,
UsageAlarmFactory,
UsageThreshold,
} from "../../common";
import {
MonitoringHeaderWidget,
MonitoringNamingStrategy,
} from "../../dashboard";
import {
DocumentDbMetricFactory,
DocumentDbMetricFactoryProps,
} from "./DocumentDbMetricFactory";

export interface DocumentDbMonitoringOptions extends BaseMonitoringProps {
readonly addCpuUsageAlarm?: Record<string, UsageThreshold>;
}

export interface DocumentDbMonitoringProps
extends DocumentDbMetricFactoryProps,
DocumentDbMonitoringOptions {}

export class DocumentDbMonitoring extends Monitoring {
protected readonly title: string;
protected readonly url?: string;

protected readonly usageAlarmFactory: UsageAlarmFactory;
protected readonly usageAnnotations: HorizontalAnnotation[];

protected readonly cpuUsageMetric: MetricWithAlarmSupport;
protected readonly readLatencyMetric: MetricWithAlarmSupport;
protected readonly writeLatencyMetric: MetricWithAlarmSupport;
protected readonly connectionsMetric: MetricWithAlarmSupport;
protected readonly cursorsMetric: MetricWithAlarmSupport;
protected readonly transactionsMetric: MetricWithAlarmSupport;
protected readonly throttledMetric: MetricWithAlarmSupport;

constructor(scope: MonitoringScope, props: DocumentDbMonitoringProps) {
super(scope, props);

const metricFactory = new DocumentDbMetricFactory(
scope.createMetricFactory(),
props
);
this.cpuUsageMetric = metricFactory.metricAverageCpuUsageInPercent();
this.readLatencyMetric = metricFactory.metricReadLatencyInMillis(
LatencyType.P90
);
this.writeLatencyMetric = metricFactory.metricWriteLatencyInMillis(
LatencyType.P90
);
this.connectionsMetric = metricFactory.metricMaxConnectionCount();
this.cursorsMetric = metricFactory.metricMaxCursorCount();
this.transactionsMetric = metricFactory.metricMaxTransactionOpenCount();
this.throttledMetric =
metricFactory.metricOperationsThrottledDueLowMemoryCount();

const namingStrategy = new MonitoringNamingStrategy({
...props,
fallbackConstructName: metricFactory.clusterIdentifier,
namedConstruct: props.cluster,
});
this.title = namingStrategy.resolveHumanReadableName();
this.url = scope
.createAwsConsoleUrlFactory()
.getDocumentDbClusterUrl(metricFactory.clusterIdentifier);
const alarmFactory = this.createAlarmFactory(
namingStrategy.resolveAlarmFriendlyName()
);

this.usageAlarmFactory = new UsageAlarmFactory(alarmFactory);
this.usageAnnotations = [];

for (const disambiguator in props.addCpuUsageAlarm) {
const alarmProps = props.addCpuUsageAlarm[disambiguator];
const createdAlarm = this.usageAlarmFactory.addMaxCpuUsagePercentAlarm(
this.cpuUsageMetric,
alarmProps,
disambiguator
);
this.usageAnnotations.push(createdAlarm.annotation);
this.addAlarm(createdAlarm);
}

props.useCreatedAlarms?.consume(this.createdAlarms());
}

summaryWidgets(): IWidget[] {
return [
this.createTitleWidget(),
this.createResourceUsageWidget(ThirdWidth, DefaultSummaryWidgetHeight),
this.createConnectionsWidget(ThirdWidth, DefaultSummaryWidgetHeight),
this.createLatencyWidget(ThirdWidth, DefaultSummaryWidgetHeight),
];
}

widgets(): IWidget[] {
return [
this.createTitleWidget(),
this.createResourceUsageWidget(QuarterWidth, DefaultGraphWidgetHeight),
this.createConnectionsWidget(QuarterWidth, DefaultGraphWidgetHeight),
this.createTransactionsWidget(QuarterWidth, DefaultGraphWidgetHeight),
this.createLatencyWidget(QuarterWidth, DefaultGraphWidgetHeight),
];
}

protected createTitleWidget() {
return new MonitoringHeaderWidget({
family: "DocumentDB",
title: this.title,
goToLinkUrl: this.url,
});
}

protected createResourceUsageWidget(width: number, height: number) {
return new GraphWidget({
width,
height,
title: "CPU Usage",
left: [this.cpuUsageMetric],
leftYAxis: PercentageAxisFromZeroToHundred,
leftAnnotations: this.usageAnnotations,
});
}

protected createConnectionsWidget(width: number, height: number) {
return new GraphWidget({
width,
height,
title: "Connections",
left: [this.connectionsMetric],
leftYAxis: CountAxisFromZero,
});
}

protected createTransactionsWidget(width: number, height: number) {
return new GraphWidget({
width,
height,
title: "Transactions",
left: [this.transactionsMetric, this.cursorsMetric],
leftYAxis: CountAxisFromZero,
});
}

protected createLatencyWidget(width: number, height: number) {
return new GraphWidget({
width,
height,
title: "Latency",
left: [this.readLatencyMetric, this.writeLatencyMetric],
leftYAxis: TimeAxisMillisFromZero,
});
}
}
2 changes: 2 additions & 0 deletions lib/monitoring/aws-docdb/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./DocumentDbMetricFactory";
export * from "./DocumentDbMonitoring";
Loading

0 comments on commit 6195488

Please sign in to comment.