From 5448009738431aeebdc6fff1c1c19395d2d5a818 Mon Sep 17 00:00:00 2001 From: Filipp Fediakov Date: Wed, 18 Oct 2023 02:31:21 +0100 Subject: [PATCH] feat(scheduler): metrics for all schedules (#27544) Class Schedule now provides static methods for accessing all schedules metrics with default configuration, such as `metricAllErrors` for viewing errors when executing targets. Advances #23394 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-scheduler-alpha/README.md | 11 ++- .../aws-scheduler-alpha/lib/schedule.ts | 94 +++++++++++++++++++ .../aws-cdk-scheduler-schedule.assets.json | 4 +- .../aws-cdk-scheduler-schedule.template.json | 12 +++ .../integ.schedule.js.snapshot/manifest.json | 8 +- .../test/integ.schedule.js.snapshot/tree.json | 34 ++++++- .../test/integ.schedule.ts | 7 ++ .../aws-scheduler-alpha/test/schedule.test.ts | 41 +++++++- 8 files changed, 204 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-scheduler-alpha/README.md b/packages/@aws-cdk/aws-scheduler-alpha/README.md index 549a9f241530d..af835a8ea02d7 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/README.md +++ b/packages/@aws-cdk/aws-scheduler-alpha/README.md @@ -245,7 +245,16 @@ EventBridge Scheduler publishes additional metrics when your schedule exhausts i ### Metrics for all schedules -TODO: Not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) +Class `Schedule` provides static methods for accessing all schedules metrics with default configuration, + such as `metricAllErrors` for viewing errors when executing targets. + + ```ts +new cloudwatch.Alarm(this, 'SchedulesErrorAlarm', { + metric: Schedule.metricAllErrors(), + threshold: 0, + evaluationPeriods: 1, +}); + ``` ### Metrics for a Group diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts index e6d65c8c0b2fb..5842e7fbb6e90 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts @@ -1,4 +1,5 @@ import { IResource, Resource } from 'aws-cdk-lib'; +import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; import { CfnSchedule } from 'aws-cdk-lib/aws-scheduler'; import { Construct } from 'constructs'; import { IGroup } from './group'; @@ -72,6 +73,99 @@ export interface ScheduleProps { * An EventBridge Schedule */ export class Schedule extends Resource implements ISchedule { + /** + * Return the given named metric for all schedules. + * + * @default - sum over 5 minutes + */ + public static metricAll(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/Scheduler', + metricName, + statistic: 'sum', + ...props, + }); + } + + /** + * Metric for the number of invocations that were throttled across all schedules. + * + * @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/scheduler-quotas.html + * + * @default - sum over 5 minutes + */ + public static metricAllThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metricAll('InvocationThrottleCount', props); + } + + /** + * Metric for all invocation attempts across all schedules. + * + * @default - sum over 5 minutes + */ + public static metricAllAttempts(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metricAll('InvocationAttemptCount', props); + } + /** + * Emitted when the target returns an exception after EventBridge Scheduler calls the target API across all schedules. + * + * @default - sum over 5 minutes + */ + public static metricAllErrors(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metricAll('TargetErrorCount', props); + } + + /** + * Metric for invocation failures due to API throttling by the target across all schedules. + * + * @default - sum over 5 minutes + */ + public static metricAllTargetThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metricAll('TargetErrorThrottledCount', props); + } + + /** + * Metric for dropped invocations when EventBridge Scheduler stops attempting to invoke the target after a schedule's retry policy has been exhausted. + * Metric is calculated for all schedules. + * + * @default - sum over 5 minutes + */ + public static metricAllDropped(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metricAll('InvocationDroppedCount', props); + } + + /** + * Metric for invocations delivered to the DLQ across all schedules. + * + * @default - sum over 5 minutes + */ + public static metricAllSentToDLQ(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metricAll('InvocationsSentToDeadLetterCount', props); + } + + /** + * Metric for failed invocations that also failed to deliver to DLQ across all schedules. + * + * @default - sum over 5 minutes + */ + public static metricAllFailedToBeSentToDLQ(errorCode?: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + if (errorCode) { + return this.metricAll(`InvocationsFailedToBeSentToDeadLetterCount_${errorCode}`, props); + } + + return this.metricAll('InvocationsFailedToBeSentToDeadLetterCount', props); + } + + /** + * Metric for delivery of failed invocations to DLQ when the payload of the event sent to the DLQ exceeds the maximum size allowed by Amazon SQS. + * Metric is calculated for all schedules. + * + * @default - sum over 5 minutes + */ + public static metricAllSentToDLQTrunacted(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.metricAll('InvocationsSentToDeadLetterCount_Truncated_MessageSizeExceeded', props); + } + /** * The schedule group associated with this schedule. */ diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json index 2a26ac17ddebd..e497b1a7d58e9 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json @@ -1,7 +1,7 @@ { "version": "34.0.0", "files": { - "8e518d2f0d7b4fcf41623a37d5a30fa0a49f5185d9301205cad6e72cc59b84a8": { + "ea2b130e655c0c74a4099d37ab0b44701887641ac2253eec676e8048f199737e": { "source": { "path": "aws-cdk-scheduler-schedule.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "8e518d2f0d7b4fcf41623a37d5a30fa0a49f5185d9301205cad6e72cc59b84a8.json", + "objectKey": "ea2b130e655c0c74a4099d37ab0b44701887641ac2253eec676e8048f199737e.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json index 59e00cc6402db..fd059201b67bf 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json @@ -116,6 +116,18 @@ } } } + }, + "AllSchedulerErrorsAlarmA3246F8C": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "TargetErrorCount", + "Namespace": "AWS/Scheduler", + "Period": 300, + "Statistic": "Sum", + "Threshold": 1 + } } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json index 0bbde393e24c3..cfd3e45618128 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/8e518d2f0d7b4fcf41623a37d5a30fa0a49f5185d9301205cad6e72cc59b84a8.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/ea2b130e655c0c74a4099d37ab0b44701887641ac2253eec676e8048f199737e.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -64,6 +64,12 @@ "data": "DisabledScheduleA1DF7F0F" } ], + "/aws-cdk-scheduler-schedule/AllSchedulerErrorsAlarm/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AllSchedulerErrorsAlarmA3246F8C" + } + ], "/aws-cdk-scheduler-schedule/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json index db604f9e73761..816d0ed175086 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json @@ -181,7 +181,7 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-scheduler-alpha.Schedule", + "fqn": "aws-cdk-lib.Resource", "version": "0.0.0" } }, @@ -224,7 +224,37 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-scheduler-alpha.Schedule", + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "AllSchedulerErrorsAlarm": { + "id": "AllSchedulerErrorsAlarm", + "path": "aws-cdk-scheduler-schedule/AllSchedulerErrorsAlarm", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-scheduler-schedule/AllSchedulerErrorsAlarm/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", + "aws:cdk:cloudformation:props": { + "comparisonOperator": "GreaterThanOrEqualToThreshold", + "evaluationPeriods": 1, + "metricName": "TargetErrorCount", + "namespace": "AWS/Scheduler", + "period": 300, + "statistic": "Sum", + "threshold": 1 + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_cloudwatch.CfnAlarm", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_cloudwatch.Alarm", "version": "0.0.0" } }, diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.ts b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.ts index a1907102a0ebf..11563b6beddae 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.ts @@ -1,5 +1,6 @@ import { IntegTest } from '@aws-cdk/integ-tests-alpha'; import * as cdk from 'aws-cdk-lib'; +import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as scheduler from '../lib'; @@ -42,6 +43,12 @@ new scheduler.Schedule(stack, 'DisabledSchedule', { enabled: false, }); +new cloudwatch.Alarm(stack, 'AllSchedulerErrorsAlarm', { + metric: scheduler.Schedule.metricAllErrors(), + threshold: 1, + evaluationPeriods: 1, +}); + new IntegTest(app, 'integtest-schedule', { testCases: [stack], }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/schedule.test.ts b/packages/@aws-cdk/aws-scheduler-alpha/test/schedule.test.ts index a4db92018322d..b03726114302f 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/schedule.test.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/schedule.test.ts @@ -1,4 +1,4 @@ -import { App, Stack } from 'aws-cdk-lib'; +import { App, Stack, Duration } from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; import * as iam from 'aws-cdk-lib/aws-iam'; @@ -62,4 +62,43 @@ describe('Schedule', () => { State: 'DISABLED', }); }); + + test('returns metric for delivery of failed invocations to DLQ', () => { + // WHEN + const metric = Schedule.metricAllFailedToBeSentToDLQ(); + + // THEN + expect(metric.namespace).toEqual('AWS/Scheduler'); + expect(metric.metricName).toEqual('InvocationsFailedToBeSentToDeadLetterCount'); + expect(metric.dimensions).toBeUndefined(); + expect(metric.statistic).toEqual('Sum'); + expect(metric.period).toEqual(Duration.minutes(5)); + }); + + test('returns metric for delivery of failed invocations to DLQ for specific error code', () => { + // WHEN + const metric = Schedule.metricAllFailedToBeSentToDLQ('test_error_code'); + + // THEN + expect(metric.namespace).toEqual('AWS/Scheduler'); + expect(metric.metricName).toEqual('InvocationsFailedToBeSentToDeadLetterCount_test_error_code'); + expect(metric.dimensions).toBeUndefined(); + expect(metric.statistic).toEqual('Sum'); + expect(metric.period).toEqual(Duration.minutes(5)); + }); + + test('returns metric for all errors with provided statistic and period', () => { + // WHEN + const metric = Schedule.metricAllErrors({ + statistic: 'Maximum', + period: Duration.minutes(1), + }); + + // THEN + expect(metric.namespace).toEqual('AWS/Scheduler'); + expect(metric.metricName).toEqual('TargetErrorCount'); + expect(metric.dimensions).toBeUndefined(); + expect(metric.statistic).toEqual('Maximum'); + expect(metric.period).toEqual(Duration.minutes(1)); + }); }); \ No newline at end of file