Skip to content

Commit 1228c41

Browse files
authored
feat(kinesis): add ability to create alarms for firehose streams approaching traffic limit quota (#382)
## Note - adding the following alarm for firehose streams - IncomingBytesExceedThresholdAlarm - IncomingRecordsExceedThresholdAlarm - IncomingPutRequestsExceedThresholdAlarm - updated `metricIncomingRecordCount` name from `"Incoming"` to `"Incoming (Records)"` to be consistent with `metricIncomingPutRequests` and `metricIncomingBytes` ## Local testing executed Successfully executed the following command ``` yarn test yarn build yarn package-all ``` --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
1 parent 53f6c82 commit 1228c41

File tree

7 files changed

+767
-12
lines changed

7 files changed

+767
-12
lines changed

API.md

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

lib/common/monitoring/alarms/KinesisAlarmFactory.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ export interface RecordsThrottledThreshold extends CustomAlarmThreshold {
1414
readonly maxRecordsThrottledThreshold: number;
1515
}
1616

17+
export interface FirehoseStreamLimitThreshold extends CustomAlarmThreshold {
18+
/**
19+
* Threshold value between [0.0, 1.0) for when the alarm should be triggered.
20+
*/
21+
readonly safetyThresholdLimit: number;
22+
}
23+
1724
export interface RecordsFailedThreshold extends CustomAlarmThreshold {
1825
readonly maxRecordsFailedThreshold: number;
1926
}
@@ -90,6 +97,36 @@ export class KinesisAlarmFactory {
9097
});
9198
}
9299

100+
addFirehoseStreamExceedSafetyThresholdAlarm(
101+
metric: MetricWithAlarmSupport,
102+
metricName: string,
103+
quotaName: string,
104+
props: FirehoseStreamLimitThreshold,
105+
disambiguator?: string
106+
) {
107+
const threshold = props.safetyThresholdLimit;
108+
if (threshold < 0 || threshold >= 1) {
109+
throw new Error(
110+
`safetyThresholdLimit must be in range [0.0, 1.0) for ${metricName}ExceedThresholdAlarm.`
111+
);
112+
}
113+
114+
return this.alarmFactory.addAlarm(metric, {
115+
treatMissingData:
116+
props.treatMissingDataOverride ?? TreatMissingData.NOT_BREACHING,
117+
comparisonOperator:
118+
props.comparisonOperatorOverride ??
119+
ComparisonOperator.GREATER_THAN_THRESHOLD,
120+
...props,
121+
disambiguator,
122+
threshold,
123+
alarmNameSuffix: metricName,
124+
alarmDescription: `${metricName} exceeded ${quotaName} alarming threshold of ${threshold}`,
125+
// we will dedupe any kind of message count issue to the same ticket
126+
alarmDedupeStringSuffix: `${metricName}ExceedThresholdLimit`,
127+
});
128+
}
129+
93130
addProvisionedReadThroughputExceededAlarm(
94131
metric: MetricWithAlarmSupport,
95132
props: RecordsThrottledThreshold,

lib/monitoring/aws-kinesis/KinesisFirehoseMetricFactory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class KinesisFirehoseMetricFactory {
7373
return this.metricFactory.createMetric(
7474
"IncomingRecords",
7575
MetricStatistic.SUM,
76-
"Incoming",
76+
"Incoming (Records)",
7777
this.dimensionsMap,
7878
undefined,
7979
FirehoseNamespace

lib/monitoring/aws-kinesis/KinesisFirehoseMonitoring.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
QuarterWidth,
2222
RateAxisFromZero,
2323
RecordsThrottledThreshold,
24+
FirehoseStreamLimitThreshold,
2425
TimeAxisMillisFromZero,
2526
} from "../../common";
2627
import {
@@ -30,6 +31,18 @@ import {
3031

3132
export interface KinesisFirehoseMonitoringOptions extends BaseMonitoringProps {
3233
readonly addRecordsThrottledAlarm?: Record<string, RecordsThrottledThreshold>;
34+
readonly addIncomingBytesExceedThresholdAlarm?: Record<
35+
string,
36+
FirehoseStreamLimitThreshold
37+
>;
38+
readonly addIncomingRecordsExceedThresholdAlarm?: Record<
39+
string,
40+
FirehoseStreamLimitThreshold
41+
>;
42+
readonly addIncomingPutRequestsExceedThresholdAlarm?: Record<
43+
string,
44+
FirehoseStreamLimitThreshold
45+
>;
3346
}
3447

3548
export interface KinesisFirehoseMonitoringProps
@@ -42,6 +55,7 @@ export class KinesisFirehoseMonitoring extends Monitoring {
4255

4356
readonly kinesisAlarmFactory: KinesisAlarmFactory;
4457
readonly recordCountAnnotations: HorizontalAnnotation[];
58+
readonly incomingLimitAnnotations: HorizontalAnnotation[];
4559

4660
readonly incomingBytesMetric: MetricWithAlarmSupport;
4761
readonly incomingRecordsMetric: MetricWithAlarmSupport;
@@ -75,6 +89,7 @@ export class KinesisFirehoseMonitoring extends Monitoring {
7589
);
7690
this.kinesisAlarmFactory = new KinesisAlarmFactory(alarmFactory);
7791
this.recordCountAnnotations = [];
92+
this.incomingLimitAnnotations = [{ value: 1, label: "100% usage" }];
7893

7994
this.incomingBytesMetric = metricFactory.metricIncomingBytes();
8095
this.incomingRecordsMetric = metricFactory.metricIncomingRecordCount();
@@ -103,6 +118,51 @@ export class KinesisFirehoseMonitoring extends Monitoring {
103118
this.addAlarm(createdAlarm);
104119
}
105120

121+
for (const disambiguator in props.addIncomingBytesExceedThresholdAlarm) {
122+
const alarmProps =
123+
props.addIncomingBytesExceedThresholdAlarm[disambiguator];
124+
const createdAlarm =
125+
this.kinesisAlarmFactory.addFirehoseStreamExceedSafetyThresholdAlarm(
126+
this.incomingBytesToLimitRate,
127+
"IncomingBytes",
128+
"BytesPerSecondLimit",
129+
alarmProps,
130+
disambiguator
131+
);
132+
this.incomingLimitAnnotations.push(createdAlarm.annotation);
133+
this.addAlarm(createdAlarm);
134+
}
135+
136+
for (const disambiguator in props.addIncomingRecordsExceedThresholdAlarm) {
137+
const alarmProps =
138+
props.addIncomingRecordsExceedThresholdAlarm[disambiguator];
139+
const createdAlarm =
140+
this.kinesisAlarmFactory.addFirehoseStreamExceedSafetyThresholdAlarm(
141+
this.incomingRecordsToLimitRate,
142+
"IncomingRecords",
143+
"RecordsPerSecondLimit",
144+
alarmProps,
145+
disambiguator
146+
);
147+
this.incomingLimitAnnotations.push(createdAlarm.annotation);
148+
this.addAlarm(createdAlarm);
149+
}
150+
151+
for (const disambiguator in props.addIncomingPutRequestsExceedThresholdAlarm) {
152+
const alarmProps =
153+
props.addIncomingPutRequestsExceedThresholdAlarm[disambiguator];
154+
const createdAlarm =
155+
this.kinesisAlarmFactory.addFirehoseStreamExceedSafetyThresholdAlarm(
156+
this.incomingPutRequestsToLimitRate,
157+
"IncomingPutRequests",
158+
"PutRequestsPerSecondLimit",
159+
alarmProps,
160+
disambiguator
161+
);
162+
this.incomingLimitAnnotations.push(createdAlarm.annotation);
163+
this.addAlarm(createdAlarm);
164+
}
165+
106166
props.useCreatedAlarms?.consume(this.createdAlarms());
107167
}
108168

@@ -174,7 +234,7 @@ export class KinesisFirehoseMonitoring extends Monitoring {
174234
this.incomingPutRequestsToLimitRate.with({ label: "PutRequests" }),
175235
],
176236
leftYAxis: RateAxisFromZero,
177-
leftAnnotations: [{ value: 1, label: "100% usage" }],
237+
leftAnnotations: this.incomingLimitAnnotations,
178238
});
179239
}
180240
}

