Skip to content

Commit b0cc9b3

Browse files
author
Eugene Cheung
authored
feat(lambda): add isIterator prop (#569)
We've received feedback that the default IteratorAge widget is largely useless for any Lambda function that isn't actually a stream handler. Function doesn't provide a way to really tell if that's the case, so we ended up with an explicit prop. We default to true for backwards compat. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
1 parent 716a99e commit b0cc9b3

File tree

4 files changed

+520
-9
lines changed

4 files changed

+520
-9
lines changed

API.md

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

lib/monitoring/aws-lambda/LambdaFunctionMonitoring.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
ErrorCountThreshold,
2424
ErrorRateThreshold,
2525
ErrorType,
26+
HalfWidth,
2627
HighTpsThreshold,
2728
LatencyAlarmFactory,
2829
LatencyThreshold,
@@ -53,6 +54,14 @@ import {
5354
} from "../../dashboard";
5455

5556
export interface LambdaFunctionMonitoringOptions extends BaseMonitoringProps {
57+
/**
58+
* Indicates that the Lambda function handles an event source (e.g. DynamoDB event stream).
59+
* This impacts what widgets are shown, as well as validates the ability to use addMaxIteratorAgeAlarm.
60+
*
61+
* @default - true
62+
*/
63+
readonly isIterator?: boolean;
64+
5665
readonly addLatencyP50Alarm?: Record<string, LatencyThreshold>;
5766
readonly addLatencyP90Alarm?: Record<string, LatencyThreshold>;
5867
readonly addLatencyP99Alarm?: Record<string, LatencyThreshold>;
@@ -153,6 +162,8 @@ export class LambdaFunctionMonitoring extends Monitoring {
153162
readonly concurrentExecutionsCountMetric: MetricWithAlarmSupport;
154163
readonly provisionedConcurrencySpilloverInvocationsCountMetric: MetricWithAlarmSupport;
155164
readonly provisionedConcurrencySpilloverInvocationsRateMetric: MetricWithAlarmSupport;
165+
166+
readonly isIterator: boolean;
156167
readonly maxIteratorAgeMetric: MetricWithAlarmSupport;
157168

158169
readonly lambdaInsightsEnabled: boolean;
@@ -227,6 +238,8 @@ export class LambdaFunctionMonitoring extends Monitoring {
227238
this.metricFactory.metricProvisionedConcurrencySpilloverInvocations();
228239
this.provisionedConcurrencySpilloverInvocationsRateMetric =
229240
this.metricFactory.metricProvisionedConcurrencySpilloverRate();
241+
242+
this.isIterator = props.isIterator ?? true;
230243
this.maxIteratorAgeMetric =
231244
this.metricFactory.metricMaxIteratorAgeInMillis();
232245

@@ -493,6 +506,12 @@ export class LambdaFunctionMonitoring extends Monitoring {
493506
this.addAlarm(createdAlarm);
494507
}
495508
for (const disambiguator in props.addMaxIteratorAgeAlarm) {
509+
if (!this.isIterator) {
510+
throw new Error(
511+
"addMaxIteratorAgeAlarm is not applicable if isIterator is not true",
512+
);
513+
}
514+
496515
const alarmProps = props.addMaxIteratorAgeAlarm[disambiguator];
497516
const createdAlarm = this.ageAlarmFactory.addIteratorMaxAgeAlarm(
498517
this.maxIteratorAgeMetric,
@@ -524,13 +543,25 @@ export class LambdaFunctionMonitoring extends Monitoring {
524543
this.createErrorRateWidget(QuarterWidth, DefaultGraphWidgetHeight),
525544
this.createRateWidget(QuarterWidth, DefaultGraphWidgetHeight),
526545
),
527-
new Row(
528-
this.createInvocationWidget(ThirdWidth, DefaultGraphWidgetHeight),
529-
this.createIteratorAgeWidget(ThirdWidth, DefaultGraphWidgetHeight),
530-
this.createErrorCountWidget(ThirdWidth, DefaultGraphWidgetHeight),
531-
),
532546
];
533547

548+
if (this.isIterator) {
549+
widgets.push(
550+
new Row(
551+
this.createInvocationWidget(ThirdWidth, DefaultGraphWidgetHeight),
552+
this.createIteratorAgeWidget(ThirdWidth, DefaultGraphWidgetHeight),
553+
this.createErrorCountWidget(ThirdWidth, DefaultGraphWidgetHeight),
554+
),
555+
);
556+
} else {
557+
widgets.push(
558+
new Row(
559+
this.createInvocationWidget(HalfWidth, DefaultGraphWidgetHeight),
560+
this.createErrorCountWidget(HalfWidth, DefaultGraphWidgetHeight),
561+
),
562+
);
563+
}
564+
534565
if (this.lambdaInsightsEnabled) {
535566
widgets.push(
536567
new Row(

test/monitoring/aws-lambda/LambdaFunctionMonitoring.test.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { AlarmWithAnnotation, LambdaFunctionMonitoring } from "../../../lib";
1212
import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil";
1313
import { TestMonitoringScope } from "../TestMonitoringScope";
1414

15-
test("snapshot test: no alarms", () => {
15+
test("snapshot test: default iterator and no alarms", () => {
1616
const stack = new Stack();
1717

1818
const scope = new TestMonitoringScope(stack, "Scope");
@@ -24,14 +24,14 @@ test("snapshot test: no alarms", () => {
2424
handler: "Dummy::handler",
2525
});
2626

27-
new LambdaFunctionMonitoring(scope, {
27+
const monitoring = new LambdaFunctionMonitoring(scope, {
2828
lambdaFunction,
2929
humanReadableName: "Dummy Lambda for testing",
3030
alarmFriendlyName: "DummyLambda",
3131
});
32+
addMonitoringDashboardsToStack(stack, monitoring);
3233

3334
// alternative: use reference
34-
3535
new LambdaFunctionMonitoring(scope, {
3636
lambdaFunction: Function.fromFunctionAttributes(stack, "DummyFunctionRef", {
3737
functionArn:
@@ -42,6 +42,29 @@ test("snapshot test: no alarms", () => {
4242
expect(Template.fromStack(stack)).toMatchSnapshot();
4343
});
4444

45+
test("snapshot test: non-iterator and no alarms", () => {
46+
const stack = new Stack();
47+
48+
const scope = new TestMonitoringScope(stack, "Scope");
49+
50+
const lambdaFunction = new Function(stack, "Function", {
51+
functionName: "DummyLambda",
52+
runtime: Runtime.NODEJS_18_X,
53+
code: InlineCode.fromInline("{}"),
54+
handler: "Dummy::handler",
55+
});
56+
57+
const monitoring = new LambdaFunctionMonitoring(scope, {
58+
lambdaFunction,
59+
humanReadableName: "Dummy Lambda for testing",
60+
alarmFriendlyName: "DummyLambda",
61+
isIterator: false,
62+
});
63+
64+
addMonitoringDashboardsToStack(stack, monitoring);
65+
expect(Template.fromStack(stack)).toMatchSnapshot();
66+
});
67+
4568
test("snapshot test: all alarms", () => {
4669
const stack = new Stack();
4770

@@ -483,6 +506,36 @@ test("snapshot test: all alarms, alarmPrefix on latency dedupeString", () => {
483506
expect(Template.fromStack(stack)).toMatchSnapshot();
484507
});
485508

509+
test("throws error if attempting to create iterator age alarm if not an iterator", () => {
510+
const stack = new Stack();
511+
512+
const scope = new TestMonitoringScope(stack, "Scope");
513+
514+
const lambdaFunction = new Function(stack, "Function", {
515+
functionName: "DummyLambda",
516+
runtime: Runtime.NODEJS_18_X,
517+
code: InlineCode.fromInline("{}"),
518+
handler: "Dummy::handler",
519+
});
520+
521+
expect(
522+
() =>
523+
new LambdaFunctionMonitoring(scope, {
524+
lambdaFunction,
525+
humanReadableName: "Dummy Lambda for testing",
526+
alarmFriendlyName: "DummyLambda",
527+
isIterator: false,
528+
addMaxIteratorAgeAlarm: {
529+
Warning: {
530+
maxAgeInMillis: 1_000_000,
531+
},
532+
},
533+
}),
534+
).toThrow(
535+
"addMaxIteratorAgeAlarm is not applicable if isIterator is not true",
536+
);
537+
});
538+
486539
test("doesn't create alarms for enhanced Lambda Insights metrics if not enabled", () => {
487540
const stack = new Stack();
488541

0 commit comments

Comments
 (0)