Skip to content

Commit be6c849

Browse files
authored
feat(kinesis): add more alarms (#105)
- adds alarms of Provisioned Capacity exceeded on Read/Write - adds alarm of Throttled Records for Kinesis Data Stream - improves unit tests (adds dashboard code to snapshots) Closes #53 --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
1 parent b6b28a4 commit be6c849

File tree

11 files changed

+822
-24
lines changed

11 files changed

+822
-24
lines changed

API.md

Lines changed: 187 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ You can also browse the documentation at https://constructs.dev/packages/cdk-mon
8282
| AWS ElastiCache (`.monitorElastiCacheCluster()`) | CPU/memory usage, evictions and connections | | |
8383
| AWS Glue (`.monitorGlueJob()`) | Traffic, job status, memory/CPU usage | | |
8484
| AWS Kinesis Data Analytics (`.monitorKinesisDataAnalytics`) | Up/Downtime, CPU/memory usage, KPU usage, checkpoint metrics, and garbage collection metrics | Downtime | |
85-
| AWS Kinesis Data Stream (`.monitorKinesisDataStream()`) | Put/Get/Incoming Record/s and Throttling | Iterator max age | |
86-
| AWS Kinesis Firehose (`.monitorKinesisFirehose()`) | Number of records, requests, latency | | |
85+
| AWS Kinesis Data Stream (`.monitorKinesisDataStream()`) | Put/Get/Incoming Record/s and Throttling | Throttling, iterator max age | |
86+
| AWS Kinesis Firehose (`.monitorKinesisFirehose()`) | Number of records, requests, latency, throttling | | |
8787
| AWS Lambda (`.monitorLambdaFunction()`) | Latency, errors, iterator max age | Latency, errors, throttles, iterator max age | Optional Lambda Insights metrics (opt-in) support |
8888
| 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 |
8989
| AWS OpenSearch/Elasticsearch (`.monitorOpenSearchCluster()`, `.monitorElasticsearchCluster()`) | Indexing and search latency, disk/memory/CPU usage | Indexing and search latency, disk/memory/CPU usage, cluster status | |

lib/common/monitoring/alarms/KinesisAlarmFactory.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,51 @@ export class KinesisAlarmFactory {
8282
ComparisonOperator.GREATER_THAN_THRESHOLD,
8383
...props,
8484
disambiguator,
85-
threshold: threshold,
85+
threshold,
8686
alarmNameSuffix: "PutRecordsFailed",
8787
alarmDescription: `Number of failed PutRecords exceeded threshold of ${threshold}`,
8888
// we will dedupe any kind of message count issue to the same ticket
8989
alarmDedupeStringSuffix: "PutRecordsFailed",
9090
});
9191
}
92+
93+
addProvisionedReadThroughputExceededAlarm(
94+
metric: MetricWithAlarmSupport,
95+
props: RecordsThrottledThreshold,
96+
disambiguator: string
97+
) {
98+
const threshold = props.maxRecordsThrottledThreshold;
99+
return this.alarmFactory.addAlarm(metric, {
100+
treatMissingData:
101+
props.treatMissingDataOverride ?? TreatMissingData.NOT_BREACHING,
102+
comparisonOperator:
103+
props.comparisonOperatorOverride ??
104+
ComparisonOperator.GREATER_THAN_THRESHOLD,
105+
...props,
106+
disambiguator,
107+
threshold,
108+
alarmNameSuffix: "ReadThroughputExceeded",
109+
alarmDescription: `Number of records resulting in read throughput capacity throttling reached the threshold of ${threshold}.`,
110+
});
111+
}
112+
113+
addProvisionedWriteThroughputExceededAlarm(
114+
metric: MetricWithAlarmSupport,
115+
props: RecordsThrottledThreshold,
116+
disambiguator: string
117+
) {
118+
const threshold = props.maxRecordsThrottledThreshold;
119+
return this.alarmFactory.addAlarm(metric, {
120+
treatMissingData:
121+
props.treatMissingDataOverride ?? TreatMissingData.NOT_BREACHING,
122+
comparisonOperator:
123+
props.comparisonOperatorOverride ??
124+
ComparisonOperator.GREATER_THAN_THRESHOLD,
125+
...props,
126+
disambiguator,
127+
threshold,
128+
alarmNameSuffix: "WriteThroughputExceeded",
129+
alarmDescription: `Number of records resulting in write throughput capacity throttling reached the threshold of ${threshold}.`,
130+
});
131+
}
92132
}

