Skip to content

Commit b6da058

Browse files
authored
feat(rds): add support for RDS Instance Monitoring (#496)
Fixes #236 Added support for monitoring RDS Instances. Implementation is largely based on RDS Cluster monitoring code and includes the metrics necessary to recreate the "automatic" Cloud Watch dashboard. This implementation supports my needs, but further metrics can be added if there is a interest. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
1 parent 0500421 commit b6da058

File tree

11 files changed

+3016
-6
lines changed

11 files changed

+3016
-6
lines changed

API.md

Lines changed: 969 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ You can browse the documentation at https://constructs.dev/packages/cdk-monitori
8686
| AWS Load Balancing (`.monitorNetworkLoadBalancer()`, `.monitorFargateApplicationLoadBalancer()`, `.monitorFargateNetworkLoadBalancer()`, `.monitorEc2ApplicationLoadBalancer()`, `.monitorEc2NetworkLoadBalancer()`) | System resources and task health | Unhealthy task count, running tasks count, (for Fargate/Ec2 apps) CPU/memory usage | Use for FargateService or Ec2Service backed by a NetworkLoadBalancer or ApplicationLoadBalancer |
8787
| AWS OpenSearch/Elasticsearch (`.monitorOpenSearchCluster()`, `.monitorElasticsearchCluster()`) | Indexing and search latency, disk/memory/CPU usage | Indexing and search latency, disk/memory/CPU usage, cluster status, KMS keys | |
8888
| AWS RDS (`.monitorRdsCluster()`) | Query duration, connections, latency, disk/CPU usage | Connections, disk and CPU usage | |
89+
| AWS RDS (`.monitorRdsInstance()`) | Query duration, connections, latency, disk/CPU usage | Connections, disk and CPU usage | |
8990
| AWS Redshift (`.monitorRedshiftCluster()`) | Query duration, connections, latency, disk/CPU usage | Query duration, connections, disk and CPU usage | |
9091
| AWS S3 Bucket (`.monitorS3Bucket()`) | Bucket size and number of objects | | |
9192
| AWS SecretsManager (`.monitorSecretsManager()`) | Max secret count, min secret sount, secret count change | Min/max secret count or change in secret count | |

lib/common/url/AwsConsoleUrlFactory.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ export class AwsConsoleUrlFactory {
119119
return this.getAwsConsoleUrl(destinationUrl);
120120
}
121121

122+
getRdsInstanceUrl(instanceId: string): string | undefined {
123+
const region = this.awsAccountRegion;
124+
const destinationUrl = `https://${region}.console.aws.amazon.com/rds/home?region=${region}#database:id=${instanceId};is-cluster=false;tab=monitoring`;
125+
return this.getAwsConsoleUrl(destinationUrl);
126+
}
127+
122128
getRedshiftClusterUrl(clusterId: string): string | undefined {
123129
const region = this.awsAccountRegion;
124130
const destinationUrl = `https://${region}.console.aws.amazon.com/redshiftv2/home?region=${region}#cluster-details?cluster=${clusterId}`;

lib/facade/IMonitoringAspect.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
LambdaFunctionMonitoringOptions,
2020
OpenSearchClusterMonitoringOptions,
2121
RdsClusterMonitoringOptions,
22+
RdsInstanceMonitoringOptions,
2223
RedshiftClusterMonitoringOptions,
2324
S3BucketMonitoringOptions,
2425
SecretsManagerSecretMonitoringOptions,
@@ -63,7 +64,12 @@ export interface MonitoringAspectProps {
6364
readonly kinesisFirehose?: MonitoringAspectType<KinesisFirehoseMonitoringOptions>;
6465
readonly lambda?: MonitoringAspectType<LambdaFunctionMonitoringOptions>;
6566
readonly openSearch?: MonitoringAspectType<OpenSearchClusterMonitoringOptions>;
67+
/**
68+
* @deprecated Use rdsCluster instead.
69+
*/
6670
readonly rds?: MonitoringAspectType<RdsClusterMonitoringOptions>;
71+
readonly rdsCluster?: MonitoringAspectType<RdsClusterMonitoringOptions>;
72+
readonly rdsInstance?: MonitoringAspectType<RdsInstanceMonitoringOptions>;
6773
readonly redshift?: MonitoringAspectType<RedshiftClusterMonitoringOptions>;
6874
readonly s3?: MonitoringAspectType<S3BucketMonitoringOptions>;
6975
readonly secretsManager?: MonitoringAspectType<SecretsManagerSecretMonitoringOptions>;

lib/facade/MonitoringAspect.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ export class MonitoringAspect implements IAspect {
6464
this.monitorKinesisFirehose(node);
6565
this.monitorLambda(node);
6666
this.monitorOpenSearch(node);
67-
this.monitorRds(node);
67+
this.monitorRdsCluster(node);
68+
this.monitorRdsInstance(node);
6869
this.monitorRedshift(node);
6970
this.monitorS3(node);
7071
this.monitorSecretsManager(node);
@@ -312,8 +313,10 @@ export class MonitoringAspect implements IAspect {
312313
}
313314
}
314315

315-
private monitorRds(node: IConstruct) {
316-
const [isEnabled, props] = this.getMonitoringDetails(this.props.rds);
316+
private monitorRdsCluster(node: IConstruct) {
317+
const [isEnabled, props] = this.getMonitoringDetails(
318+
this.props.rdsCluster ?? this.props.rds
319+
);
317320
if (isEnabled && node instanceof rds.DatabaseCluster) {
318321
this.monitoringFacade.monitorRdsCluster({
319322
cluster: node,
@@ -323,6 +326,19 @@ export class MonitoringAspect implements IAspect {
323326
}
324327
}
325328

329+
private monitorRdsInstance(node: IConstruct) {
330+
const [isEnabled, props] = this.getMonitoringDetails(
331+
this.props.rdsInstance
332+
);
333+
if (isEnabled && node instanceof rds.DatabaseInstance) {
334+
this.monitoringFacade.monitorRdsInstance({
335+
instance: node,
336+
alarmFriendlyName: node.node.path,
337+
...props,
338+
});
339+
}
340+
}
341+
326342
private monitorRedshift(node: IConstruct) {
327343
const [isEnabled, props] = this.getMonitoringDetails(this.props.redshift);
328344
if (isEnabled && node instanceof redshift.Cluster) {

lib/facade/MonitoringFacade.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ import {
9494
QueueProcessingFargateServiceMonitoringProps,
9595
RdsClusterMonitoring,
9696
RdsClusterMonitoringProps,
97+
RdsInstanceMonitoring,
98+
RdsInstanceMonitoringProps,
9799
RedshiftClusterMonitoring,
98100
RedshiftClusterMonitoringProps,
99101
S3BucketMonitoring,
@@ -681,6 +683,12 @@ export class MonitoringFacade extends MonitoringScope {
681683
return this;
682684
}
683685

686+
monitorRdsInstance(props: RdsInstanceMonitoringProps): this {
687+
const segment = new RdsInstanceMonitoring(this, props);
688+
this.addSegment(segment, props);
689+
return this;
690+
}
691+
684692
monitorRedshiftCluster(props: RedshiftClusterMonitoringProps): this {
685693
const segment = new RedshiftClusterMonitoring(this, props);
686694
this.addSegment(segment, props);
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { DimensionsMap } from "aws-cdk-lib/aws-cloudwatch";
2+
import { IDatabaseInstance } from "aws-cdk-lib/aws-rds";
3+
4+
import {
5+
LatencyType,
6+
MetricFactory,
7+
MetricStatistic,
8+
getLatencyTypeLabel,
9+
getLatencyTypeStatistic,
10+
} from "../../common";
11+
12+
const RdsNamespace = "AWS/RDS";
13+
14+
export interface RdsInstanceMetricFactoryProps {
15+
/**
16+
* database instance
17+
*/
18+
readonly instance: IDatabaseInstance;
19+
}
20+
21+
export class RdsInstanceMetricFactory {
22+
readonly instanceIdentifier: string;
23+
readonly instance: IDatabaseInstance;
24+
protected readonly metricFactory: MetricFactory;
25+
protected readonly dimensionsMap: DimensionsMap;
26+
27+
constructor(
28+
metricFactory: MetricFactory,
29+
props: RdsInstanceMetricFactoryProps
30+
) {
31+
this.metricFactory = metricFactory;
32+
this.instance = props.instance;
33+
this.instanceIdentifier = props.instance.instanceIdentifier;
34+
this.dimensionsMap = {
35+
DBInstanceIdentifier: this.instanceIdentifier,
36+
};
37+
}
38+
39+
metricTotalConnectionCount() {
40+
return this.metricFactory.adaptMetric(
41+
this.instance.metricDatabaseConnections({
42+
statistic: MetricStatistic.SUM,
43+
label: "Connections: Sum",
44+
})
45+
);
46+
}
47+
48+
metricAverageCpuUsageInPercent() {
49+
return this.metricFactory.adaptMetric(
50+
this.instance.metricCPUUtilization({
51+
statistic: MetricStatistic.AVERAGE,
52+
label: "CPU Usage",
53+
})
54+
);
55+
}
56+
57+
metricMaxFreeStorageSpace() {
58+
return this.metricFactory.adaptMetric(
59+
this.instance.metricFreeStorageSpace({
60+
statistic: MetricStatistic.MAX,
61+
label: "FreeStorageSpace: MAX",
62+
})
63+
);
64+
}
65+
66+
metricAverageFreeableMemory() {
67+
return this.metricFactory.adaptMetric(
68+
this.instance.metricFreeableMemory({
69+
statistic: MetricStatistic.AVERAGE,
70+
label: "FreeStorageSpace: Average",
71+
})
72+
);
73+
}
74+
75+
metricReadLatencyInMillis(latencyType: LatencyType) {
76+
const label = "ReadLatency " + getLatencyTypeLabel(latencyType);
77+
return this.metric(
78+
"ReadLatency",
79+
getLatencyTypeStatistic(latencyType),
80+
label
81+
);
82+
}
83+
84+
metricReadThroughput() {
85+
return this.metric(
86+
"ReadThroughput",
87+
MetricStatistic.AVERAGE,
88+
"ReadThroughput: Average"
89+
);
90+
}
91+
92+
metricReadIops() {
93+
return this.metricFactory.adaptMetric(
94+
this.instance.metricReadIOPS({
95+
statistic: MetricStatistic.AVERAGE,
96+
label: "ReadIOPS: Average",
97+
})
98+
);
99+
}
100+
101+
metricWriteLatencyInMillis(latencyType: LatencyType) {
102+
const label = "WriteLatency " + getLatencyTypeLabel(latencyType);
103+
return this.metric(
104+
"WriteLatency",
105+
getLatencyTypeStatistic(latencyType),
106+
label
107+
);
108+
}
109+
110+
metricWriteThroughput() {
111+
return this.metric(
112+
"WriteThroughput",
113+
MetricStatistic.AVERAGE,
114+
"WriteThroughput: Average"
115+
);
116+
}
117+
118+
metricWriteIops() {
119+
return this.metricFactory.adaptMetric(
120+
this.instance.metricWriteIOPS({
121+
statistic: MetricStatistic.AVERAGE,
122+
label: "WriteIOPS: Average",
123+
})
124+
);
125+
}
126+
127+
private metric(
128+
metricName: string,
129+
statistic: MetricStatistic,
130+
label: string
131+
) {
132+
return this.metricFactory.createMetric(
133+
metricName,
134+
statistic,
135+
label,
136+
this.dimensionsMap,
137+
undefined,
138+
RdsNamespace
139+
);
140+
}
141+
}

0 commit comments

Comments
 (0)