Skip to content

Commit

Permalink
feat(rds): add support for RDS Instance Monitoring (#496)
Browse files Browse the repository at this point in the history
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_
  • Loading branch information
ayvazj authored Feb 12, 2024
1 parent 0500421 commit b6da058
Show file tree
Hide file tree
Showing 11 changed files with 3,016 additions and 6 deletions.
972 changes: 969 additions & 3 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 @@ -86,6 +86,7 @@ You can browse the documentation at https://constructs.dev/packages/cdk-monitori
| 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 |
| 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 | |
| AWS RDS (`.monitorRdsCluster()`) | Query duration, connections, latency, disk/CPU usage | Connections, disk and CPU usage | |
| AWS RDS (`.monitorRdsInstance()`) | Query duration, connections, latency, disk/CPU usage | Connections, disk and CPU usage | |
| AWS Redshift (`.monitorRedshiftCluster()`) | Query duration, connections, latency, disk/CPU usage | Query duration, connections, disk and CPU usage | |
| AWS S3 Bucket (`.monitorS3Bucket()`) | Bucket size and number of objects | | |
| AWS SecretsManager (`.monitorSecretsManager()`) | Max secret count, min secret sount, secret count change | Min/max secret count or change in secret count | |
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 @@ -119,6 +119,12 @@ export class AwsConsoleUrlFactory {
return this.getAwsConsoleUrl(destinationUrl);
}

getRdsInstanceUrl(instanceId: string): string | undefined {
const region = this.awsAccountRegion;
const destinationUrl = `https://${region}.console.aws.amazon.com/rds/home?region=${region}#database:id=${instanceId};is-cluster=false;tab=monitoring`;
return this.getAwsConsoleUrl(destinationUrl);
}

getRedshiftClusterUrl(clusterId: string): string | undefined {
const region = this.awsAccountRegion;
const destinationUrl = `https://${region}.console.aws.amazon.com/redshiftv2/home?region=${region}#cluster-details?cluster=${clusterId}`;
Expand Down
6 changes: 6 additions & 0 deletions lib/facade/IMonitoringAspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
LambdaFunctionMonitoringOptions,
OpenSearchClusterMonitoringOptions,
RdsClusterMonitoringOptions,
RdsInstanceMonitoringOptions,
RedshiftClusterMonitoringOptions,
S3BucketMonitoringOptions,
SecretsManagerSecretMonitoringOptions,
Expand Down Expand Up @@ -63,7 +64,12 @@ export interface MonitoringAspectProps {
readonly kinesisFirehose?: MonitoringAspectType<KinesisFirehoseMonitoringOptions>;
readonly lambda?: MonitoringAspectType<LambdaFunctionMonitoringOptions>;
readonly openSearch?: MonitoringAspectType<OpenSearchClusterMonitoringOptions>;
/**
* @deprecated Use rdsCluster instead.
*/
readonly rds?: MonitoringAspectType<RdsClusterMonitoringOptions>;
readonly rdsCluster?: MonitoringAspectType<RdsClusterMonitoringOptions>;
readonly rdsInstance?: MonitoringAspectType<RdsInstanceMonitoringOptions>;
readonly redshift?: MonitoringAspectType<RedshiftClusterMonitoringOptions>;
readonly s3?: MonitoringAspectType<S3BucketMonitoringOptions>;
readonly secretsManager?: MonitoringAspectType<SecretsManagerSecretMonitoringOptions>;
Expand Down
22 changes: 19 additions & 3 deletions lib/facade/MonitoringAspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ export class MonitoringAspect implements IAspect {
this.monitorKinesisFirehose(node);
this.monitorLambda(node);
this.monitorOpenSearch(node);
this.monitorRds(node);
this.monitorRdsCluster(node);
this.monitorRdsInstance(node);
this.monitorRedshift(node);
this.monitorS3(node);
this.monitorSecretsManager(node);
Expand Down Expand Up @@ -312,8 +313,10 @@ export class MonitoringAspect implements IAspect {
}
}

private monitorRds(node: IConstruct) {
const [isEnabled, props] = this.getMonitoringDetails(this.props.rds);
private monitorRdsCluster(node: IConstruct) {
const [isEnabled, props] = this.getMonitoringDetails(
this.props.rdsCluster ?? this.props.rds
);
if (isEnabled && node instanceof rds.DatabaseCluster) {
this.monitoringFacade.monitorRdsCluster({
cluster: node,
Expand All @@ -323,6 +326,19 @@ export class MonitoringAspect implements IAspect {
}
}

private monitorRdsInstance(node: IConstruct) {
const [isEnabled, props] = this.getMonitoringDetails(
this.props.rdsInstance
);
if (isEnabled && node instanceof rds.DatabaseInstance) {
this.monitoringFacade.monitorRdsInstance({
instance: node,
alarmFriendlyName: node.node.path,
...props,
});
}
}

private monitorRedshift(node: IConstruct) {
const [isEnabled, props] = this.getMonitoringDetails(this.props.redshift);
if (isEnabled && node instanceof redshift.Cluster) {
Expand Down
8 changes: 8 additions & 0 deletions lib/facade/MonitoringFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ import {
QueueProcessingFargateServiceMonitoringProps,
RdsClusterMonitoring,
RdsClusterMonitoringProps,
RdsInstanceMonitoring,
RdsInstanceMonitoringProps,
RedshiftClusterMonitoring,
RedshiftClusterMonitoringProps,
S3BucketMonitoring,
Expand Down Expand Up @@ -681,6 +683,12 @@ export class MonitoringFacade extends MonitoringScope {
return this;
}

monitorRdsInstance(props: RdsInstanceMonitoringProps): this {
const segment = new RdsInstanceMonitoring(this, props);
this.addSegment(segment, props);
return this;
}

monitorRedshiftCluster(props: RedshiftClusterMonitoringProps): this {
const segment = new RedshiftClusterMonitoring(this, props);
this.addSegment(segment, props);
Expand Down
141 changes: 141 additions & 0 deletions lib/monitoring/aws-rds/RdsInstanceMetricFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { DimensionsMap } from "aws-cdk-lib/aws-cloudwatch";
import { IDatabaseInstance } from "aws-cdk-lib/aws-rds";

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

const RdsNamespace = "AWS/RDS";

export interface RdsInstanceMetricFactoryProps {
/**
* database instance
*/
readonly instance: IDatabaseInstance;
}

export class RdsInstanceMetricFactory {
readonly instanceIdentifier: string;
readonly instance: IDatabaseInstance;
protected readonly metricFactory: MetricFactory;
protected readonly dimensionsMap: DimensionsMap;

constructor(
metricFactory: MetricFactory,
props: RdsInstanceMetricFactoryProps
) {
this.metricFactory = metricFactory;
this.instance = props.instance;
this.instanceIdentifier = props.instance.instanceIdentifier;
this.dimensionsMap = {
DBInstanceIdentifier: this.instanceIdentifier,
};
}

metricTotalConnectionCount() {
return this.metricFactory.adaptMetric(
this.instance.metricDatabaseConnections({
statistic: MetricStatistic.SUM,
label: "Connections: Sum",
})
);
}

metricAverageCpuUsageInPercent() {
return this.metricFactory.adaptMetric(
this.instance.metricCPUUtilization({
statistic: MetricStatistic.AVERAGE,
label: "CPU Usage",
})
);
}

metricMaxFreeStorageSpace() {
return this.metricFactory.adaptMetric(
this.instance.metricFreeStorageSpace({
statistic: MetricStatistic.MAX,
label: "FreeStorageSpace: MAX",
})
);
}

metricAverageFreeableMemory() {
return this.metricFactory.adaptMetric(
this.instance.metricFreeableMemory({
statistic: MetricStatistic.AVERAGE,
label: "FreeStorageSpace: Average",
})
);
}

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

metricReadThroughput() {
return this.metric(
"ReadThroughput",
MetricStatistic.AVERAGE,
"ReadThroughput: Average"
);
}

metricReadIops() {
return this.metricFactory.adaptMetric(
this.instance.metricReadIOPS({
statistic: MetricStatistic.AVERAGE,
label: "ReadIOPS: Average",
})
);
}

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

metricWriteThroughput() {
return this.metric(
"WriteThroughput",
MetricStatistic.AVERAGE,
"WriteThroughput: Average"
);
}

metricWriteIops() {
return this.metricFactory.adaptMetric(
this.instance.metricWriteIOPS({
statistic: MetricStatistic.AVERAGE,
label: "WriteIOPS: Average",
})
);
}

private metric(
metricName: string,
statistic: MetricStatistic,
label: string
) {
return this.metricFactory.createMetric(
metricName,
statistic,
label,
this.dimensionsMap,
undefined,
RdsNamespace
);
}
}
Loading

0 comments on commit b6da058

Please sign in to comment.