lib/monitoring/aws-kinesis/KinesisDataStreamMonitoring.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ export interface KinesisDataStreamMonitoringOptions
4040
RecordsThrottledThreshold
4141
>;
4242
readonly addPutRecordsFailedAlarm?: Record<string, RecordsFailedThreshold>;
43+
readonly addThrottledRecordsAlarm?: Record<string, RecordsThrottledThreshold>;
44+
readonly addReadProvisionedThroughputExceededAlarm?: Record<
45+
string,
46+
RecordsThrottledThreshold
47+
>;
48+
readonly addWriteProvisionedThroughputExceededAlarm?: Record<
49+
string,
50+
RecordsThrottledThreshold
51+
>;
4352
}
4453

4554
export interface KinesisDataStreamMonitoringProps
@@ -52,6 +61,7 @@ export class KinesisDataStreamMonitoring extends Monitoring {
5261

5362
protected readonly kinesisAlarmFactory: KinesisAlarmFactory;
5463
protected readonly ageAnnotations: HorizontalAnnotation[];
64+
protected readonly provisionedCapacityAnnotations: HorizontalAnnotation[];
5565
protected readonly recordCountAnnotations: HorizontalAnnotation[];
5666

5767
protected readonly metricGetRecordSumBytes: MetricWithAlarmSupport;
@@ -90,6 +100,7 @@ export class KinesisDataStreamMonitoring extends Monitoring {
90100
namingStrategy.resolveAlarmFriendlyName()
91101
);
92102
this.kinesisAlarmFactory = new KinesisAlarmFactory(alarmFactory);
103+
this.provisionedCapacityAnnotations = [];
93104
this.ageAnnotations = [];
94105
this.recordCountAnnotations = [];
95106

@@ -162,6 +173,30 @@ export class KinesisDataStreamMonitoring extends Monitoring {
162173
this.recordCountAnnotations.push(createdAlarm.annotation);
163174
this.addAlarm(createdAlarm);
164175
}
176+
for (const disambiguator in props.addReadProvisionedThroughputExceededAlarm) {
177+
const alarmProps =
178+
props.addReadProvisionedThroughputExceededAlarm[disambiguator];
179+
const createdAlarm =
180+
this.kinesisAlarmFactory.addProvisionedReadThroughputExceededAlarm(
181+
this.readProvisionedThroughputExceededMetric,
182+
alarmProps,
183+
disambiguator
184+
);
185+
this.provisionedCapacityAnnotations.push(createdAlarm.annotation);
186+
this.addAlarm(createdAlarm);
187+
}
188+
for (const disambiguator in props.addWriteProvisionedThroughputExceededAlarm) {
189+
const alarmProps =
190+
props.addWriteProvisionedThroughputExceededAlarm[disambiguator];
191+
const createdAlarm =
192+
this.kinesisAlarmFactory.addProvisionedWriteThroughputExceededAlarm(
193+
this.writeProvisionedThroughputExceededMetric,
194+
alarmProps,
195+
disambiguator
196+
);
197+
this.provisionedCapacityAnnotations.push(createdAlarm.annotation);
198+
this.addAlarm(createdAlarm);
199+
}
165200

166201
props.useCreatedAlarms?.consume(this.createdAlarms());
167202
}
@@ -173,7 +208,7 @@ export class KinesisDataStreamMonitoring extends Monitoring {
173208
this.createIncomingDataWidget(QuarterWidth, DefaultSummaryWidgetHeight),
174209
this.createIteratorAgeWidget(QuarterWidth, DefaultSummaryWidgetHeight),
175210
this.createLatencyWidget(QuarterWidth, DefaultSummaryWidgetHeight),
176-
this.createThrottleWidget(QuarterWidth, DefaultSummaryWidgetHeight)
211+
this.createCapacityWidget(QuarterWidth, DefaultSummaryWidgetHeight)
177212
),
178213
];
179214
}
@@ -185,7 +220,7 @@ export class KinesisDataStreamMonitoring extends Monitoring {
185220
this.createIncomingDataWidget(QuarterWidth, DefaultGraphWidgetHeight),
186221
this.createIteratorAgeWidget(QuarterWidth, DefaultGraphWidgetHeight),
187222
this.createLatencyWidget(QuarterWidth, DefaultGraphWidgetHeight),
188-
this.createThrottleWidget(QuarterWidth, DefaultGraphWidgetHeight)
223+
this.createCapacityWidget(QuarterWidth, DefaultGraphWidgetHeight)
189224
),
190225
this.createFirstAdditionalRow(),
191226
this.createSecondAdditionalRow(),
@@ -248,16 +283,17 @@ export class KinesisDataStreamMonitoring extends Monitoring {
248283
});
249284
}
250285

