diff --git a/README.md b/README.md index 5b3e1df9..23e1f506 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,18 @@ SNS Topic dashboard widgets show: |--|--| |![Invalid Attributes](https://raw.githubusercontent.com/fourtheorem/slic-watch/main/docs/snsInvalidAttributes.png)|![Notifications Failed](https://raw.githubusercontent.com/fourtheorem/slic-watch/main/docs/snsNotificationsFailed.png) | +### EventBridge + +EventBridge alarms are created for: +1. Failed Invocations +2. ThrottledRules + +EventBridge Rule dashboard widgets show: + +|FailedInvocations|Invocations|ThrottledRules| +|--|--| +|![FailedInvocations](https://raw.githubusercontent.com/fourtheorem/slic-watch/main/docs/eventBridgeFailedInvocations.png)|![Invocations](https://raw.githubusercontent.com/fourtheorem/slic-watch/main/docs/eventBridgeInvocations.png)| + ## Configuration Configuration is entirely optional - SLIC Watch provides defaults that work out of the box. @@ -286,6 +298,14 @@ custom: NumberOfNotificationsFailed: Statistic: Sum Threshold: 1 + Events: + #EventBridge + FailedInvocations: + Statistic: Sum + Threshold: 1 + ThrottledRules: + Statistic: Sum + Threshold: 1 dashboard: enabled: true @@ -371,6 +391,14 @@ custom: Statistic: ["Sum"] NumberOfNotificationsFailed: Statistic: ["Sum"] + Events: + #EventBridge + FailedInvocations: + Statistic: ["Sum"] + ThrottledRules: + Statistic: ["Sum"] + Invocations: + Statistic: ["Sum"] ``` An example project is provided for reference: [serverless-test-project](./serverless-test-project) diff --git a/docs/eventBridgeFailedInvocations.png b/docs/eventBridgeFailedInvocations.png new file mode 100644 index 00000000..ca1c8976 Binary files /dev/null and b/docs/eventBridgeFailedInvocations.png differ diff --git a/docs/eventBridgeInvocations.png b/docs/eventBridgeInvocations.png new file mode 100644 index 00000000..e0a3dda1 Binary files /dev/null and b/docs/eventBridgeInvocations.png differ diff --git a/serverless-plugin/alarms-eventbridge.js b/serverless-plugin/alarms-eventbridge.js new file mode 100644 index 00000000..933c145c --- /dev/null +++ b/serverless-plugin/alarms-eventbridge.js @@ -0,0 +1,119 @@ +'use strict' + +/** + * @param {object} eventsAlarmsConfig The fully resolved alarm configuration + */ +module.exports = function eventsAlarms (eventsAlarmsConfig, context) { + return { + createRuleAlarms + } + + /** + * Add all required Events alarms to the provided CloudFormation template + * based on the EventBridge Rule found within + * + * @param {CloudFormationTemplate} cfTemplate A CloudFormation template object + */ + function createRuleAlarms (cfTemplate) { + const ruleResources = cfTemplate.getResourcesByType( + 'AWS::Events::Rule' + ) + + for (const [ruleResourceName, ruleResource] of Object.entries(ruleResources)) { + if (eventsAlarmsConfig.FailedInvocations.enabled) { + const failedInvocations = createFailedInvocationsAlarm( + ruleResourceName, + ruleResource, + eventsAlarmsConfig.FailedInvocations + ) + cfTemplate.addResource(failedInvocations.resourceName, failedInvocations.resource) + } + + if (eventsAlarmsConfig.ThrottledRules.enabled) { + const throttledRules = createThrottledRulesAlarm( + ruleResourceName, + ruleResource, + eventsAlarmsConfig.ThrottledRules + ) + cfTemplate.addResource(throttledRules.resourceName, throttledRules.resource) + } + } + } + + function createRuleAlarm ( + alarmName, + alarmDescription, + ruleName, + comparisonOperator, + threshold, + metricName, + statistic, + period, + evaluationPeriods, + treatMissingData + ) { + const metricProperties = { + Dimensions: [{ Name: 'RuleName', Value: ruleName }], + MetricName: metricName, + Namespace: 'AWS/Events', + Period: period, + Statistic: statistic + } + + return { + Type: 'AWS::CloudWatch::Alarm', + Properties: { + ActionsEnabled: true, + AlarmActions: context.alarmActions, + AlarmName: alarmName, + AlarmDescription: alarmDescription, + EvaluationPeriods: evaluationPeriods, + ComparisonOperator: comparisonOperator, + Threshold: threshold, + TreatMissingData: treatMissingData, + ...metricProperties + } + } + } + + function createFailedInvocationsAlarm (ruleResourceName, ruleResource, config) { + const ruleName = ruleResource.Properties.Name + const threshold = config.Threshold + + return { + resourceName: `slicWatchEventsFailedInvocationsAlarm${ruleResourceName}`, + resource: createRuleAlarm( + `EventsFailedInvocationsAlarm_${ruleName}`, // alarmName + `Failed Invocations for ${ruleName} breaches ${threshold}`, // alarmDescription + ruleName, + config.ComparisonOperator, + threshold, + 'FailedInvocations', // metricName + config.Statistic, + config.Period, + config.EvaluationPeriods, + config.TreatMissingData + ) + } + } + + function createThrottledRulesAlarm (ruleResourceName, ruleResource, config) { + const ruleName = ruleResource.Properties.Name + const threshold = config.Threshold + return { + resourceName: `slicWatchEventsThrottledRulesAlarm${ruleResourceName}`, + resource: createRuleAlarm( + `EventsThrottledRulesAlarm_${ruleName}`, // alarmName + `Throttled RulesAlarm for ${ruleName} breaches ${threshold}`, // alarmDescription + ruleName, + config.ComparisonOperator, + threshold, + 'ThrottledRules', // metricName + config.Statistic, + config.Period, + config.EvaluationPeriods, + config.TreatMissingData + ) + } + } +} diff --git a/serverless-plugin/alarms.js b/serverless-plugin/alarms.js index a89fe435..5d358846 100644 --- a/serverless-plugin/alarms.js +++ b/serverless-plugin/alarms.js @@ -11,6 +11,7 @@ const kinesisAlarms = require('./alarms-kinesis') const sqsAlarms = require('./alarms-sqs') const ecsAlarms = require('./alarms-ecs') const snsAlarms = require('./alarms-sns') +const ruleAlarms = require('./alarms-eventbridge') module.exports = function alarms (serverless, alarmConfig, functionAlarmConfigs, context) { const { @@ -21,7 +22,8 @@ module.exports = function alarms (serverless, alarmConfig, functionAlarmConfigs, SQS: sqsConfig, Lambda: lambdaConfig, ECS: ecsConfig, - SNS: snsConfig + SNS: snsConfig, + Events: ruleConfig } = cascade(alarmConfig) const cascadedFunctionAlarmConfigs = applyAlarmConfig(lambdaConfig, functionAlarmConfigs) @@ -33,6 +35,7 @@ module.exports = function alarms (serverless, alarmConfig, functionAlarmConfigs, const { createSQSAlarms } = sqsAlarms(sqsConfig, context, serverless) const { createECSAlarms } = ecsAlarms(ecsConfig, context, serverless) const { createSNSAlarms } = snsAlarms(snsConfig, context, serverless) + const { createRuleAlarms } = ruleAlarms(ruleConfig, context, serverless) return { addAlarms @@ -54,6 +57,7 @@ module.exports = function alarms (serverless, alarmConfig, functionAlarmConfigs, sqsConfig.enabled && createSQSAlarms(cfTemplate) ecsConfig.enabled && createECSAlarms(cfTemplate) snsConfig.enabled && createSNSAlarms(cfTemplate) + ruleConfig.enabled && createRuleAlarms(cfTemplate) } } } diff --git a/serverless-plugin/config-schema.js b/serverless-plugin/config-schema.js index 3910cae3..5dad1ad3 100644 --- a/serverless-plugin/config-schema.js +++ b/serverless-plugin/config-schema.js @@ -19,7 +19,8 @@ const supportedAlarms = { Kinesis: ['GetRecords.IteratorAgeMilliseconds', 'ReadProvisionedThroughputExceeded', 'WriteProvisionedThroughputExceeded', 'PutRecord.Success', 'PutRecords.Success', 'GetRecords.Success'], SQS: ['AgeOfOldestMessage', 'InFlightMessagesPc'], ECS: ['MemoryUtilization', 'CPUUtilization'], - SNS: ['NumberOfNotificationsFilteredOut-InvalidAttributes', 'NumberOfNotificationsFailed'] + SNS: ['NumberOfNotificationsFilteredOut-InvalidAttributes', 'NumberOfNotificationsFailed'], + Events: ['FailedInvocations', 'ThrottledRules'] } const supportedWidgets = { @@ -30,7 +31,8 @@ const supportedWidgets = { Kinesis: ['GetRecords.IteratorAgeMilliseconds', 'ReadProvisionedThroughputExceeded', 'WriteProvisionedThroughputExceeded', 'PutRecord.Success', 'PutRecords.Success', 'GetRecords.Success'], SQS: ['NumberOfMessagesSent', 'NumberOfMessagesReceived', 'NumberOfMessagesDeleted', 'ApproximateAgeOfOldestMessage', 'ApproximateNumberOfMessagesVisible'], ECS: ['MemoryUtilization', 'CPUUtilization'], - SNS: ['NumberOfNotificationsFilteredOut-InvalidAttributes', 'NumberOfNotificationsFailed'] + SNS: ['NumberOfNotificationsFilteredOut-InvalidAttributes', 'NumberOfNotificationsFailed'], + Events: ['FailedInvocations', 'ThrottledRules', 'Invocations'] } const commonAlarmProperties = { diff --git a/serverless-plugin/dashboard.js b/serverless-plugin/dashboard.js index 9c9ebd80..71a4c2fb 100644 --- a/serverless-plugin/dashboard.js +++ b/serverless-plugin/dashboard.js @@ -22,7 +22,8 @@ module.exports = function dashboard (serverless, dashboardConfig, functionDashbo Kinesis: kinesisDashConfig, SQS: sqsDashConfig, ECS: ecsDashConfig, - SNS: snsDashConfig + SNS: snsDashConfig, + Events: ruleDashConfig } } = cascade(dashboardConfig) @@ -61,6 +62,9 @@ module.exports = function dashboard (serverless, dashboardConfig, functionDashbo const topicResources = cfTemplate.getResourcesByType( 'AWS::SNS::Topic' ) + const ruleResources = cfTemplate.getResourcesByType( + 'AWS::Events::Rule' + ) const eventSourceMappingFunctions = cfTemplate.getEventSourceMappingFunctions() const apiWidgets = createApiWidgets(apiResources) @@ -74,6 +78,7 @@ module.exports = function dashboard (serverless, dashboardConfig, functionDashbo const queueWidgets = createQueueWidgets(queueResources) const ecsWidgets = createEcsWidgets(ecsServiceResources) const topicWidgets = createTopicWidgets(topicResources) + const ruleWidgets = createRuleWidgets(ruleResources) const positionedWidgets = layOutWidgets([ ...apiWidgets, @@ -83,7 +88,8 @@ module.exports = function dashboard (serverless, dashboardConfig, functionDashbo ...streamWidgets, ...queueWidgets, ...ecsWidgets, - ...topicWidgets + ...topicWidgets, + ...ruleWidgets ]) if (positionedWidgets.length > 0) { @@ -501,6 +507,43 @@ module.exports = function dashboard (serverless, dashboardConfig, functionDashbo return topicWidgets } + /** + * Create a set of CloudWatch Dashboard widgets for SNS services. + * + * @param {object} ruleResources Object of SNS Service resources by resource name + */ + function createRuleWidgets (ruleResources) { + const ruleWidgets = [] + for (const res of Object.values(ruleResources)) { + const ruleName = res.Properties.Name + + const widgetMetrics = [] + for (const [metric, metricConfig] of Object.entries(getConfiguredMetrics(ruleDashConfig))) { + if (metricConfig.enabled) { + for (const stat of metricConfig.Statistic) { + widgetMetrics.push({ + namespace: 'AWS/Events', + metric, + dimensions: { + RuleName: ruleName + }, + stat + }) + } + } + } + if (widgetMetrics.length > 0) { + const metricStatWidget = createMetricWidget( + `Events rule ${ruleName}`, + widgetMetrics, + ruleDashConfig + ) + ruleWidgets.push(metricStatWidget) + } + } + return ruleWidgets + } + /** * Set the location and dimension properties of each provided widget * diff --git a/serverless-plugin/default-config.yaml b/serverless-plugin/default-config.yaml index 4e69e21c..fb172dbd 100644 --- a/serverless-plugin/default-config.yaml +++ b/serverless-plugin/default-config.yaml @@ -101,6 +101,14 @@ alarms: NumberOfNotificationsFailed: Statistic: Sum Threshold: 1 + Events: + #EventBridge + FailedInvocations: + Statistic: Sum + Threshold: 1 + ThrottledRules: + Statistic: Sum + Threshold: 1 dashboard: enabled: true @@ -185,5 +193,12 @@ dashboard: Statistic: ["Sum"] NumberOfNotificationsFailed: Statistic: ["Sum"] - + Events: + #EventBridge + FailedInvocations: + Statistic: ["Sum"] + ThrottledRules: + Statistic: ["Sum"] + Invocations: + Statistic: ["Sum"] \ No newline at end of file diff --git a/serverless-plugin/tests/alarms-eventbridge.test.js b/serverless-plugin/tests/alarms-eventbridge.test.js new file mode 100644 index 00000000..658341a3 --- /dev/null +++ b/serverless-plugin/tests/alarms-eventbridge.test.js @@ -0,0 +1,99 @@ +'use strict' + +const ruleAlarms = require('../alarms-eventbridge') +const { test } = require('tap') +const defaultConfig = require('../default-config') +const { + assertCommonAlarmProperties, + alarmNameToType, + createTestConfig, + createTestCloudFormationTemplate, + testContext +} = require('./testing-utils') + +test('Events alarms are created', (t) => { + const alarmConfig = createTestConfig( + defaultConfig.alarms, + { + Period: 120, + EvaluationPeriods: 2, + TreatMissingData: 'breaching', + ComparisonOperator: 'GreaterThanOrEqualToThreshold', + Events: { + FailedInvocations: { + Threshold: 50 + }, + ThrottledRules: { + Threshold: 50 + } + } + } + ) + + const ruleAlarmConfig = alarmConfig.Events + + const { createRuleAlarms } = ruleAlarms(ruleAlarmConfig, testContext) + const cfTemplate = createTestCloudFormationTemplate() + createRuleAlarms(cfTemplate) + + const alarmResources = cfTemplate.getResourcesByType('AWS::CloudWatch::Alarm') + + const expectedTypes = { + EventsFailedInvocationsAlarm: 'FailedInvocations', + EventsThrottledRulesAlarm: 'ThrottledRules' + } + + t.equal(Object.keys(alarmResources).length, Object.keys(expectedTypes).length) + for (const alarmResource of Object.values(alarmResources)) { + const al = alarmResource.Properties + assertCommonAlarmProperties(t, al) + const alarmType = alarmNameToType(al.AlarmName) + const expectedMetric = expectedTypes[alarmType] + t.equal(al.MetricName, expectedMetric) + t.ok(al.Statistic) + t.equal(al.Threshold, ruleAlarmConfig[expectedMetric].Threshold) + t.equal(al.EvaluationPeriods, 2) + t.equal(al.TreatMissingData, 'breaching') + t.equal(al.ComparisonOperator, 'GreaterThanOrEqualToThreshold') + t.equal(al.Namespace, 'AWS/Events') + t.equal(al.Period, 120) + t.same(al.Dimensions, [ + { + Name: 'RuleName', + Value: 'serverless-test-project-dev-eventsRule-rule-1' + } + ]) + } + + t.end() +}) + +test('Events alarms are not created when disabled globally', (t) => { + const alarmConfig = createTestConfig( + defaultConfig.alarms, + { + Events: { + enabled: false, // disabled globally + Period: 60, + FailedInvocations: { + Threshold: 50 + }, + ThrottledRules: { + Threshold: 50 + } + } + } + ) + + const ruleAlarmConfig = alarmConfig.Events + + const { createRuleAlarms } = ruleAlarms(ruleAlarmConfig, testContext) + + const cfTemplate = createTestCloudFormationTemplate() + createRuleAlarms(cfTemplate) + + const alarmResources = cfTemplate.getResourcesByType('AWS::CloudWatch::Alarm') + + t.same({}, alarmResources) + t.end() +}) diff --git a/serverless-plugin/tests/alarms.test.js b/serverless-plugin/tests/alarms.test.js index 3ef92578..63f11a7a 100644 --- a/serverless-plugin/tests/alarms.test.js +++ b/serverless-plugin/tests/alarms.test.js @@ -22,7 +22,7 @@ test('Alarms create all service alarms', (t) => { namespaces.add(resource.Properties.Namespace) } } - t.same(namespaces, new Set(['AWS/Lambda', 'AWS/ApiGateway', 'AWS/States', 'AWS/DynamoDB', 'AWS/Kinesis', 'AWS/SQS', 'AWS/ECS', 'AWS/SNS'])) + t.same(namespaces, new Set(['AWS/Lambda', 'AWS/ApiGateway', 'AWS/States', 'AWS/DynamoDB', 'AWS/Kinesis', 'AWS/SQS', 'AWS/ECS', 'AWS/SNS', 'AWS/Events'])) t.end() }) diff --git a/serverless-plugin/tests/dashboard.test.js b/serverless-plugin/tests/dashboard.test.js index 3599bc72..a0ce4efe 100644 --- a/serverless-plugin/tests/dashboard.test.js +++ b/serverless-plugin/tests/dashboard.test.js @@ -245,6 +245,27 @@ test('A dashboard includes metrics', (t) => { t.end() }) + t.test('dashboard includes Events metrics', (t) => { + const widgets = dashBody.widgets.filter(({ properties: { title } }) => + title.startsWith('Events') + ) + t.equal(widgets.length, 1) + const namespaces = new Set() + for (const widget of widgets) { + for (const metric of widget.properties.metrics) { + namespaces.add(metric[0]) + } + } + t.same(namespaces, new Set(['AWS/Events'])) + const expectedTitles = new Set(['Events rule serverless-test-project-dev-eventsRule-rule-1']) + + const actualTitles = new Set( + widgets.map((widget) => widget.properties.title) + ) + t.same(actualTitles, expectedTitles) + t.end() + }) + t.end() }) @@ -279,7 +300,7 @@ test('DynamoDB widgets are created without GSIs', (t) => { }) test('No dashboard is created if all widgets are disabled', (t) => { - const services = ['Lambda', 'ApiGateway', 'States', 'DynamoDB', 'SQS', 'Kinesis', 'ECS', 'SNS'] + const services = ['Lambda', 'ApiGateway', 'States', 'DynamoDB', 'SQS', 'Kinesis', 'ECS', 'SNS', 'Events'] const dashConfig = cloneDeep(defaultConfig.dashboard) for (const service of services) { dashConfig.widgets[service].enabled = false @@ -293,7 +314,7 @@ test('No dashboard is created if all widgets are disabled', (t) => { }) test('No dashboard is created if all metrics are disabled', (t) => { - const services = ['Lambda', 'ApiGateway', 'States', 'DynamoDB', 'SQS', 'Kinesis', 'ECS', 'SNS'] + const services = ['Lambda', 'ApiGateway', 'States', 'DynamoDB', 'SQS', 'Kinesis', 'ECS', 'SNS', 'Events'] const dashConfig = cloneDeep(defaultConfig.dashboard) for (const service of services) { for (const metricConfig of Object.values(dashConfig.widgets[service])) { diff --git a/serverless-plugin/tests/resources/cloudformation-template-stack.json b/serverless-plugin/tests/resources/cloudformation-template-stack.json index fbadfc08..9722630c 100644 --- a/serverless-plugin/tests/resources/cloudformation-template-stack.json +++ b/serverless-plugin/tests/resources/cloudformation-template-stack.json @@ -1094,6 +1094,29 @@ "Ref": "topic" } } + }, + "ServerlesstestprojectdeveventsRulerule1EventBridgeRule": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail-type": [ + "Invoke Lambda Function" + ] + }, + "Name": "serverless-test-project-dev-eventsRule-rule-1", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "EventsRuleLambdaFunction", + "Arn" + ] + }, + "Id": "serverless-test-project-dev-eventsRule-rule-1-target" + } + ] + } } }, "Outputs": { diff --git a/serverless-test-project/rule-handler.js b/serverless-test-project/rule-handler.js new file mode 100644 index 00000000..131ca156 --- /dev/null +++ b/serverless-test-project/rule-handler.js @@ -0,0 +1,11 @@ +'use strict' + +module.exports.handleRule = async (event) => { + console.log(event) + const e = Boolean(event.detail.triggerError) + if (e) { + throw new Error('Error triggered') + } else { + console.log('Successful event delivery') + } +} diff --git a/serverless-test-project/serverless.yml b/serverless-test-project/serverless.yml index 7acab31b..b79671d2 100644 --- a/serverless-test-project/serverless.yml +++ b/serverless-test-project/serverless.yml @@ -14,6 +14,7 @@ custom: Invocations: enabled: true Threshold: 10 + SQS: AgeOfOldestMessage: Statistic: Maximum @@ -86,6 +87,21 @@ functions: method: post path: "subscription" + eventsRule: + handler: rule-handler.handleRule + slicWatch: + alarms: + Lambda: + enabled: false + events: + - eventBridge: + pattern: + detail-type: + - Invoke Lambda Function + retryPolicy: + maximumEventAge: 60 + maximumRetryAttempts: 2 + resources: ${file(./sls-resources.yml)} stepFunctions: @@ -107,3 +123,4 @@ stepFunctions: includeExecutionData: true destinations: - Fn::GetAtt: [expressWorkflowLogGroup, Arn] + diff --git a/serverless-test-project/sls-resources.yml b/serverless-test-project/sls-resources.yml index 81bf23dd..6f9d1b33 100644 --- a/serverless-test-project/sls-resources.yml +++ b/serverless-test-project/sls-resources.yml @@ -146,4 +146,4 @@ Resources: Properties: Endpoint: !Sub "https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/${self:provider.stage}/subscription" Protocol: https - TopicArn: !Ref topic \ No newline at end of file + TopicArn: !Ref topic diff --git a/testing/eventbridge_events_testing.ipynb b/testing/eventbridge_events_testing.ipynb new file mode 100644 index 00000000..4093f998 --- /dev/null +++ b/testing/eventbridge_events_testing.ipynb @@ -0,0 +1,211 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3e825220", + "metadata": {}, + "source": [ + "# EventBridge Alarm Testing" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a64602bf", + "metadata": {}, + "outputs": [], + "source": [ + "import boto3\n", + "from tqdm.notebook import tqdm" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "bd84a08d", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "events_client = boto3.client('events',\n", + "region_name='eu-west-1', \n", + "aws_access_key_id='aws_access_key_id',\n", + "aws_secret_access_key='aws_secret_access_key'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e2228271", + "metadata": {}, + "outputs": [], + "source": [ + "rule_name = 'serverless-test-project-dev-eventsRule-rule-1'" + ] + }, + { + "cell_type": "markdown", + "id": "f4b6f4e5", + "metadata": {}, + "source": [ + "### Send a normal event" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "baf3649c", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'FailedEntryCount': 0,\n", + " 'Entries': [{'EventId': '73fe5cb2-4e97-f302-44d6-3e444c14e744'}],\n", + " 'ResponseMetadata': {'RequestId': 'b8083213-0476-4ee7-9b32-e004bbaeb401',\n", + " 'HTTPStatusCode': 200,\n", + " 'HTTPHeaders': {'x-amzn-requestid': 'b8083213-0476-4ee7-9b32-e004bbaeb401',\n", + " 'content-type': 'application/x-amz-json-1.1',\n", + " 'content-length': '85',\n", + " 'date': 'Mon, 11 Jul 2022 10:51:15 GMT'},\n", + " 'RetryAttempts': 0}}" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "events_client.put_events(\n", + " Entries=[\n", + " {\n", + " 'DetailType': 'Invoke Lambda Function',\n", + " 'Source': 'aws',\n", + " 'EventBusName': 'default',\n", + " 'Detail' : '{\"abc\":\"abc\"}'\n", + " }\n", + " ]\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "b4bf991c", + "metadata": {}, + "source": [ + "### Send a failed event" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "ceb4e0ff", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'FailedEntryCount': 1,\n", + " 'Entries': [{'ErrorCode': 'MalformedDetail',\n", + " 'ErrorMessage': 'Detail is malformed.'}],\n", + " 'ResponseMetadata': {'RequestId': '27cc1e46-7b22-4e4d-ac7d-013f68c0fe16',\n", + " 'HTTPStatusCode': 200,\n", + " 'HTTPHeaders': {'x-amzn-requestid': '27cc1e46-7b22-4e4d-ac7d-013f68c0fe16',\n", + " 'content-type': 'application/x-amz-json-1.1',\n", + " 'content-length': '104',\n", + " 'date': 'Mon, 11 Jul 2022 14:29:46 GMT'},\n", + " 'RetryAttempts': 0}}" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# for _ in tqdm(range(0, 10)):\n", + "events_client.put_events(\n", + " Entries=[\n", + " {\n", + " 'DetailType': 'Invoke Lambda Function',\n", + " 'Source': 'aws',\n", + " 'EventBusName': 'default',\n", + " 'Detail' : '{\"triggerError\": True}'\n", + " }\n", + " ]\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "d1bc94cc", + "metadata": {}, + "source": [ + "### Send lots of events" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0060a197", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "802026b83caa487a851035a037dbbe13", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/10000 [00:00