Skip to content

Commit 4c1fe9d

Browse files
authored
feat(redshift, rds): alarm on min/max connection count and long query duration (#251)
Adds alarm for min/max connection count and for long query duration. Closes #248 Closes #249 Closes #232 --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
1 parent b21db69 commit 4c1fe9d

File tree

10 files changed

+1280
-47
lines changed

10 files changed

+1280
-47
lines changed

API.md

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

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ You can browse the documentation at https://constructs.dev/packages/cdk-monitori
9292
| AWS Lambda (`.monitorLambdaFunction()`) | Latency, errors, iterator max age | Latency, errors, throttles, iterator max age | Optional Lambda Insights metrics (opt-in) support |
9393
| 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 |
9494
| 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 | |
95-
| AWS RDS (`.monitorRdsCluster()`) | Query duration, connections, latency, disk/CPU usage | Disk and CPU usage | |
96-
| AWS Redshift (`.monitorRedshiftCluster()`) | Query duration, connections, latency, disk/CPU usage | Disk and CPU usage | |
95+
| AWS RDS (`.monitorRdsCluster()`) | Query duration, connections, latency, disk/CPU usage | Connections, disk and CPU usage | |
96+
| AWS Redshift (`.monitorRedshiftCluster()`) | Query duration, connections, latency, disk/CPU usage | Query duration, connections, disk and CPU usage | |
9797
| AWS S3 Bucket (`.monitorS3Bucket()`) | Bucket size and number of objects | | |
9898
| AWS SecretsManager (`.monitorSecretsManagerSecret()`) | Days since last rotation | Days since last change or rotation | |
9999
| AWS SNS Topic (`.monitorSnsTopic()`) | Message count, size, failed notifications | Failed notifications, min/max published messages | |
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {
2+
ComparisonOperator,
3+
TreatMissingData,
4+
} from "aws-cdk-lib/aws-cloudwatch";
5+
6+
import { AlarmFactory, CustomAlarmThreshold } from "../../alarm";
7+
import { MetricWithAlarmSupport } from "../../metric";
8+
9+
export interface LowConnectionCountThreshold extends CustomAlarmThreshold {
10+
readonly minConnectionCount: number;
11+
}
12+
13+
export interface HighConnectionCountThreshold extends CustomAlarmThreshold {
14+
readonly maxConnectionCount: number;
15+
}
16+
17+
export class ConnectionAlarmFactory {
18+
protected readonly alarmFactory: AlarmFactory;
19+
20+
constructor(alarmFactory: AlarmFactory) {
21+
this.alarmFactory = alarmFactory;
22+
}
23+
24+
addMinConnectionCountAlarm(
25+
metric: MetricWithAlarmSupport,
26+
props: LowConnectionCountThreshold,
27+
disambiguator?: string
28+
) {
29+
return this.alarmFactory.addAlarm(metric, {
30+
treatMissingData:
31+
props.treatMissingDataOverride ?? TreatMissingData.MISSING,
32+
comparisonOperator:
33+
props.comparisonOperatorOverride ??
34+
ComparisonOperator.LESS_THAN_THRESHOLD,
35+
...props,
36+
disambiguator,
37+
threshold: props.minConnectionCount,
38+
alarmNameSuffix: "Connection-Count-Low",
39+
alarmDescription: `Number of connections is too low.`,
40+
});
41+
}
42+
43+
addMaxConnectionCountAlarm(
44+
metric: MetricWithAlarmSupport,
45+
props: HighConnectionCountThreshold,
46+
disambiguator?: string
47+
) {
48+
return this.alarmFactory.addAlarm(metric, {
49+
treatMissingData:
50+
props.treatMissingDataOverride ?? TreatMissingData.MISSING,
51+
comparisonOperator:
52+
props.comparisonOperatorOverride ??
53+
ComparisonOperator.GREATER_THAN_THRESHOLD,
54+
...props,
55+
disambiguator,
56+
threshold: props.maxConnectionCount,
57+
alarmNameSuffix: "Connection-Count-High",
58+
alarmDescription: `Number of connections is too high.`,
59+
});
60+
}
61+
}

lib/common/monitoring/alarms/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from "./AgeAlarmFactory";
22
export * from "./AnomalyDetectingAlarmFactory";
33
export * from "./CustomAlarmFactory";
4+
export * from "./ConnectionAlarmFactory";
45
export * from "./DynamoAlarmFactory";
56
export * from "./ElastiCacheAlarmFactory";
67
export * from "./ErrorAlarmFactory";

lib/monitoring/aws-rds/RdsClusterMonitoring.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import {
66

77
import {
88
BaseMonitoringProps,
9+
ConnectionAlarmFactory,
910
CountAxisFromZero,
1011
DefaultGraphWidgetHeight,
1112
DefaultSummaryWidgetHeight,
1213
HalfWidth,
14+
HighConnectionCountThreshold,
15+
LowConnectionCountThreshold,
1316
MetricWithAlarmSupport,
1417
Monitoring,
1518
MonitoringScope,
@@ -32,6 +35,14 @@ import {
3235
export interface RdsClusterMonitoringOptions extends BaseMonitoringProps {
3336
readonly addDiskSpaceUsageAlarm?: Record<string, UsageThreshold>;
3437
readonly addCpuUsageAlarm?: Record<string, UsageThreshold>;
38+
readonly addMinConnectionCountAlarm?: Record<
39+
string,
40+
LowConnectionCountThreshold
41+
>;
42+
readonly addMaxConnectionCountAlarm?: Record<
43+
string,
44+
HighConnectionCountThreshold
45+
>;
3546
}
3647

3748
export interface RdsClusterMonitoringProps
@@ -43,7 +54,9 @@ export class RdsClusterMonitoring extends Monitoring {
4354
readonly url?: string;
4455

4556
readonly usageAlarmFactory: UsageAlarmFactory;
57+
readonly connectionAlarmFactory: ConnectionAlarmFactory;
4658
readonly usageAnnotations: HorizontalAnnotation[];
59+
readonly connectionAnnotations: HorizontalAnnotation[];
4760

4861
readonly connectionsMetric: MetricWithAlarmSupport;
4962
readonly diskSpaceUsageMetric: MetricWithAlarmSupport;
@@ -83,7 +96,10 @@ export class RdsClusterMonitoring extends Monitoring {
8396
namingStrategy.resolveAlarmFriendlyName()
8497
);
8598
this.usageAlarmFactory = new UsageAlarmFactory(alarmFactory);
99+
this.connectionAlarmFactory = new ConnectionAlarmFactory(alarmFactory);
100+
86101
this.usageAnnotations = [];
102+
this.connectionAnnotations = [];
87103

88104
for (const disambiguator in props.addDiskSpaceUsageAlarm) {
89105
const alarmProps = props.addDiskSpaceUsageAlarm[disambiguator];
@@ -107,6 +123,30 @@ export class RdsClusterMonitoring extends Monitoring {
107123
this.addAlarm(createdAlarm);
108124
}
109125

126+
for (const disambiguator in props.addMinConnectionCountAlarm) {
127+
const alarmProps = props.addMinConnectionCountAlarm[disambiguator];
128+
const createdAlarm =
129+
this.connectionAlarmFactory.addMinConnectionCountAlarm(
130+
this.connectionsMetric,
131+
alarmProps,
132+
disambiguator
133+
);
134+
this.connectionAnnotations.push(createdAlarm.annotation);
135+
this.addAlarm(createdAlarm);
136+
}
137+
138+
for (const disambiguator in props.addMaxConnectionCountAlarm) {
139+
const alarmProps = props.addMaxConnectionCountAlarm[disambiguator];
140+
const createdAlarm =
141+
this.connectionAlarmFactory.addMaxConnectionCountAlarm(
142+
this.connectionsMetric,
143+
alarmProps,
144+
disambiguator
145+
);
146+
this.connectionAnnotations.push(createdAlarm.annotation);
147+
this.addAlarm(createdAlarm);
148+
}
149+
110150
props.useCreatedAlarms?.consume(this.createdAlarms());
111151
}
112152

@@ -154,6 +194,7 @@ export class RdsClusterMonitoring extends Monitoring {
154194
title: "Connections",
155195
left: [this.connectionsMetric],
156196
leftYAxis: CountAxisFromZero,
197+
leftAnnotations: this.connectionAnnotations,
157198
});
158199
}
159200

lib/monitoring/aws-redshift/RedshiftClusterMonitoring.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ import {
88
BaseMonitoringProps,
99
BooleanAxisFromZeroToOne,
1010
CountAxisFromZero,
11+
ConnectionAlarmFactory,
1112
DefaultGraphWidgetHeight,
1213
DefaultSummaryWidgetHeight,
14+
DurationThreshold,
1315
HalfQuarterWidth,
16+
HighConnectionCountThreshold,
17+
LatencyAlarmFactory,
18+
LatencyType,
19+
LowConnectionCountThreshold,
1420
MetricWithAlarmSupport,
1521
Monitoring,
1622
MonitoringScope,
@@ -33,6 +39,15 @@ import {
3339
export interface RedshiftClusterMonitoringOptions extends BaseMonitoringProps {
3440
readonly addDiskSpaceUsageAlarm?: Record<string, UsageThreshold>;
3541
readonly addCpuUsageAlarm?: Record<string, UsageThreshold>;
42+
readonly addMaxLongQueryDurationAlarm?: Record<string, DurationThreshold>;
43+
readonly addMinConnectionCountAlarm?: Record<
44+
string,
45+
LowConnectionCountThreshold
46+
>;
47+
readonly addMaxConnectionCountAlarm?: Record<
48+
string,
49+
HighConnectionCountThreshold
50+
>;
3651
}
3752

3853
export interface RedshiftClusterMonitoringProps
@@ -44,6 +59,10 @@ export class RedshiftClusterMonitoring extends Monitoring {
4459
readonly url?: string;
4560

4661
readonly usageAlarmFactory: UsageAlarmFactory;
62+
readonly latencyAlarmFactory: LatencyAlarmFactory;
63+
readonly connectionAlarmFactory: ConnectionAlarmFactory;
64+
readonly queryDurationAnnotations: HorizontalAnnotation[];
65+
readonly connectionAnnotations: HorizontalAnnotation[];
4766
readonly usageAnnotations: HorizontalAnnotation[];
4867

4968
readonly connectionsMetric: MetricWithAlarmSupport;
@@ -72,6 +91,10 @@ export class RedshiftClusterMonitoring extends Monitoring {
7291
namingStrategy.resolveAlarmFriendlyName()
7392
);
7493
this.usageAlarmFactory = new UsageAlarmFactory(alarmFactory);
94+
this.latencyAlarmFactory = new LatencyAlarmFactory(alarmFactory);
95+
this.connectionAlarmFactory = new ConnectionAlarmFactory(alarmFactory);
96+
this.queryDurationAnnotations = [];
97+
this.connectionAnnotations = [];
7598
this.usageAnnotations = [];
7699

77100
const metricFactory = new RedshiftClusterMetricFactory(
@@ -114,6 +137,42 @@ export class RedshiftClusterMonitoring extends Monitoring {
114137
this.addAlarm(createdAlarm);
115138
}
116139

140+
for (const disambiguator in props.addMaxLongQueryDurationAlarm) {
141+
const alarmProps = props.addMaxLongQueryDurationAlarm[disambiguator];
142+
const createdAlarm = this.latencyAlarmFactory.addDurationAlarm(
143+
this.longQueryDurationMetric,
144+
LatencyType.P90,
145+
alarmProps,
146+
disambiguator
147+
);
148+
this.queryDurationAnnotations.push(createdAlarm.annotation);
149+
this.addAlarm(createdAlarm);
150+
}
151+
152+
for (const disambiguator in props.addMinConnectionCountAlarm) {
153+
const alarmProps = props.addMinConnectionCountAlarm[disambiguator];
154+
const createdAlarm =
155+
this.connectionAlarmFactory.addMinConnectionCountAlarm(
156+
this.connectionsMetric,
157+
alarmProps,
158+
disambiguator
159+
);
160+
this.connectionAnnotations.push(createdAlarm.annotation);
161+
this.addAlarm(createdAlarm);
162+
}
163+
164+
for (const disambiguator in props.addMaxConnectionCountAlarm) {
165+
const alarmProps = props.addMaxConnectionCountAlarm[disambiguator];
166+
const createdAlarm =
167+
this.connectionAlarmFactory.addMaxConnectionCountAlarm(
168+
this.connectionsMetric,
169+
alarmProps,
170+
disambiguator
171+
);
172+
this.connectionAnnotations.push(createdAlarm.annotation);
173+
this.addAlarm(createdAlarm);
174+
}
175+
117176
props.useCreatedAlarms?.consume(this.createdAlarms());
118177
}
119178

@@ -163,6 +222,7 @@ export class RedshiftClusterMonitoring extends Monitoring {
163222
title: "Connections",
164223
left: [this.connectionsMetric],
165224
leftYAxis: CountAxisFromZero,
225+
leftAnnotations: this.connectionAnnotations,
166226
});
167227
}
168228

@@ -177,6 +237,7 @@ export class RedshiftClusterMonitoring extends Monitoring {
177237
this.longQueryDurationMetric,
178238
],
179239
leftYAxis: TimeAxisMillisFromZero,
240+
leftAnnotations: this.queryDurationAnnotations,
180241
});
181242
}
182243

test/monitoring/aws-rds/RdsClusterMonitoring.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ test("snapshot test: all alarms", () => {
3838
maxUsagePercent: 70,
3939
},
4040
},
41+
addMinConnectionCountAlarm: {
42+
Warning: {
43+
minConnectionCount: 1,
44+
},
45+
},
46+
addMaxConnectionCountAlarm: {
47+
Warning: {
48+
maxConnectionCount: 100,
49+
},
50+
},
4151
useCreatedAlarms: {
4252
consume(alarms) {
4353
numAlarmsCreated = alarms.length;
@@ -46,6 +56,6 @@ test("snapshot test: all alarms", () => {
4656
});
4757

4858
addMonitoringDashboardsToStack(stack, monitoring);
49-
expect(numAlarmsCreated).toStrictEqual(2);
59+
expect(numAlarmsCreated).toStrictEqual(4);
5060
expect(Template.fromStack(stack)).toMatchSnapshot();
5161
});

0 commit comments

Comments
 (0)