251-
protected createThrottleWidget(width: number, height: number) {
286+
protected createCapacityWidget(width: number, height: number) {
252287
return new GraphWidget({
253288
width,
254289
height,
255-
title: "Throttles",
290+
title: "Provisioned Capacity Exceeded",
256291
left: [
257292
this.readProvisionedThroughputExceededMetric,
258293
this.writeProvisionedThroughputExceededMetric,
259294
],
260295
leftYAxis: TimeAxisMillisFromZero,
296+
leftAnnotations: this.provisionedCapacityAnnotations,
261297
});
262298
}
263299

lib/monitoring/aws-kinesis/KinesisFirehoseMetricFactory.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ export class KinesisFirehoseMetricFactory {
5858
);
5959
}
6060

61+
metricIncomingPutRequests() {
62+
return this.metricFactory.createMetric(
63+
"IncomingPutRequests",
64+
MetricStatistic.SUM,
65+
"Incoming (PutRequest)",
66+
this.dimensionsMap,
67+
undefined,
68+
FirehoseNamespace
69+
);
70+
}
71+
6172
metricIncomingRecordCount() {
6273
return this.metricFactory.createMetric(
6374
"IncomingRecords",
@@ -101,4 +112,70 @@ export class KinesisFirehoseMetricFactory {
101112
FirehoseNamespace
102113
);
103114
}
115+
116+
metricIncomingBytesToLimitRate() {
117+
return this.metricFactory.createMetricMath(
118+
"(bytes_in / PERIOD(bytes_in)) / bytes_max",
119+
{
120+
bytes_in: this.metricIncomingBytes(),
121+
bytes_max: this.metricBytesPerSecondLimit(),
122+
},
123+
"Incoming Bytes / Limit"
124+
);
125+
}
126+
127+
metricIncomingRecordsToLimitRate() {
128+
return this.metricFactory.createMetricMath(
129+
"(records_in / PERIOD(records_in)) / records_max",
130+
{
131+
records_in: this.metricIncomingRecordCount(),
132+
records_max: this.metricRecordsPerSecondLimit(),
133+
},
134+
"Incoming Records / Limit"
135+
);
136+
}
137+
138+
metricIncomingPutRequestsToLimitRate() {
139+
return this.metricFactory.createMetricMath(
140+
"(requests_in / PERIOD(requests_in)) / requests_max",
141+
{
142+
requests_in: this.metricIncomingPutRequests(),
143+
requests_max: this.metricPutRequestsPerSecondLimit(),
144+
},
145+
"Incoming PutRequests / Limit"
146+
);
147+
}
148+
149+
metricBytesPerSecondLimit() {
150+
return this.metricFactory.createMetric(
151+
"BytesPerSecondLimit",
152+
MetricStatistic.AVERAGE,
153+
"Incoming Bytes/s Limit",
154+
undefined,
155+
undefined,
156+
FirehoseNamespace
157+
);
158+
}
159+
160+
metricRecordsPerSecondLimit() {
161+
return this.metricFactory.createMetric(
162+
"RecordsPerSecondLimit",
163+
MetricStatistic.AVERAGE,
164+
"Records/s Limit",
165+
undefined,
166+
undefined,
167+
FirehoseNamespace
168+
);
169+
}
170+
171+
metricPutRequestsPerSecondLimit() {
172+
return this.metricFactory.createMetric(
173+
"PutRequestsPerSecondLimit",
174+
MetricStatistic.AVERAGE,
175+
"PutRequests/s Limit",
176+
undefined,
177+
undefined,
178+
FirehoseNamespace
179+
);
180+
}
104181
}

