Skip to content

Commit

Permalink
feat(lambda): add isIterator prop (#569)
Browse files Browse the repository at this point in the history
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_
  • Loading branch information
echeung-amzn authored Sep 9, 2024
1 parent 716a99e commit b0cc9b3
Show file tree
Hide file tree
Showing 4 changed files with 520 additions and 9 deletions.
39 changes: 39 additions & 0 deletions API.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 36 additions & 5 deletions lib/monitoring/aws-lambda/LambdaFunctionMonitoring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
ErrorCountThreshold,
ErrorRateThreshold,
ErrorType,
HalfWidth,
HighTpsThreshold,
LatencyAlarmFactory,
LatencyThreshold,
Expand Down Expand Up @@ -53,6 +54,14 @@ import {
} from "../../dashboard";

export interface LambdaFunctionMonitoringOptions extends BaseMonitoringProps {
/**
* Indicates that the Lambda function handles an event source (e.g. DynamoDB event stream).
* This impacts what widgets are shown, as well as validates the ability to use addMaxIteratorAgeAlarm.
*
* @default - true
*/
readonly isIterator?: boolean;

readonly addLatencyP50Alarm?: Record<string, LatencyThreshold>;
readonly addLatencyP90Alarm?: Record<string, LatencyThreshold>;
readonly addLatencyP99Alarm?: Record<string, LatencyThreshold>;
Expand Down Expand Up @@ -153,6 +162,8 @@ export class LambdaFunctionMonitoring extends Monitoring {
readonly concurrentExecutionsCountMetric: MetricWithAlarmSupport;
readonly provisionedConcurrencySpilloverInvocationsCountMetric: MetricWithAlarmSupport;
readonly provisionedConcurrencySpilloverInvocationsRateMetric: MetricWithAlarmSupport;

readonly isIterator: boolean;
readonly maxIteratorAgeMetric: MetricWithAlarmSupport;

readonly lambdaInsightsEnabled: boolean;
Expand Down Expand Up @@ -227,6 +238,8 @@ export class LambdaFunctionMonitoring extends Monitoring {
this.metricFactory.metricProvisionedConcurrencySpilloverInvocations();
this.provisionedConcurrencySpilloverInvocationsRateMetric =
this.metricFactory.metricProvisionedConcurrencySpilloverRate();

this.isIterator = props.isIterator ?? true;
this.maxIteratorAgeMetric =
this.metricFactory.metricMaxIteratorAgeInMillis();

Expand Down Expand Up @@ -493,6 +506,12 @@ export class LambdaFunctionMonitoring extends Monitoring {
this.addAlarm(createdAlarm);
}
for (const disambiguator in props.addMaxIteratorAgeAlarm) {
if (!this.isIterator) {
throw new Error(
"addMaxIteratorAgeAlarm is not applicable if isIterator is not true",
);
}

const alarmProps = props.addMaxIteratorAgeAlarm[disambiguator];
const createdAlarm = this.ageAlarmFactory.addIteratorMaxAgeAlarm(
this.maxIteratorAgeMetric,
Expand Down Expand Up @@ -524,13 +543,25 @@ export class LambdaFunctionMonitoring extends Monitoring {
this.createErrorRateWidget(QuarterWidth, DefaultGraphWidgetHeight),
this.createRateWidget(QuarterWidth, DefaultGraphWidgetHeight),
),
new Row(
this.createInvocationWidget(ThirdWidth, DefaultGraphWidgetHeight),
this.createIteratorAgeWidget(ThirdWidth, DefaultGraphWidgetHeight),
this.createErrorCountWidget(ThirdWidth, DefaultGraphWidgetHeight),
),
];

if (this.isIterator) {
widgets.push(
new Row(
this.createInvocationWidget(ThirdWidth, DefaultGraphWidgetHeight),
this.createIteratorAgeWidget(ThirdWidth, DefaultGraphWidgetHeight),
this.createErrorCountWidget(ThirdWidth, DefaultGraphWidgetHeight),
),
);
} else {
widgets.push(
new Row(
this.createInvocationWidget(HalfWidth, DefaultGraphWidgetHeight),
this.createErrorCountWidget(HalfWidth, DefaultGraphWidgetHeight),
),
);
}

if (this.lambdaInsightsEnabled) {
widgets.push(
new Row(
Expand Down
59 changes: 56 additions & 3 deletions test/monitoring/aws-lambda/LambdaFunctionMonitoring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { AlarmWithAnnotation, LambdaFunctionMonitoring } from "../../../lib";
import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil";
import { TestMonitoringScope } from "../TestMonitoringScope";

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

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

new LambdaFunctionMonitoring(scope, {
const monitoring = new LambdaFunctionMonitoring(scope, {
lambdaFunction,
humanReadableName: "Dummy Lambda for testing",
alarmFriendlyName: "DummyLambda",
});
addMonitoringDashboardsToStack(stack, monitoring);

// alternative: use reference

new LambdaFunctionMonitoring(scope, {
lambdaFunction: Function.fromFunctionAttributes(stack, "DummyFunctionRef", {
functionArn:
Expand All @@ -42,6 +42,29 @@ test("snapshot test: no alarms", () => {
expect(Template.fromStack(stack)).toMatchSnapshot();
});

test("snapshot test: non-iterator and no alarms", () => {
const stack = new Stack();

const scope = new TestMonitoringScope(stack, "Scope");

const lambdaFunction = new Function(stack, "Function", {
functionName: "DummyLambda",
runtime: Runtime.NODEJS_18_X,
code: InlineCode.fromInline("{}"),
handler: "Dummy::handler",
});

const monitoring = new LambdaFunctionMonitoring(scope, {
lambdaFunction,
humanReadableName: "Dummy Lambda for testing",
alarmFriendlyName: "DummyLambda",
isIterator: false,
});

addMonitoringDashboardsToStack(stack, monitoring);
expect(Template.fromStack(stack)).toMatchSnapshot();
});

test("snapshot test: all alarms", () => {
const stack = new Stack();

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

test("throws error if attempting to create iterator age alarm if not an iterator", () => {
const stack = new Stack();

const scope = new TestMonitoringScope(stack, "Scope");

const lambdaFunction = new Function(stack, "Function", {
functionName: "DummyLambda",
runtime: Runtime.NODEJS_18_X,
code: InlineCode.fromInline("{}"),
handler: "Dummy::handler",
});

expect(
() =>
new LambdaFunctionMonitoring(scope, {
lambdaFunction,
humanReadableName: "Dummy Lambda for testing",
alarmFriendlyName: "DummyLambda",
isIterator: false,
addMaxIteratorAgeAlarm: {
Warning: {
maxAgeInMillis: 1_000_000,
},
},
}),
).toThrow(
"addMaxIteratorAgeAlarm is not applicable if isIterator is not true",
);
});

test("doesn't create alarms for enhanced Lambda Insights metrics if not enabled", () => {
const stack = new Stack();

Expand Down
Loading

0 comments on commit b0cc9b3

Please sign in to comment.