test/facade/__snapshots__/MonitoringAspect.test.ts.snap

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

test/monitoring/aws-kinesis/KinesisFirehoseMonitoring.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ test("snapshot test: all alarms", () => {
3232
maxRecordsThrottledThreshold: 5,
3333
},
3434
},
35+
addIncomingBytesExceedThresholdAlarm: {
36+
Critical: {
37+
safetyThresholdLimit: 0.6,
38+
},
39+
},
40+
addIncomingRecordsExceedThresholdAlarm: {
41+
Critical: {
42+
safetyThresholdLimit: 0.7,
43+
},
44+
},
45+
addIncomingPutRequestsExceedThresholdAlarm: {
46+
Critical: {
47+
safetyThresholdLimit: 0.8,
48+
},
49+
},
3550
useCreatedAlarms: {
3651
consume(alarms) {
3752
numAlarmsCreated = alarms.length;
@@ -40,6 +55,46 @@ test("snapshot test: all alarms", () => {
4055
});
4156

4257
addMonitoringDashboardsToStack(stack, monitoring);
43-
expect(numAlarmsCreated).toStrictEqual(1);
58+
expect(numAlarmsCreated).toStrictEqual(4);
4459
expect(Template.fromStack(stack)).toMatchSnapshot();
4560
});
61+
62+
test("test: validation error if incoming traffic usage alarm threshold equal to 1", () => {
63+
const stack = new Stack();
64+
65+
const scope = new TestMonitoringScope(stack, "Scope");
66+
67+
expect(() => {
68+
new KinesisFirehoseMonitoring(scope, {
69+
deliveryStreamName:
70+
"my-firehose-delivery-stream-with-unexpected-threshold",
71+
addIncomingBytesExceedThresholdAlarm: {
72+
Critical: {
73+
safetyThresholdLimit: 1.0,
74+
},
75+
},
76+
});
77+
}).toThrow(
78+
`safetyThresholdLimit must be in range [0.0, 1.0) for IncomingBytesExceedThresholdAlarm.`
79+
);
80+
});
81+
82+
test("test: validation error if incoming traffic usage alarm threshold less than 0", () => {
83+
const stack = new Stack();
84+
85+
const scope = new TestMonitoringScope(stack, "Scope");
86+
87+
expect(() => {
88+
new KinesisFirehoseMonitoring(scope, {
89+
deliveryStreamName:
90+
"my-firehose-delivery-stream-with-unexpected-threshold",
91+
addIncomingRecordsExceedThresholdAlarm: {
92+
Critical: {
93+
safetyThresholdLimit: -0.1,
94+
},
95+
},
96+
});
97+
}).toThrow(
98+
`safetyThresholdLimit must be in range [0.0, 1.0) for IncomingRecordsExceedThresholdAlarm.`
99+
);
100+
});

0 commit comments

Comments
 (0)