diff --git a/API.md b/API.md
index fabb8522..70d2f9f6 100644
--- a/API.md
+++ b/API.md
@@ -1062,6 +1062,8 @@ new MonitoringFacade(scope: Construct, id: string, props?: MonitoringFacadeProps
| createdAlarmsWithDisambiguator
| Returns a subset of created alarms that are marked by a specific disambiguator. |
| createdAlarmsWithTag
| Returns a subset of created alarms that are marked by a specific custom tag. |
| createdCompositeAlarms
| Returns the added composite alarms. |
+| createdCompositeAlarmsWithDisambiguator
| Returns a subset of created composite alarms that are marked by a specific disambiguator. |
+| createdCompositeAlarmsWithTag
| Returns a subset of created composite alarms that are marked by a specific custom tag. |
| createdDashboard
| *No description.* |
| createdDashboardSegments
| Returns all the added segments. |
| createdMonitorings
| Returns the added segments that subclass {@link Monitoring}. |
@@ -1331,13 +1333,12 @@ A function that will accept a source alarm and determine whether and how a new a
##### `createCompositeAlarmUsingDisambiguator`
```typescript
-public createCompositeAlarmUsingDisambiguator(alarmDisambiguator: string, props?: AddCompositeAlarmProps): CompositeAlarm
+public createCompositeAlarmUsingDisambiguator(alarmDisambiguator: string, props?: AddCompositeAlarmProps, allowNestedCompositeAlarms?: boolean): CompositeAlarm
```
Finds a subset of created alarms that are marked by a specific disambiguator and creates a composite alarm.
-This composite alarm is created with an 'OR' condition, so it triggers with any child alarm.
-NOTE: This composite alarm is not added among other alarms, so it is not returned by createdAlarms() calls.
+NOTE: This composite alarm is not returned in createdAlarms() calls, use createdCompositeAlarms() instead.
###### `alarmDisambiguator`Required
@@ -1355,16 +1356,26 @@ customization options.
---
+###### `allowNestedCompositeAlarms`Optional
+
+- *Type:* boolean
+
+whether to allow nested composite alarms.
+
+If set to true, previously created composite alarms with the matching disambiguator will be included in the composite alarm.
+
+---
+
##### `createCompositeAlarmUsingTag`
```typescript
-public createCompositeAlarmUsingTag(customTag: string, props?: AddCompositeAlarmProps): CompositeAlarm
+public createCompositeAlarmUsingTag(customTag: string, props?: AddCompositeAlarmProps, allowNestedCompositeAlarms?: boolean): CompositeAlarm
```
Finds a subset of created alarms that are marked by a specific custom tag and creates a composite alarm.
-This composite alarm is created with an 'OR' condition, so it triggers with any child alarm.
-NOTE: This composite alarm is not added among other alarms, so it is not returned by createdAlarms() calls.
+This composite alarm is created with an 'OR' condition so it triggers with any child alarm.
+NOTE: This composite alarm is not returned in createdAlarms() calls, use createdCompositeAlarms() instead.
###### `customTag`Required
@@ -1382,6 +1393,16 @@ customization options.
---
+###### `allowNestedCompositeAlarms`Optional
+
+- *Type:* boolean
+
+whether to allow nested composite alarms.
+
+If set to true, previously created composite alarms with the matching tag will be included in the composite alarm.
+
+---
+
##### ~~`createdAlarmDashboard`~~
```typescript
@@ -1436,6 +1457,38 @@ public createdCompositeAlarms(): CompositeAlarm[]
Returns the added composite alarms.
+##### `createdCompositeAlarmsWithDisambiguator`
+
+```typescript
+public createdCompositeAlarmsWithDisambiguator(disambiguator: string): CompositeAlarm[]
+```
+
+Returns a subset of created composite alarms that are marked by a specific disambiguator.
+
+###### `disambiguator`Required
+
+- *Type:* string
+
+disambiguator to filter alarms by.
+
+---
+
+##### `createdCompositeAlarmsWithTag`
+
+```typescript
+public createdCompositeAlarmsWithTag(customTag: string): CompositeAlarm[]
+```
+
+Returns a subset of created composite alarms that are marked by a specific custom tag.
+
+###### `customTag`Required
+
+- *Type:* string
+
+tag to filter alarms by.
+
+---
+
##### ~~`createdDashboard`~~
```typescript
@@ -12239,6 +12292,58 @@ public readonly addFailedBuildRateAlarm: {[ key: string ]: ErrorRateThreshold};
---
+### CompositeAlarmWithMetadata
+
+Composite alarm with metadata.
+
+#### Initializer
+
+```typescript
+import { CompositeAlarmWithMetadata } from 'cdk-monitoring-constructs'
+
+const compositeAlarmWithMetadata: CompositeAlarmWithMetadata = { ... }
+```
+
+#### Properties
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| alarm
| aws-cdk-lib.aws_cloudwatch.CompositeAlarm
| *No description.* |
+| customTags
| string[]
| *No description.* |
+| disambiguator
| string
| *No description.* |
+
+---
+
+##### `alarm`Required
+
+```typescript
+public readonly alarm: CompositeAlarm;
+```
+
+- *Type:* aws-cdk-lib.aws_cloudwatch.CompositeAlarm
+
+---
+
+##### `customTags`Optional
+
+```typescript
+public readonly customTags: string[];
+```
+
+- *Type:* string[]
+
+---
+
+##### `disambiguator`Optional
+
+```typescript
+public readonly disambiguator: string;
+```
+
+- *Type:* string
+
+---
+
### ConsumedCapacityThreshold
#### Initializer
@@ -54040,7 +54145,8 @@ new AlarmFactory(alarmScope: Construct, props: AlarmFactoryProps)
| **Name** | **Description** |
| --- | --- |
| addAlarm
| *No description.* |
-| addCompositeAlarm
| *No description.* |
+| addCompositeAlarm
| Creates a composite alarm from a collection of alarms and composite alarms. |
+| addCompositeAlarmFromAlarmBases
| Creates a composite alarm from a collection of alarm base objects. |
---
@@ -54065,19 +54171,59 @@ public addAlarm(metric: Metric | MathExpression, props: AddAlarmProps): AlarmWit
##### `addCompositeAlarm`
```typescript
-public addCompositeAlarm(alarms: AlarmWithAnnotation[], props: AddCompositeAlarmProps): CompositeAlarm
+public addCompositeAlarm(alarms: AlarmWithAnnotation[], props: AddCompositeAlarmProps, compositeAlarms?: CompositeAlarm[]): CompositeAlarm
```
+Creates a composite alarm from a collection of alarms and composite alarms.
+
###### `alarms`Required
- *Type:* AlarmWithAnnotation[]
+Array of individual alarms to be composed.
+
---
###### `props`Required
- *Type:* AddCompositeAlarmProps
+Customization options for the composite alarm.
+
+---
+
+###### `compositeAlarms`Optional
+
+- *Type:* aws-cdk-lib.aws_cloudwatch.CompositeAlarm[]
+
+Optional array of composite alarms to be composed together with regular alarms.
+
+---
+
+##### `addCompositeAlarmFromAlarmBases`
+
+```typescript
+public addCompositeAlarmFromAlarmBases(alarms: AlarmBase[], props: AddCompositeAlarmProps): CompositeAlarm
+```
+
+Creates a composite alarm from a collection of alarm base objects.
+
+This allows creation of composite alarms that include both metric alarms and other composite alarms.
+
+###### `alarms`Required
+
+- *Type:* aws-cdk-lib.aws_cloudwatch.AlarmBase[]
+
+Array of AlarmBase objects (can include both Alarm and CompositeAlarm).
+
+---
+
+###### `props`Required
+
+- *Type:* AddCompositeAlarmProps
+
+Customization options for the composite alarm.
+
---
diff --git a/lib/common/alarm/AlarmFactory.ts b/lib/common/alarm/AlarmFactory.ts
index 990f8d5e..b5f6489c 100644
--- a/lib/common/alarm/AlarmFactory.ts
+++ b/lib/common/alarm/AlarmFactory.ts
@@ -821,9 +821,37 @@ export class AlarmFactory {
};
}
+ /**
+ * Creates a composite alarm from a collection of alarms and composite alarms.
+ *
+ * @param alarms Array of individual alarms to be composed
+ * @param props Customization options for the composite alarm
+ * @param compositeAlarms Optional array of composite alarms to be composed together with regular alarms
+ * @returns Newly created composite alarm
+ */
addCompositeAlarm(
alarms: AlarmWithAnnotation[],
props: AddCompositeAlarmProps,
+ compositeAlarms: CompositeAlarm[] = [],
+ ): CompositeAlarm {
+ const alarmBases = [
+ ...alarms.map((alarm) => alarm.alarm),
+ ...compositeAlarms,
+ ] as AlarmBase[];
+ return this.addCompositeAlarmFromAlarmBases(alarmBases, props);
+ }
+
+ /**
+ * Creates a composite alarm from a collection of alarm base objects.
+ * This allows creation of composite alarms that include both metric alarms and other composite alarms.
+ *
+ * @param alarms Array of AlarmBase objects (can include both Alarm and CompositeAlarm)
+ * @param props Customization options for the composite alarm
+ * @returns Newly created composite alarm
+ */
+ addCompositeAlarmFromAlarmBases(
+ alarms: AlarmBase[],
+ props: AddCompositeAlarmProps,
): CompositeAlarm {
const actionsEnabled = this.determineActionsEnabled(
props?.actionsEnabled,
@@ -840,7 +868,10 @@ export class AlarmFactory {
props?.documentationLink,
);
const dedupeString = this.alarmNamingStrategy.getDedupeString(namingInput);
- const alarmRule = this.determineCompositeAlarmRule(alarms, props);
+ const alarmRule = this.determineCompositeAlarmRuleFromAlarmBases(
+ alarms,
+ props,
+ );
const alarm = new CompositeAlarm(this.alarmScope, alarmName, {
compositeAlarmName: alarmName,
@@ -867,8 +898,23 @@ export class AlarmFactory {
protected determineCompositeAlarmRule(
alarms: AlarmWithAnnotation[],
props: AddCompositeAlarmProps,
+ compositeAlarms: CompositeAlarm[] = [],
): IAlarmRule {
- const alarmRules = alarms.map((alarm) => alarm.alarmRuleWhenAlarming);
+ const alarmBases = [
+ ...alarms.map((alarm) => alarm.alarm),
+ ...compositeAlarms,
+ ] as AlarmBase[];
+ return this.determineCompositeAlarmRuleFromAlarmBases(alarmBases, props);
+ }
+
+ protected determineCompositeAlarmRuleFromAlarmBases(
+ alarms: AlarmBase[],
+ props: AddCompositeAlarmProps,
+ ): IAlarmRule {
+ const alarmRules = alarms.map((alarm) =>
+ AlarmRule.fromAlarm(alarm, AlarmState.ALARM),
+ );
+
const operator = props.compositeOperator ?? CompositeAlarmOperator.OR;
switch (operator) {
case CompositeAlarmOperator.AND:
diff --git a/lib/facade/MonitoringFacade.ts b/lib/facade/MonitoringFacade.ts
index 284456fb..bfa6cfff 100644
--- a/lib/facade/MonitoringFacade.ts
+++ b/lib/facade/MonitoringFacade.ts
@@ -1,5 +1,10 @@
import { Aspects, Stack } from "aws-cdk-lib";
-import { CompositeAlarm, Dashboard, IWidget } from "aws-cdk-lib/aws-cloudwatch";
+import {
+ AlarmBase,
+ CompositeAlarm,
+ Dashboard,
+ IWidget,
+} from "aws-cdk-lib/aws-cloudwatch";
import { Construct } from "constructs";
import { MonitoringAspectProps } from "./IMonitoringAspect";
@@ -162,6 +167,15 @@ export interface MonitoringFacadeProps {
readonly dashboardFactory?: IDynamicDashboardFactory;
}
+/**
+ * Composite alarm with metadata.
+ */
+export interface CompositeAlarmWithMetadata {
+ readonly alarm: CompositeAlarm;
+ readonly customTags?: string[];
+ readonly disambiguator?: string;
+}
+
/**
* An implementation of a {@link MonitoringScope}.
*
@@ -178,7 +192,7 @@ export class MonitoringFacade extends MonitoringScope {
| IDashboardSegment
| IDynamicDashboardSegment
)[];
- protected readonly createdComposites: CompositeAlarm[];
+ protected readonly createdComposites: CompositeAlarmWithMetadata[];
protected readonly createdClones: AlarmWithAnnotation[];
constructor(scope: Construct, id: string, props?: MonitoringFacadeProps) {
@@ -325,7 +339,31 @@ export class MonitoringFacade extends MonitoringScope {
* Returns the added composite alarms.
*/
createdCompositeAlarms(): CompositeAlarm[] {
- return this.createdComposites;
+ return this.createdComposites.map(({ alarm }) => alarm);
+ }
+
+ /**
+ * Returns a subset of created composite alarms that are marked by a specific custom tag.
+ *
+ * @param customTag tag to filter alarms by
+ */
+ createdCompositeAlarmsWithTag(customTag: string): CompositeAlarm[] {
+ return this.createdComposites
+ .filter(({ customTags }) => customTags?.includes(customTag))
+ .map(({ alarm }) => alarm);
+ }
+
+ /**
+ * Returns a subset of created composite alarms that are marked by a specific disambiguator.
+ *
+ * @param disambiguator disambiguator to filter alarms by
+ */
+ createdCompositeAlarmsWithDisambiguator(
+ disambiguator: string,
+ ): CompositeAlarm[] {
+ return this.createdComposites
+ .filter(({ disambiguator: d }) => d === disambiguator)
+ .map(({ alarm }) => alarm);
}
/**
@@ -349,53 +387,91 @@ export class MonitoringFacade extends MonitoringScope {
/**
* Finds a subset of created alarms that are marked by a specific custom tag and creates a composite alarm.
- * This composite alarm is created with an 'OR' condition, so it triggers with any child alarm.
- * NOTE: This composite alarm is not added among other alarms, so it is not returned by createdAlarms() calls.
+ * This composite alarm is created with an 'OR' condition so it triggers with any child alarm.
+ * NOTE: This composite alarm is not returned in createdAlarms() calls, use createdCompositeAlarms() instead.
*
* @param customTag tag to filter alarms by
* @param props customization options
+ * @param allowNestedCompositeAlarms whether to allow nested composite alarms. If set to true, previously created composite alarms with the matching tag will be included in the composite alarm.
*/
createCompositeAlarmUsingTag(
customTag: string,
props?: AddCompositeAlarmProps,
+ allowNestedCompositeAlarms: boolean = false,
): CompositeAlarm | undefined {
- const alarms = this.createdAlarmsWithTag(customTag);
- if (alarms.length > 0) {
+ const metricAlarms = this.createdAlarmsWithTag(customTag).map(
+ (a) => a.alarm,
+ );
+ const compositeAlarms = allowNestedCompositeAlarms
+ ? this.createdCompositeAlarmsWithTag(customTag)
+ : [];
+ const allAlarms = [...metricAlarms, ...compositeAlarms] as AlarmBase[];
+
+ if (allAlarms.length > 0) {
const disambiguator = props?.disambiguator ?? customTag;
const alarmFactory = this.createAlarmFactory("Composite");
- const composite = alarmFactory.addCompositeAlarm(alarms, {
- ...(props ?? {}),
+ const composite = alarmFactory.addCompositeAlarmFromAlarmBases(
+ allAlarms,
+ {
+ ...(props ?? {}),
+ disambiguator,
+ },
+ );
+
+ this.createdComposites.push({
+ alarm: composite,
+ customTags: props?.customTags,
disambiguator,
});
- this.createdComposites.push(composite);
+
return composite;
}
+
return undefined;
}
/**
* Finds a subset of created alarms that are marked by a specific disambiguator and creates a composite alarm.
- * This composite alarm is created with an 'OR' condition, so it triggers with any child alarm.
- * NOTE: This composite alarm is not added among other alarms, so it is not returned by createdAlarms() calls.
+ *
+ * NOTE: This composite alarm is not returned in createdAlarms() calls, use createdCompositeAlarms() instead.
*
* @param alarmDisambiguator disambiguator to filter alarms by
* @param props customization options
+ * @param allowNestedCompositeAlarms whether to allow nested composite alarms. If set to true, previously created composite alarms with the matching disambiguator will be included in the composite alarm.
*/
createCompositeAlarmUsingDisambiguator(
alarmDisambiguator: string,
props?: AddCompositeAlarmProps,
+ allowNestedCompositeAlarms: boolean = false,
): CompositeAlarm | undefined {
- const alarms = this.createdAlarmsWithDisambiguator(alarmDisambiguator);
- if (alarms.length > 0) {
+ const metricAlarms = this.createdAlarmsWithDisambiguator(
+ alarmDisambiguator,
+ ).map((a) => a.alarm);
+ const compositeAlarms = allowNestedCompositeAlarms
+ ? this.createdCompositeAlarmsWithDisambiguator(alarmDisambiguator)
+ : [];
+ const allAlarms = [...metricAlarms, ...compositeAlarms] as AlarmBase[];
+
+ if (allAlarms.length > 0) {
const disambiguator = props?.disambiguator ?? alarmDisambiguator;
const alarmFactory = this.createAlarmFactory("Composite");
- const composite = alarmFactory.addCompositeAlarm(alarms, {
- ...(props ?? {}),
+ const composite = alarmFactory.addCompositeAlarmFromAlarmBases(
+ allAlarms,
+ {
+ ...(props ?? {}),
+ disambiguator,
+ },
+ );
+
+ this.createdComposites.push({
+ alarm: composite,
+ customTags: props?.customTags,
disambiguator,
});
- this.createdComposites.push(composite);
+
return composite;
}
+
return undefined;
}
diff --git a/test/common/alarm/AlarmFactory.test.ts b/test/common/alarm/AlarmFactory.test.ts
index 524cca09..c6ed6f7b 100644
--- a/test/common/alarm/AlarmFactory.test.ts
+++ b/test/common/alarm/AlarmFactory.test.ts
@@ -8,6 +8,7 @@ import {
Metric,
Shading,
TreatMissingData,
+ AlarmBase,
} from "aws-cdk-lib/aws-cloudwatch";
import { Topic } from "aws-cdk-lib/aws-sns";
import { Construct } from "constructs";
@@ -793,3 +794,78 @@ test("addAlarm: custom metric adjuster, applies it and DefaultMetricAdjuster aft
)?.period,
).toEqual(Duration.minutes(10).toSeconds());
});
+
+test("addCompositeAlarmFromAlarmBases: supports combining metric alarms and composite alarms", () => {
+ const stack = new Stack();
+ const factory = new AlarmFactory(stack, {
+ globalMetricDefaults,
+ globalAlarmDefaults: globalAlarmDefaultsWithDisambiguator,
+ localAlarmNamePrefix: "prefix",
+ });
+ const metric = new Metric({
+ namespace: "DummyNamespace",
+ metricName: "DummyMetric",
+ });
+ const alarm1 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm1",
+ alarmDescription: "Testing alarm 1",
+ threshold: 1,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+ const alarm2 = factory.addAlarm(metric, {
+ alarmNameSuffix: "Alarm2",
+ alarmDescription: "Testing alarm 2",
+ threshold: 2,
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
+ treatMissingData: TreatMissingData.MISSING,
+ });
+
+ // Extract the underlying AlarmBase objects
+ const alarm1Base: AlarmBase = alarm1.alarm;
+
+ // Create first level composite alarm using the base method
+ const firstLevelComposite = factory.addCompositeAlarmFromAlarmBases(
+ [alarm1Base],
+ {
+ disambiguator: "FirstLevelBase",
+ alarmNameSuffix: "FirstLevelBase",
+ },
+ );
+
+ // Extract the underlying AlarmBase for alarm2
+ const alarm2Base: AlarmBase = alarm2.alarm;
+
+ // Create second level composite alarm that includes a metric alarm and the first level composite
+ factory.addCompositeAlarmFromAlarmBases([alarm2Base, firstLevelComposite], {
+ disambiguator: "SecondLevelBase",
+ alarmNameSuffix: "SecondLevelBase",
+ compositeOperator: CompositeAlarmOperator.OR,
+ });
+
+ // Verify the template
+ const template = Template.fromStack(stack);
+
+ // We should have two metric alarms and two composite alarms
+ template.resourceCountIs("AWS::CloudWatch::Alarm", 2);
+ template.resourceCountIs("AWS::CloudWatch::CompositeAlarm", 2);
+
+ // The first level composite should reference only alarm1
+ template.hasResourceProperties("AWS::CloudWatch::CompositeAlarm", {
+ AlarmName: Match.stringLikeRegexp("-FirstLevelBase$"),
+ AlarmRule: Match.objectLike({
+ "Fn::Join": Match.anyValue(),
+ }),
+ });
+
+ // The second level composite should reference both alarm2 and the first level composite
+ template.hasResourceProperties("AWS::CloudWatch::CompositeAlarm", {
+ AlarmName: Match.stringLikeRegexp("-SecondLevelBase$"),
+ AlarmRule: Match.objectLike({
+ "Fn::Join": Match.anyValue(),
+ }),
+ });
+
+ // Snapshot verification
+ expect(template).toMatchSnapshot();
+});
diff --git a/test/common/alarm/__snapshots__/AlarmFactory.test.ts.snap b/test/common/alarm/__snapshots__/AlarmFactory.test.ts.snap
index 7dbd76be..f390e039 100644
--- a/test/common/alarm/__snapshots__/AlarmFactory.test.ts.snap
+++ b/test/common/alarm/__snapshots__/AlarmFactory.test.ts.snap
@@ -514,3 +514,131 @@ Object {
},
}
`;
+
+exports[`addCompositeAlarmFromAlarmBases: supports combining metric alarms and composite alarms 1`] = `
+Object {
+ "Parameters": Object {
+ "BootstrapVersion": Object {
+ "Default": "/cdk-bootstrap/hnb659fds/version",
+ "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]",
+ "Type": "AWS::SSM::Parameter::Value",
+ },
+ },
+ "Resources": Object {
+ "DummyServiceAlarmsprefixAlarm1F4FCF957": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Testing alarm 1",
+ "AlarmName": "DummyServiceAlarms-prefix-Alarm1",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 6,
+ "EvaluationPeriods": 6,
+ "MetricName": "DummyMetric",
+ "Namespace": "DummyNamespace",
+ "Period": 300,
+ "Statistic": "Average",
+ "Threshold": 1,
+ "TreatMissingData": "missing",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "DummyServiceAlarmsprefixAlarm25ADF8B37": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Testing alarm 2",
+ "AlarmName": "DummyServiceAlarms-prefix-Alarm2",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 6,
+ "EvaluationPeriods": 6,
+ "MetricName": "DummyMetric",
+ "Namespace": "DummyNamespace",
+ "Period": 300,
+ "Statistic": "Average",
+ "Threshold": 2,
+ "TreatMissingData": "missing",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "DummyServiceAlarmsprefixFirstLevelBase6385B032": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Composite alarm",
+ "AlarmName": "DummyServiceAlarms-prefix-FirstLevelBase",
+ "AlarmRule": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "(ALARM(\\"",
+ Object {
+ "Fn::GetAtt": Array [
+ "DummyServiceAlarmsprefixAlarm1F4FCF957",
+ "Arn",
+ ],
+ },
+ "\\"))",
+ ],
+ ],
+ },
+ },
+ "Type": "AWS::CloudWatch::CompositeAlarm",
+ },
+ "DummyServiceAlarmsprefixSecondLevelBaseE5D5046F": Object {
+ "Properties": Object {
+ "ActionsEnabled": false,
+ "AlarmDescription": "Composite alarm",
+ "AlarmName": "DummyServiceAlarms-prefix-SecondLevelBase",
+ "AlarmRule": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "(ALARM(\\"",
+ Object {
+ "Fn::GetAtt": Array [
+ "DummyServiceAlarmsprefixAlarm25ADF8B37",
+ "Arn",
+ ],
+ },
+ "\\") OR ALARM(\\"",
+ Object {
+ "Fn::GetAtt": Array [
+ "DummyServiceAlarmsprefixFirstLevelBase6385B032",
+ "Arn",
+ ],
+ },
+ "\\"))",
+ ],
+ ],
+ },
+ },
+ "Type": "AWS::CloudWatch::CompositeAlarm",
+ },
+ },
+ "Rules": Object {
+ "CheckBootstrapVersion": Object {
+ "Assertions": Array [
+ Object {
+ "Assert": Object {
+ "Fn::Not": Array [
+ Object {
+ "Fn::Contains": Array [
+ Array [
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ ],
+ Object {
+ "Ref": "BootstrapVersion",
+ },
+ ],
+ },
+ ],
+ },
+ "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.",
+ },
+ ],
+ },
+ },
+}
+`;
diff --git a/test/facade/MonitoringFacade.test.ts b/test/facade/MonitoringFacade.test.ts
index 8af20a8d..0c48e6ad 100644
--- a/test/facade/MonitoringFacade.test.ts
+++ b/test/facade/MonitoringFacade.test.ts
@@ -393,4 +393,222 @@ describe("test of defaults", () => {
// Snapshot verification
expect(template).toMatchSnapshot();
});
+
+ test("createCompositeAlarmUsingTag creates a composite alarm from matching alarms", () => {
+ const stack = new Stack();
+ const onAlarmTopic = new Topic(stack, "OnAlarmTopicForComposites", {
+ topicName: "CompositeAlarmTopic",
+ });
+ const facade = new MonitoringFacade(stack, "CompositeAlarmTestFacade", {
+ metricFactoryDefaults: {
+ namespace: "CompositeTest",
+ },
+ alarmFactoryDefaults: {
+ alarmNamePrefix: "CompTest",
+ actionsEnabled: true,
+ datapointsToAlarm: 3,
+ action: notifySns(onAlarmTopic),
+ },
+ });
+
+ // Create a set of metric alarms with the same tag
+ facade.monitorDynamoTable({
+ table: Table.fromTableName(stack, "CompositeTestTable", "TestTable"),
+ addAverageSuccessfulGetItemLatencyAlarm: {
+ Critical: {
+ maxLatency: Duration.seconds(10),
+ customTags: ["comp-test-tag"],
+ },
+ },
+ addReadThrottledEventsCountAlarm: {
+ Critical: {
+ maxThrottledEventsThreshold: 100,
+ customTags: ["comp-test-tag"],
+ },
+ },
+ });
+
+ // Create first level composite alarm from metric alarms
+ const firstLevelComposite = facade.createCompositeAlarmUsingTag(
+ "comp-test-tag",
+ {
+ disambiguator: "FirstLevel",
+ alarmNameSuffix: "FirstLevel",
+ customTags: ["comp-test-nested"],
+ },
+ true, // allow nested composite alarms
+ );
+
+ expect(firstLevelComposite).toBeDefined();
+
+ // Create more metric alarms with a different tag
+ facade.monitorLambdaFunction({
+ lambdaFunction: Function.fromFunctionAttributes(
+ stack,
+ "CompositeTestFunction",
+ {
+ functionArn: `arn:aws:lambda:us-west-2:01234567890:function:TestFunction`,
+ sameEnvironment: true,
+ },
+ ),
+ addFaultRateAlarm: {
+ Critical: {
+ maxErrorRate: 0.5,
+ customTags: ["comp-test-nested"],
+ },
+ },
+ });
+
+ // Create second level composite alarm that includes metric alarms and the first level composite
+ const secondLevelComposite = facade.createCompositeAlarmUsingTag(
+ "comp-test-nested",
+ {
+ disambiguator: "SecondLevel",
+ alarmNameSuffix: "SecondLevel",
+ },
+ true, // allow nested composite alarms
+ );
+
+ expect(secondLevelComposite).toBeDefined();
+
+ // Verify the generated resources
+ const template = Template.fromStack(stack);
+
+ // Find first level composite alarm
+ const firstLevelCompositeAlarm = template.findResources(
+ "AWS::CloudWatch::CompositeAlarm",
+ Match.objectLike({
+ Properties: {
+ AlarmName: Match.stringLikeRegexp("-FirstLevel$"),
+ AlarmRule: Match.anyValue(),
+ },
+ }),
+ );
+ expect(Object.keys(firstLevelCompositeAlarm).length).toBe(1);
+
+ // Find second level composite alarm
+ const secondLevelCompositeAlarm = template.findResources(
+ "AWS::CloudWatch::CompositeAlarm",
+ Match.objectLike({
+ Properties: {
+ AlarmName: Match.stringLikeRegexp("-SecondLevel$"),
+ AlarmRule: Match.anyValue(),
+ },
+ }),
+ );
+ expect(Object.keys(secondLevelCompositeAlarm).length).toBe(1);
+
+ // Verify total number of alarms (3 metric alarms + 2 composite alarms)
+ const allAlarms = [
+ ...Object.keys(template.findResources("AWS::CloudWatch::Alarm")),
+ ...Object.keys(template.findResources("AWS::CloudWatch::CompositeAlarm")),
+ ];
+ expect(allAlarms.length).toBe(5);
+
+ // Snapshot verification
+ expect(template).toMatchSnapshot();
+ });
+
+ test("composite alarms are not nested when allowNestedCompositeAlarms is false", () => {
+ const stack = new Stack();
+ const onAlarmTopic = new Topic(stack, "AlarmTopic");
+ const facade = new MonitoringFacade(stack, "NestingTestFacade", {
+ alarmFactoryDefaults: {
+ alarmNamePrefix: "NestTest",
+ actionsEnabled: true,
+ action: notifySns(onAlarmTopic),
+ },
+ });
+
+ // Create first set of metric alarms with the "FirstGroup" tag
+ facade.monitorDynamoTable({
+ table: Table.fromTableName(stack, "FirstGroupTable", "FirstGroupTable"),
+ addAverageSuccessfulGetItemLatencyAlarm: {
+ Critical: {
+ maxLatency: Duration.seconds(10),
+ customTags: ["FirstGroup"],
+ },
+ },
+ });
+
+ // Create first level composite alarm from the "FirstGroup" tag
+ const firstLevelComposite = facade.createCompositeAlarmUsingTag(
+ "FirstGroup",
+ {
+ disambiguator: "FirstLevel",
+ alarmNameSuffix: "FirstLevel",
+ customTags: ["FirstLevelTag"],
+ },
+ );
+ expect(firstLevelComposite).toBeDefined();
+
+ // Create second set of metric alarms
+ facade.monitorLambdaFunction({
+ lambdaFunction: Function.fromFunctionName(
+ stack,
+ "SecondGroupFunction",
+ "SecondGroupFunction",
+ ),
+ addFaultRateAlarm: {
+ Critical: {
+ maxErrorRate: 0.1,
+ customTags: ["FirstLevelTag"],
+ },
+ },
+ });
+
+ // Create second level composite alarm with allowNestedCompositeAlarms = false
+ const secondLevelComposite = facade.createCompositeAlarmUsingTag(
+ "FirstLevelTag",
+ {
+ disambiguator: "SecondLevel",
+ alarmNameSuffix: "SecondLevel",
+ },
+ false, // Explicitly set allowNestedCompositeAlarms to false
+ );
+ expect(secondLevelComposite).toBeDefined();
+
+ // Verify the resources in the resulting template
+ const template = Template.fromStack(stack);
+
+ // Check that both composite alarms exist
+ const compositeAlarms = template.findResources(
+ "AWS::CloudWatch::CompositeAlarm",
+ );
+ expect(Object.keys(compositeAlarms).length).toBe(2);
+
+ // Find all the alarm ARNs that are referenced in the second level composite's AlarmRule
+ let secondLevelCompositeLogicalId: string | undefined;
+
+ // Find the logical ID of the second level composite alarm
+ for (const [logicalId, resource] of Object.entries(compositeAlarms)) {
+ if (resource.Properties?.AlarmName?.toString().includes("-SecondLevel")) {
+ secondLevelCompositeLogicalId = logicalId;
+ break;
+ }
+ }
+
+ expect(secondLevelCompositeLogicalId).toBeDefined();
+
+ // Get the AlarmRule from the second level composite alarm
+ const secondLevelRule =
+ compositeAlarms[secondLevelCompositeLogicalId!].Properties.AlarmRule;
+
+ // Convert the rule to string for inspection
+ const ruleString = JSON.stringify(secondLevelRule);
+
+ // Verify the rule doesn't include a reference to the first level composite alarm
+ // by checking it doesn't contain FirstLevel in the alarm name
+ expect(ruleString).not.toContain("FirstLevel");
+
+ // Total resources should be 2 metric alarms + 2 composite alarms = 4
+ const allAlarms = [
+ ...Object.keys(template.findResources("AWS::CloudWatch::Alarm")),
+ ...Object.keys(template.findResources("AWS::CloudWatch::CompositeAlarm")),
+ ];
+ expect(allAlarms.length).toBe(4);
+
+ // Snapshot verification
+ expect(template).toMatchSnapshot();
+ });
});
diff --git a/test/facade/__snapshots__/MonitoringFacade.test.ts.snap b/test/facade/__snapshots__/MonitoringFacade.test.ts.snap
index 98987d7f..0ba4ee11 100644
--- a/test/facade/__snapshots__/MonitoringFacade.test.ts.snap
+++ b/test/facade/__snapshots__/MonitoringFacade.test.ts.snap
@@ -517,6 +517,1050 @@ Object {
}
`;
+exports[`test of defaults composite alarms are not nested when allowNestedCompositeAlarms is false 1`] = `
+Object {
+ "Parameters": Object {
+ "BootstrapVersion": Object {
+ "Default": "/cdk-bootstrap/hnb659fds/version",
+ "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]",
+ "Type": "AWS::SSM::Parameter::Value",
+ },
+ },
+ "Resources": Object {
+ "AlarmTopicD01E77F9": Object {
+ "Type": "AWS::SNS::Topic",
+ },
+ "NestingTestFacadeNestTestCompositeFirstLevel5ED59FF5": Object {
+ "Properties": Object {
+ "ActionsEnabled": true,
+ "AlarmActions": Array [
+ Object {
+ "Ref": "AlarmTopicD01E77F9",
+ },
+ ],
+ "AlarmDescription": "Composite alarm",
+ "AlarmName": "NestTest-Composite-FirstLevel",
+ "AlarmRule": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "(ALARM(\\"",
+ Object {
+ "Fn::GetAtt": Array [
+ "NestingTestFacadeNestTestFirstGroupTableLatencyAverageGetItemCriticalF377C877",
+ "Arn",
+ ],
+ },
+ "\\"))",
+ ],
+ ],
+ },
+ },
+ "Type": "AWS::CloudWatch::CompositeAlarm",
+ },
+ "NestingTestFacadeNestTestCompositeSecondLevelEC1274EC": Object {
+ "Properties": Object {
+ "ActionsEnabled": true,
+ "AlarmActions": Array [
+ Object {
+ "Ref": "AlarmTopicD01E77F9",
+ },
+ ],
+ "AlarmDescription": "Composite alarm",
+ "AlarmName": "NestTest-Composite-SecondLevel",
+ "AlarmRule": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "(ALARM(\\"",
+ Object {
+ "Fn::GetAtt": Array [
+ "NestingTestFacadeNestTestSecondGroupFunctionFaultRateCritical0226378C",
+ "Arn",
+ ],
+ },
+ "\\"))",
+ ],
+ ],
+ },
+ },
+ "Type": "AWS::CloudWatch::CompositeAlarm",
+ },
+ "NestingTestFacadeNestTestFirstGroupTableLatencyAverageGetItemCriticalF377C877": Object {
+ "Properties": Object {
+ "ActionsEnabled": true,
+ "AlarmActions": Array [
+ Object {
+ "Ref": "AlarmTopicD01E77F9",
+ },
+ ],
+ "AlarmDescription": "Average latency is too high.",
+ "AlarmName": "NestTest-FirstGroupTable-Latency-Average-GetItem-Critical",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 3,
+ "EvaluationPeriods": 3,
+ "Metrics": Array [
+ Object {
+ "Id": "m1",
+ "Label": "GetItem",
+ "MetricStat": Object {
+ "Metric": Object {
+ "Dimensions": Array [
+ Object {
+ "Name": "Operation",
+ "Value": "GetItem",
+ },
+ Object {
+ "Name": "TableName",
+ "Value": "FirstGroupTable",
+ },
+ ],
+ "MetricName": "SuccessfulRequestLatency",
+ "Namespace": "AWS/DynamoDB",
+ },
+ "Period": 300,
+ "Stat": "Average",
+ },
+ "ReturnData": true,
+ },
+ ],
+ "Threshold": 10000,
+ "TreatMissingData": "notBreaching",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "NestingTestFacadeNestTestSecondGroupFunctionFaultRateCritical0226378C": Object {
+ "Properties": Object {
+ "ActionsEnabled": true,
+ "AlarmActions": Array [
+ Object {
+ "Ref": "AlarmTopicD01E77F9",
+ },
+ ],
+ "AlarmDescription": "Fault rate is too high.",
+ "AlarmName": "NestTest-SecondGroupFunction-Fault-Rate-Critical",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 3,
+ "EvaluationPeriods": 3,
+ "Metrics": Array [
+ Object {
+ "Id": "m1",
+ "Label": "Faults (avg)",
+ "MetricStat": Object {
+ "Metric": Object {
+ "Dimensions": Array [
+ Object {
+ "Name": "FunctionName",
+ "Value": Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ },
+ ],
+ "MetricName": "Errors",
+ "Namespace": "AWS/Lambda",
+ },
+ "Period": 300,
+ "Stat": "Average",
+ },
+ "ReturnData": true,
+ },
+ ],
+ "Threshold": 0.1,
+ "TreatMissingData": "notBreaching",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "NestingTestFacadeNestingTestFacadeDashboardsDashboardA2475CC6": Object {
+ "Properties": Object {
+ "DashboardBody": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "{\\"start\\":\\"-PT8H\\",\\"periodOverride\\":\\"inherit\\",\\"widgets\\":[{\\"type\\":\\"text\\",\\"width\\":24,\\"height\\":1,\\"x\\":0,\\"y\\":0,\\"properties\\":{\\"markdown\\":\\"### Dynamo Table **[FirstGroupTable](https://",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ".console.aws.amazon.com/dynamodb/home?region=",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "#tables:selected=FirstGroupTable)**\\"}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":3,\\"x\\":0,\\"y\\":1,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Read Usage\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\"Consumed\\",\\"expression\\":\\"consumed_rcu_sum/PERIOD(consumed_rcu_sum)\\",\\"id\\":\\"consumed_read_cap\\"}],[\\"AWS/DynamoDB\\",\\"ConsumedReadCapacityUnits\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"consumed_rcu_sum\\"}],[\\"AWS/DynamoDB\\",\\"ProvisionedReadCapacityUnits\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"label\\":\\"Provisioned\\",\\"id\\":\\"provisioned_read_cap\\"}],[{\\"label\\":\\"Utilization\\",\\"expression\\":\\"100*(consumed_read_cap/provisioned_read_cap)\\",\\"yAxis\\":\\"right\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false},\\"right\\":{\\"min\\":0,\\"max\\":100,\\"label\\":\\"%\\",\\"showUnits\\":false}},\\"legend\\":{\\"position\\":\\"right\\"}}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":3,\\"x\\":0,\\"y\\":4,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Write Usage\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\"Consumed\\",\\"expression\\":\\"consumed_wcu_sum/PERIOD(consumed_wcu_sum)\\",\\"id\\":\\"consumed_write_cap\\"}],[\\"AWS/DynamoDB\\",\\"ConsumedWriteCapacityUnits\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"consumed_wcu_sum\\"}],[\\"AWS/DynamoDB\\",\\"ProvisionedWriteCapacityUnits\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"label\\":\\"Provisioned\\",\\"id\\":\\"provisioned_write_cap\\"}],[{\\"label\\":\\"Utilization\\",\\"expression\\":\\"100*(consumed_write_cap/provisioned_write_cap)\\",\\"yAxis\\":\\"right\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false},\\"right\\":{\\"min\\":0,\\"max\\":100,\\"label\\":\\"%\\",\\"showUnits\\":false}},\\"legend\\":{\\"position\\":\\"right\\"}}},{\\"type\\":\\"metric\\",\\"width\\":9,\\"height\\":6,\\"x\\":6,\\"y\\":1,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Latency (Average)\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\" \\",\\"expression\\":\\"SEARCH('{\\\\\\"AWS/DynamoDB\\\\\\",\\\\\\"TableName\\\\\\",\\\\\\"Operation\\\\\\"} \\\\\\"TableName\\\\\\"=\\\\\\"FirstGroupTable\\\\\\" MetricName=\\\\\\"SuccessfulRequestLatency\\\\\\"', 'Average', 300)\\"}]],\\"annotations\\":{\\"horizontal\\":[{\\"label\\":\\"GetItem > 10000 for 3 datapoints within 15 minutes\\",\\"value\\":10000,\\"yAxis\\":\\"left\\"}]},\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"ms\\",\\"showUnits\\":false}},\\"legend\\":{\\"position\\":\\"right\\"}}},{\\"type\\":\\"metric\\",\\"width\\":3,\\"height\\":6,\\"x\\":15,\\"y\\":1,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Throttles\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\"Read\\",\\"expression\\":\\"FILL(readThrottles,0)\\"}],[\\"AWS/DynamoDB\\",\\"ReadThrottleEvents\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"label\\":\\"Read\\",\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"readThrottles\\"}],[{\\"label\\":\\"Write\\",\\"expression\\":\\"FILL(writeThrottles,0)\\"}],[\\"AWS/DynamoDB\\",\\"WriteThrottleEvents\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"label\\":\\"Write\\",\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"writeThrottles\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":6,\\"x\\":18,\\"y\\":1,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Errors\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\"System Errors\\",\\"expression\\":\\"systemErrorGetItem+systemErrorBatchGetItem+systemErrorScan+systemErrorQuery+systemErrorGetRecords+systemErrorPutItem+systemErrorDeleteItem+systemErrorUpdateItem+systemErrorBatchWriteItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"GetItem\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorGetItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"BatchGetItem\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorBatchGetItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"Scan\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorScan\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"Query\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorQuery\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"GetRecords\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorGetRecords\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"PutItem\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorPutItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"DeleteItem\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorDeleteItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"UpdateItem\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorUpdateItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"BatchWriteItem\\",\\"TableName\\",\\"FirstGroupTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorBatchWriteItem\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false}}}},{\\"type\\":\\"text\\",\\"width\\":24,\\"height\\":1,\\"x\\":0,\\"y\\":7,\\"properties\\":{\\"markdown\\":\\"### Lambda Function **[SecondGroupFunction](https://",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ".console.aws.amazon.com/lambda/home?region=",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "#/functions/",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ")**\\"}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":5,\\"x\\":0,\\"y\\":8,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"TPS\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\"TPS\\",\\"expression\\":\\"FILL(requests,0) / PERIOD(requests)\\"}],[\\"AWS/Lambda\\",\\"Invocations\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"Invocations\\",\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"requests\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Rate\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":5,\\"x\\":6,\\"y\\":8,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Latency\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"Duration\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"P50 (avg: \${AVG})\\",\\"stat\\":\\"p50\\"}],[\\"AWS/Lambda\\",\\"Duration\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"P90 (avg: \${AVG})\\",\\"stat\\":\\"p90\\"}],[\\"AWS/Lambda\\",\\"Duration\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"P99 (avg: \${AVG})\\",\\"stat\\":\\"p99\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"ms\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":5,\\"x\\":12,\\"y\\":8,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Errors (rate)\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"Errors\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"Faults (avg)\\"}]],\\"annotations\\":{\\"horizontal\\":[{\\"label\\":\\"Faults (avg) > 0.1 for 3 datapoints within 15 minutes\\",\\"value\\":0.1,\\"yAxis\\":\\"left\\"}]},\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Rate\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":5,\\"x\\":18,\\"y\\":8,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Rates\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"Throttles\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"Throttles (avg)\\"}],[\\"AWS/Lambda\\",\\"ProvisionedConcurrencySpilloverInvocations\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"Provisioned Concurrency Spillovers (avg)\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Rate\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":8,\\"height\\":5,\\"x\\":0,\\"y\\":13,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Invocations\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"Invocations\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"Invocations\\",\\"stat\\":\\"Sum\\"}],[\\"AWS/Lambda\\",\\"Throttles\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"Throttles\\",\\"stat\\":\\"Sum\\"}],[\\"AWS/Lambda\\",\\"ConcurrentExecutions\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"Concurrent\\",\\"stat\\":\\"Maximum\\"}],[\\"AWS/Lambda\\",\\"ProvisionedConcurrencySpilloverInvocations\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"Provisioned Concurrency Spillovers\\",\\"stat\\":\\"Sum\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":8,\\"height\\":5,\\"x\\":8,\\"y\\":13,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Errors\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"Errors\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"Faults\\",\\"stat\\":\\"Sum\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":8,\\"height\\":5,\\"x\\":16,\\"y\\":18,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Iterator\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"IteratorAge\\",\\"FunctionName\\",\\"",
+ Object {
+ "Fn::Select": Array [
+ 6,
+ Object {
+ "Fn::Split": Array [
+ ":",
+ Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "arn:",
+ Object {
+ "Ref": "AWS::Partition",
+ },
+ ":lambda:",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ":",
+ Object {
+ "Ref": "AWS::AccountId",
+ },
+ ":function:SecondGroupFunction",
+ ],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ "\\",{\\"label\\":\\"Iterator Age\\",\\"stat\\":\\"Maximum\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"ms\\",\\"showUnits\\":false}}}}]}",
+ ],
+ ],
+ },
+ "DashboardName": "NestingTestFacade",
+ },
+ "Type": "AWS::CloudWatch::Dashboard",
+ },
+ },
+ "Rules": Object {
+ "CheckBootstrapVersion": Object {
+ "Assertions": Array [
+ Object {
+ "Assert": Object {
+ "Fn::Not": Array [
+ Object {
+ "Fn::Contains": Array [
+ Array [
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ ],
+ Object {
+ "Ref": "BootstrapVersion",
+ },
+ ],
+ },
+ ],
+ },
+ "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.",
+ },
+ ],
+ },
+ },
+}
+`;
+
+exports[`test of defaults createCompositeAlarmUsingTag creates a composite alarm from matching alarms 1`] = `
+Object {
+ "Parameters": Object {
+ "BootstrapVersion": Object {
+ "Default": "/cdk-bootstrap/hnb659fds/version",
+ "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]",
+ "Type": "AWS::SSM::Parameter::Value",
+ },
+ },
+ "Resources": Object {
+ "CompositeAlarmTestFacadeCompTestCompositeFirstLevel21A6FDB8": Object {
+ "Properties": Object {
+ "ActionsEnabled": true,
+ "AlarmActions": Array [
+ Object {
+ "Ref": "OnAlarmTopicForCompositesC70E2B13",
+ },
+ ],
+ "AlarmDescription": "Composite alarm",
+ "AlarmName": "CompTest-Composite-FirstLevel",
+ "AlarmRule": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "(ALARM(\\"",
+ Object {
+ "Fn::GetAtt": Array [
+ "CompositeAlarmTestFacadeCompTestCompositeTestTableReadThrottledEventsCritical6E9971F4",
+ "Arn",
+ ],
+ },
+ "\\") OR ALARM(\\"",
+ Object {
+ "Fn::GetAtt": Array [
+ "CompositeAlarmTestFacadeCompTestCompositeTestTableLatencyAverageGetItemCriticalF6E113E5",
+ "Arn",
+ ],
+ },
+ "\\"))",
+ ],
+ ],
+ },
+ },
+ "Type": "AWS::CloudWatch::CompositeAlarm",
+ },
+ "CompositeAlarmTestFacadeCompTestCompositeSecondLevel94FEC718": Object {
+ "Properties": Object {
+ "ActionsEnabled": true,
+ "AlarmActions": Array [
+ Object {
+ "Ref": "OnAlarmTopicForCompositesC70E2B13",
+ },
+ ],
+ "AlarmDescription": "Composite alarm",
+ "AlarmName": "CompTest-Composite-SecondLevel",
+ "AlarmRule": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "(ALARM(\\"",
+ Object {
+ "Fn::GetAtt": Array [
+ "CompositeAlarmTestFacadeCompTestCompositeTestFunctionFaultRateCritical2102776D",
+ "Arn",
+ ],
+ },
+ "\\") OR ALARM(\\"",
+ Object {
+ "Fn::GetAtt": Array [
+ "CompositeAlarmTestFacadeCompTestCompositeFirstLevel21A6FDB8",
+ "Arn",
+ ],
+ },
+ "\\"))",
+ ],
+ ],
+ },
+ },
+ "Type": "AWS::CloudWatch::CompositeAlarm",
+ },
+ "CompositeAlarmTestFacadeCompTestCompositeTestFunctionFaultRateCritical2102776D": Object {
+ "Properties": Object {
+ "ActionsEnabled": true,
+ "AlarmActions": Array [
+ Object {
+ "Ref": "OnAlarmTopicForCompositesC70E2B13",
+ },
+ ],
+ "AlarmDescription": "Fault rate is too high.",
+ "AlarmName": "CompTest-CompositeTestFunction-Fault-Rate-Critical",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 3,
+ "EvaluationPeriods": 3,
+ "Metrics": Array [
+ Object {
+ "Id": "m1",
+ "Label": "Faults (avg)",
+ "MetricStat": Object {
+ "Metric": Object {
+ "Dimensions": Array [
+ Object {
+ "Name": "FunctionName",
+ "Value": "TestFunction",
+ },
+ ],
+ "MetricName": "Errors",
+ "Namespace": "AWS/Lambda",
+ },
+ "Period": 300,
+ "Stat": "Average",
+ },
+ "ReturnData": true,
+ },
+ ],
+ "Threshold": 0.5,
+ "TreatMissingData": "notBreaching",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "CompositeAlarmTestFacadeCompTestCompositeTestTableLatencyAverageGetItemCriticalF6E113E5": Object {
+ "Properties": Object {
+ "ActionsEnabled": true,
+ "AlarmActions": Array [
+ Object {
+ "Ref": "OnAlarmTopicForCompositesC70E2B13",
+ },
+ ],
+ "AlarmDescription": "Average latency is too high.",
+ "AlarmName": "CompTest-CompositeTestTable-Latency-Average-GetItem-Critical",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 3,
+ "EvaluationPeriods": 3,
+ "Metrics": Array [
+ Object {
+ "Id": "m1",
+ "Label": "GetItem",
+ "MetricStat": Object {
+ "Metric": Object {
+ "Dimensions": Array [
+ Object {
+ "Name": "Operation",
+ "Value": "GetItem",
+ },
+ Object {
+ "Name": "TableName",
+ "Value": "TestTable",
+ },
+ ],
+ "MetricName": "SuccessfulRequestLatency",
+ "Namespace": "AWS/DynamoDB",
+ },
+ "Period": 300,
+ "Stat": "Average",
+ },
+ "ReturnData": true,
+ },
+ ],
+ "Threshold": 10000,
+ "TreatMissingData": "notBreaching",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "CompositeAlarmTestFacadeCompTestCompositeTestTableReadThrottledEventsCritical6E9971F4": Object {
+ "Properties": Object {
+ "ActionsEnabled": true,
+ "AlarmActions": Array [
+ Object {
+ "Ref": "OnAlarmTopicForCompositesC70E2B13",
+ },
+ ],
+ "AlarmDescription": "Read throttled events above threshold.",
+ "AlarmName": "CompTest-CompositeTestTable-Read-Throttled-Events-Critical",
+ "ComparisonOperator": "GreaterThanThreshold",
+ "DatapointsToAlarm": 3,
+ "EvaluationPeriods": 3,
+ "Metrics": Array [
+ Object {
+ "Expression": "FILL(readThrottles,0)",
+ "Id": "expr_1",
+ "Label": "Read",
+ },
+ Object {
+ "Id": "readThrottles",
+ "Label": "Read",
+ "MetricStat": Object {
+ "Metric": Object {
+ "Dimensions": Array [
+ Object {
+ "Name": "TableName",
+ "Value": "TestTable",
+ },
+ ],
+ "MetricName": "ReadThrottleEvents",
+ "Namespace": "AWS/DynamoDB",
+ },
+ "Period": 300,
+ "Stat": "Sum",
+ },
+ "ReturnData": false,
+ },
+ ],
+ "Threshold": 100,
+ "TreatMissingData": "notBreaching",
+ },
+ "Type": "AWS::CloudWatch::Alarm",
+ },
+ "CompositeAlarmTestFacadeCompositeAlarmTestFacadeDashboardsDashboard633A0A44": Object {
+ "Properties": Object {
+ "DashboardBody": Object {
+ "Fn::Join": Array [
+ "",
+ Array [
+ "{\\"start\\":\\"-PT8H\\",\\"periodOverride\\":\\"inherit\\",\\"widgets\\":[{\\"type\\":\\"text\\",\\"width\\":24,\\"height\\":1,\\"x\\":0,\\"y\\":0,\\"properties\\":{\\"markdown\\":\\"### Dynamo Table **[CompositeTestTable](https://",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ".console.aws.amazon.com/dynamodb/home?region=",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "#tables:selected=TestTable)**\\"}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":3,\\"x\\":0,\\"y\\":1,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Read Usage\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\"Consumed\\",\\"expression\\":\\"consumed_rcu_sum/PERIOD(consumed_rcu_sum)\\",\\"id\\":\\"consumed_read_cap\\"}],[\\"AWS/DynamoDB\\",\\"ConsumedReadCapacityUnits\\",\\"TableName\\",\\"TestTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"consumed_rcu_sum\\"}],[\\"AWS/DynamoDB\\",\\"ProvisionedReadCapacityUnits\\",\\"TableName\\",\\"TestTable\\",{\\"label\\":\\"Provisioned\\",\\"id\\":\\"provisioned_read_cap\\"}],[{\\"label\\":\\"Utilization\\",\\"expression\\":\\"100*(consumed_read_cap/provisioned_read_cap)\\",\\"yAxis\\":\\"right\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false},\\"right\\":{\\"min\\":0,\\"max\\":100,\\"label\\":\\"%\\",\\"showUnits\\":false}},\\"legend\\":{\\"position\\":\\"right\\"}}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":3,\\"x\\":0,\\"y\\":4,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Write Usage\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\"Consumed\\",\\"expression\\":\\"consumed_wcu_sum/PERIOD(consumed_wcu_sum)\\",\\"id\\":\\"consumed_write_cap\\"}],[\\"AWS/DynamoDB\\",\\"ConsumedWriteCapacityUnits\\",\\"TableName\\",\\"TestTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"consumed_wcu_sum\\"}],[\\"AWS/DynamoDB\\",\\"ProvisionedWriteCapacityUnits\\",\\"TableName\\",\\"TestTable\\",{\\"label\\":\\"Provisioned\\",\\"id\\":\\"provisioned_write_cap\\"}],[{\\"label\\":\\"Utilization\\",\\"expression\\":\\"100*(consumed_write_cap/provisioned_write_cap)\\",\\"yAxis\\":\\"right\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false},\\"right\\":{\\"min\\":0,\\"max\\":100,\\"label\\":\\"%\\",\\"showUnits\\":false}},\\"legend\\":{\\"position\\":\\"right\\"}}},{\\"type\\":\\"metric\\",\\"width\\":9,\\"height\\":6,\\"x\\":6,\\"y\\":1,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Latency (Average)\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\" \\",\\"expression\\":\\"SEARCH('{\\\\\\"AWS/DynamoDB\\\\\\",\\\\\\"TableName\\\\\\",\\\\\\"Operation\\\\\\"} \\\\\\"TableName\\\\\\"=\\\\\\"TestTable\\\\\\" MetricName=\\\\\\"SuccessfulRequestLatency\\\\\\"', 'Average', 300)\\"}]],\\"annotations\\":{\\"horizontal\\":[{\\"label\\":\\"GetItem > 10000 for 3 datapoints within 15 minutes\\",\\"value\\":10000,\\"yAxis\\":\\"left\\"}]},\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"ms\\",\\"showUnits\\":false}},\\"legend\\":{\\"position\\":\\"right\\"}}},{\\"type\\":\\"metric\\",\\"width\\":3,\\"height\\":6,\\"x\\":15,\\"y\\":1,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Throttles\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\"Read\\",\\"expression\\":\\"FILL(readThrottles,0)\\"}],[\\"AWS/DynamoDB\\",\\"ReadThrottleEvents\\",\\"TableName\\",\\"TestTable\\",{\\"label\\":\\"Read\\",\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"readThrottles\\"}],[{\\"label\\":\\"Write\\",\\"expression\\":\\"FILL(writeThrottles,0)\\"}],[\\"AWS/DynamoDB\\",\\"WriteThrottleEvents\\",\\"TableName\\",\\"TestTable\\",{\\"label\\":\\"Write\\",\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"writeThrottles\\"}]],\\"annotations\\":{\\"horizontal\\":[{\\"label\\":\\"Read > 100 for 3 datapoints within 15 minutes\\",\\"value\\":100,\\"yAxis\\":\\"left\\"}]},\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":6,\\"x\\":18,\\"y\\":1,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Errors\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\"System Errors\\",\\"expression\\":\\"systemErrorGetItem+systemErrorBatchGetItem+systemErrorScan+systemErrorQuery+systemErrorGetRecords+systemErrorPutItem+systemErrorDeleteItem+systemErrorUpdateItem+systemErrorBatchWriteItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"GetItem\\",\\"TableName\\",\\"TestTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorGetItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"BatchGetItem\\",\\"TableName\\",\\"TestTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorBatchGetItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"Scan\\",\\"TableName\\",\\"TestTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorScan\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"Query\\",\\"TableName\\",\\"TestTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorQuery\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"GetRecords\\",\\"TableName\\",\\"TestTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorGetRecords\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"PutItem\\",\\"TableName\\",\\"TestTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorPutItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"DeleteItem\\",\\"TableName\\",\\"TestTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorDeleteItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"UpdateItem\\",\\"TableName\\",\\"TestTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorUpdateItem\\"}],[\\"AWS/DynamoDB\\",\\"SystemErrors\\",\\"Operation\\",\\"BatchWriteItem\\",\\"TableName\\",\\"TestTable\\",{\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"systemErrorBatchWriteItem\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false}}}},{\\"type\\":\\"text\\",\\"width\\":24,\\"height\\":1,\\"x\\":0,\\"y\\":7,\\"properties\\":{\\"markdown\\":\\"### Lambda Function **[CompositeTestFunction](https://",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ ".console.aws.amazon.com/lambda/home?region=",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "#/functions/TestFunction)**\\"}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":5,\\"x\\":0,\\"y\\":8,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"TPS\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[{\\"label\\":\\"TPS\\",\\"expression\\":\\"FILL(requests,0) / PERIOD(requests)\\"}],[\\"AWS/Lambda\\",\\"Invocations\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"Invocations\\",\\"stat\\":\\"Sum\\",\\"visible\\":false,\\"id\\":\\"requests\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Rate\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":5,\\"x\\":6,\\"y\\":8,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Latency\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"Duration\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"P50 (avg: \${AVG})\\",\\"stat\\":\\"p50\\"}],[\\"AWS/Lambda\\",\\"Duration\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"P90 (avg: \${AVG})\\",\\"stat\\":\\"p90\\"}],[\\"AWS/Lambda\\",\\"Duration\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"P99 (avg: \${AVG})\\",\\"stat\\":\\"p99\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"ms\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":5,\\"x\\":12,\\"y\\":8,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Errors (rate)\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"Errors\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"Faults (avg)\\"}]],\\"annotations\\":{\\"horizontal\\":[{\\"label\\":\\"Faults (avg) > 0.5 for 3 datapoints within 15 minutes\\",\\"value\\":0.5,\\"yAxis\\":\\"left\\"}]},\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Rate\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":6,\\"height\\":5,\\"x\\":18,\\"y\\":8,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Rates\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"Throttles\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"Throttles (avg)\\"}],[\\"AWS/Lambda\\",\\"ProvisionedConcurrencySpilloverInvocations\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"Provisioned Concurrency Spillovers (avg)\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Rate\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":8,\\"height\\":5,\\"x\\":0,\\"y\\":13,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Invocations\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"Invocations\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"Invocations\\",\\"stat\\":\\"Sum\\"}],[\\"AWS/Lambda\\",\\"Throttles\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"Throttles\\",\\"stat\\":\\"Sum\\"}],[\\"AWS/Lambda\\",\\"ConcurrentExecutions\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"Concurrent\\",\\"stat\\":\\"Maximum\\"}],[\\"AWS/Lambda\\",\\"ProvisionedConcurrencySpilloverInvocations\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"Provisioned Concurrency Spillovers\\",\\"stat\\":\\"Sum\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":8,\\"height\\":5,\\"x\\":8,\\"y\\":13,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Errors\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"Errors\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"Faults\\",\\"stat\\":\\"Sum\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"Count\\",\\"showUnits\\":false}}}},{\\"type\\":\\"metric\\",\\"width\\":8,\\"height\\":5,\\"x\\":16,\\"y\\":18,\\"properties\\":{\\"view\\":\\"timeSeries\\",\\"title\\":\\"Iterator\\",\\"region\\":\\"",
+ Object {
+ "Ref": "AWS::Region",
+ },
+ "\\",\\"metrics\\":[[\\"AWS/Lambda\\",\\"IteratorAge\\",\\"FunctionName\\",\\"TestFunction\\",{\\"label\\":\\"Iterator Age\\",\\"stat\\":\\"Maximum\\"}]],\\"yAxis\\":{\\"left\\":{\\"min\\":0,\\"label\\":\\"ms\\",\\"showUnits\\":false}}}}]}",
+ ],
+ ],
+ },
+ "DashboardName": "CompositeAlarmTestFacade",
+ },
+ "Type": "AWS::CloudWatch::Dashboard",
+ },
+ "OnAlarmTopicForCompositesC70E2B13": Object {
+ "Properties": Object {
+ "TopicName": "CompositeAlarmTopic",
+ },
+ "Type": "AWS::SNS::Topic",
+ },
+ },
+ "Rules": Object {
+ "CheckBootstrapVersion": Object {
+ "Assertions": Array [
+ Object {
+ "Assert": Object {
+ "Fn::Not": Array [
+ Object {
+ "Fn::Contains": Array [
+ Array [
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ ],
+ Object {
+ "Ref": "BootstrapVersion",
+ },
+ ],
+ },
+ ],
+ },
+ "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.",
+ },
+ ],
+ },
+ },
+}
+`;
+
exports[`test of defaults typical usage 1`] = `
Object {
"Parameters": Object {