lib/monitoring/aws-kinesis/KinesisFirehoseMonitoring.ts

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
1-
import { GraphWidget, IWidget } from "aws-cdk-lib/aws-cloudwatch";
1+
import {
2+
GraphWidget,
3+
HorizontalAnnotation,
4+
IWidget,
5+
} from "aws-cdk-lib/aws-cloudwatch";
26

37
import {
48
BaseMonitoringProps,
59
CountAxisFromZero,
610
DefaultGraphWidgetHeight,
711
DefaultSummaryWidgetHeight,
812
HalfWidth,
13+
KinesisAlarmFactory,
914
MetricWithAlarmSupport,
1015
Monitoring,
1116
MonitoringScope,
12-
ThirdWidth,
17+
QuarterWidth,
18+
RateAxisFromZero,
19+
RecordsThrottledThreshold,
1320
TimeAxisMillisFromZero,
1421
} from "../../common";
1522
import {
@@ -21,7 +28,9 @@ import {
2128
KinesisFirehoseMetricFactoryProps,
2229
} from "./KinesisFirehoseMetricFactory";
2330

24-
export interface KinesisFirehoseMonitoringOptions extends BaseMonitoringProps {}
31+
export interface KinesisFirehoseMonitoringOptions extends BaseMonitoringProps {
32+
readonly addRecordsThrottledAlarm?: Record<string, RecordsThrottledThreshold>;
33+
}
2534

2635
export interface KinesisFirehoseMonitoringProps
2736
extends KinesisFirehoseMetricFactoryProps,
@@ -31,13 +40,19 @@ export class KinesisFirehoseMonitoring extends Monitoring {
3140
protected readonly title: string;
3241
protected readonly streamUrl?: string;
3342

43+
protected readonly kinesisAlarmFactory: KinesisAlarmFactory;
44+
protected readonly recordCountAnnotations: HorizontalAnnotation[];
45+
3446
protected readonly incomingBytesMetric: MetricWithAlarmSupport;
3547
protected readonly incomingRecordsMetric: MetricWithAlarmSupport;
3648
protected readonly throttledRecordsMetric: MetricWithAlarmSupport;
3749
protected readonly successfulConversionMetric: MetricWithAlarmSupport;
3850
protected readonly failedConversionMetric: MetricWithAlarmSupport;
3951
protected readonly putRecordLatency: MetricWithAlarmSupport;
4052
protected readonly putRecordBatchLatency: MetricWithAlarmSupport;
53+
protected readonly incomingBytesToLimitRate: MetricWithAlarmSupport;
54+
protected readonly incomingRecordsToLimitRate: MetricWithAlarmSupport;
55+
protected readonly incomingPutRequestsToLimitRate: MetricWithAlarmSupport;
4156

4257
constructor(scope: MonitoringScope, props: KinesisFirehoseMonitoringProps) {
4358
super(scope);
@@ -55,6 +70,12 @@ export class KinesisFirehoseMonitoring extends Monitoring {
5570
scope.createMetricFactory(),
5671
props
5772
);
73+
const alarmFactory = this.createAlarmFactory(
74+
namingStrategy.resolveAlarmFriendlyName()
75+
);
76+
this.kinesisAlarmFactory = new KinesisAlarmFactory(alarmFactory);
77+
this.recordCountAnnotations = [];
78+
5879
this.incomingBytesMetric = metricFactory.metricIncomingBytes();
5980
this.incomingRecordsMetric = metricFactory.metricIncomingRecordCount();
6081
this.throttledRecordsMetric = metricFactory.metricThrottledRecordCount();
@@ -64,6 +85,25 @@ export class KinesisFirehoseMonitoring extends Monitoring {
6485
this.putRecordLatency = metricFactory.metricPutRecordLatencyP90InMillis();
6586
this.putRecordBatchLatency =
6687
metricFactory.metricPutRecordBatchLatencyP90InMillis();
88+
this.incomingBytesToLimitRate =
89+
metricFactory.metricIncomingBytesToLimitRate();
90+
this.incomingRecordsToLimitRate =
91+
metricFactory.metricIncomingRecordsToLimitRate();
92+
this.incomingPutRequestsToLimitRate =
93+
metricFactory.metricIncomingPutRequestsToLimitRate();
94+
95+
for (const disambiguator in props.addRecordsThrottledAlarm) {
96+
const alarmProps = props.addRecordsThrottledAlarm[disambiguator];
97+
const createdAlarm = this.kinesisAlarmFactory.addPutRecordsThrottledAlarm(
98+
this.throttledRecordsMetric,
99+
alarmProps,
100+
disambiguator
101+
);
102+
this.recordCountAnnotations.push(createdAlarm.annotation);
103+
this.addAlarm(createdAlarm);
104+
}
105+
106+
props.useCreatedAlarms?.consume(this.createdAlarms());
67107
}
68108

69109
summaryWidgets(): IWidget[] {
@@ -77,9 +117,10 @@ export class KinesisFirehoseMonitoring extends Monitoring {
77117
widgets(): IWidget[] {
78118
return [
79119
this.createTitleWidget(),
80-
this.createIncomingRecordWidget(ThirdWidth, DefaultGraphWidgetHeight),
81-
this.createLatencyWidget(ThirdWidth, DefaultGraphWidgetHeight),
82-
this.createConversionWidget(ThirdWidth, DefaultGraphWidgetHeight),
120+
this.createIncomingRecordWidget(QuarterWidth, DefaultGraphWidgetHeight),
121+
this.createLatencyWidget(QuarterWidth, DefaultGraphWidgetHeight),
122+
this.createConversionWidget(QuarterWidth, DefaultGraphWidgetHeight),
123+
this.createLimitWidget(QuarterWidth, DefaultGraphWidgetHeight),
83124
];
84125
}
85126

@@ -98,6 +139,7 @@ export class KinesisFirehoseMonitoring extends Monitoring {
98139
title: "Records",
99140
left: [this.incomingRecordsMetric, this.throttledRecordsMetric],
100141
leftYAxis: CountAxisFromZero,
142+
leftAnnotations: this.recordCountAnnotations,
101143
});
102144
}
103145

@@ -120,4 +162,19 @@ export class KinesisFirehoseMonitoring extends Monitoring {
120162
leftYAxis: CountAxisFromZero,
121163
});
122164
}
165+
166+
protected createLimitWidget(width: number, height: number) {
167+
return new GraphWidget({
168+
width,
169+
height,
170+
title: "Limits (rate)",
171+
left: [
172+
this.incomingBytesToLimitRate.with({ label: "Bytes" }),
173+
this.incomingRecordsToLimitRate.with({ label: "Records" }),
174+
this.incomingPutRequestsToLimitRate.with({ label: "PutRequests" }),
175+
],
176+
leftYAxis: RateAxisFromZero,
177+
leftAnnotations: [{ value: 1, label: "100% usage" }],
178+
});
179+
}
123180
}

0 commit comments

Comments
 (0)