@guardian/cdk 48.2.0-beta.1 → 48.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,39 @@
1
1
  import type { IMetric } from "aws-cdk-lib/aws-cloudwatch";
2
+ import { Construct } from "constructs";
2
3
  import type { GuStack } from "../../constructs/core";
3
4
  export interface ErrorBudgetAlarmProps {
4
- snsTopicNameForAlerts: string;
5
+ sloName: string;
5
6
  sloTarget: number;
6
7
  badEvents: IMetric;
7
8
  validEvents: IMetric;
9
+ snsTopicNameForAlerts: string;
8
10
  }
9
- export declare class ErrorBudgetAlarmExperimental {
11
+ /**
12
+ * Use this construct to create alarms that will notify you when you are at risk of missing your SLO targets.
13
+ *
14
+ * These alarms follow the recommendations in the [SRE Workbook](https://sre.google/workbook/alerting-on-slos/) to
15
+ * implement Multiwindow, Multi-Burn-Rate Alerts.
16
+ *
17
+ * The CloudWatch implementation of this strategy creates nine separate CloudWatch alarms. The main alarms that users
18
+ * need to care about are the three composite alarms. These track different burn rates of error budget consumption:
19
+ * fast (e.g. complete outages), medium (e.g. moderate error percentage over hours, rather than minutes) and slow (e.g.
20
+ * a small percentage of errors sustained over a much longer period). Multiple burn rates are used to ensure that
21
+ * we are able to pick up all scenarios which could threaten the SLO, without interrupting developers with an
22
+ * unnecessary level of urgency.
23
+ *
24
+ * Each composite alarm creates two child alarms; these are used to support multiple windows (or periods in CloudWatch
25
+ * terminology). A composite alarm is configured to send notifications if both of its child alarms are in an alarm state.
26
+ * The child alarms themselves have no actions/notifications configured and users should not need to interact with them
27
+ * directly. Multiple windows are helpful for accuracy and reset time. The long window helps us to avoid sending an
28
+ * alert in scenarios where no intervention is required, for example a very brief spike in errors that is resolved
29
+ * automatically (e.g. when AWS replaces a 'bad' EC2 instance). The short window helps to ensure that the alert is reset
30
+ * (or moved back into OK state, in CloudWatch terminology) promptly once a problem has been resolved.
31
+ *
32
+ * The composite alarms have an awareness of priority, meaning that low priority alarm notifications are suppressed if
33
+ * a higher priority alarm is already firing. That is, if you receive an alert about fast error budget consumption, you
34
+ * should not receive an alert from the medium or slow versions of the alarm.
35
+ */
36
+ export declare class GuErrorBudgetAlarmExperimental extends Construct {
10
37
  constructor(scope: GuStack, props: ErrorBudgetAlarmProps);
11
38
  }
12
39
  //# sourceMappingURL=error-budget-alarm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-budget-alarm.d.ts","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EAAC,MAAM,4BAA4B,CAAC;AAW7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;CAEtB;AAwCD,qBAAa,4BAA4B;gBAE3B,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB;CAqEzD"}
1
+ {"version":3,"file":"error-budget-alarm.d.ts","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAa7E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AA+BD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,8BAA+B,SAAQ,SAAS;gBAC/C,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB;CA8DzD"}
@@ -1,77 +1,102 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ErrorBudgetAlarmExperimental = void 0;
3
+ exports.GuErrorBudgetAlarmExperimental = void 0;
4
4
  const aws_cdk_lib_1 = require("aws-cdk-lib");
5
5
  const aws_cloudwatch_1 = require("aws-cdk-lib/aws-cloudwatch");
6
6
  const aws_cloudwatch_actions_1 = require("aws-cdk-lib/aws-cloudwatch-actions");
7
7
  const aws_sns_1 = require("aws-cdk-lib/aws-sns");
8
- function applySuppression(alarmToSuppress, suppressingAlarm, shortWindow) {
9
- const cfnAlarm = alarmToSuppress.node.defaultChild;
10
- cfnAlarm.actionsSuppressor = suppressingAlarm.alarmArn;
11
- cfnAlarm.actionsSuppressorWaitPeriod = 120;
12
- cfnAlarm.actionsSuppressorExtensionPeriod = shortWindow.toSeconds();
13
- }
14
- // Do we need to extend construct here?
15
- class ErrorBudgetAlarmExperimental {
8
+ const constructs_1 = require("constructs");
9
+ /**
10
+ * Use this construct to create alarms that will notify you when you are at risk of missing your SLO targets.
11
+ *
12
+ * These alarms follow the recommendations in the [SRE Workbook](https://sre.google/workbook/alerting-on-slos/) to
13
+ * implement Multiwindow, Multi-Burn-Rate Alerts.
14
+ *
15
+ * The CloudWatch implementation of this strategy creates nine separate CloudWatch alarms. The main alarms that users
16
+ * need to care about are the three composite alarms. These track different burn rates of error budget consumption:
17
+ * fast (e.g. complete outages), medium (e.g. moderate error percentage over hours, rather than minutes) and slow (e.g.
18
+ * a small percentage of errors sustained over a much longer period). Multiple burn rates are used to ensure that
19
+ * we are able to pick up all scenarios which could threaten the SLO, without interrupting developers with an
20
+ * unnecessary level of urgency.
21
+ *
22
+ * Each composite alarm creates two child alarms; these are used to support multiple windows (or periods in CloudWatch
23
+ * terminology). A composite alarm is configured to send notifications if both of its child alarms are in an alarm state.
24
+ * The child alarms themselves have no actions/notifications configured and users should not need to interact with them
25
+ * directly. Multiple windows are helpful for accuracy and reset time. The long window helps us to avoid sending an
26
+ * alert in scenarios where no intervention is required, for example a very brief spike in errors that is resolved
27
+ * automatically (e.g. when AWS replaces a 'bad' EC2 instance). The short window helps to ensure that the alert is reset
28
+ * (or moved back into OK state, in CloudWatch terminology) promptly once a problem has been resolved.
29
+ *
30
+ * The composite alarms have an awareness of priority, meaning that low priority alarm notifications are suppressed if
31
+ * a higher priority alarm is already firing. That is, if you receive an alert about fast error budget consumption, you
32
+ * should not receive an alert from the medium or slow versions of the alarm.
33
+ */
34
+ class GuErrorBudgetAlarmExperimental extends constructs_1.Construct {
16
35
  constructor(scope, props) {
36
+ super(scope, props.sloName); // The assumption here is that `sloName` will be unique (per stack)
17
37
  const errorBudget = 1 - props.sloTarget;
18
- const snsTopic = aws_sns_1.Topic.fromTopicArn(scope, 'SnsSloAlarms', `arn:aws:sns:${scope.region}:${scope.account}:${props.snsTopicNameForAlerts}`);
38
+ const snsTopic = aws_sns_1.Topic.fromTopicArn(scope, `SnsSloAlarmsFor${props.sloName}`, `arn:aws:sns:${scope.region}:${scope.account}:${props.snsTopicNameForAlerts}`);
19
39
  const fastBurnRate = {
20
- speed: 'Fast',
21
- burnRate: 15,
22
- longWindow: aws_cdk_lib_1.Duration.hours(1),
23
- shortWindow: aws_cdk_lib_1.Duration.minutes(5),
40
+ speed: "Fast",
41
+ burnRate: 14.4,
42
+ longPeriod: aws_cdk_lib_1.Duration.hours(1),
43
+ shortPeriod: aws_cdk_lib_1.Duration.minutes(5),
24
44
  };
25
45
  const fastBurnAlarm = new CompositeBurnRateAlarm(scope, {
46
+ sloName: props.sloName,
26
47
  badEvents: props.badEvents,
27
- burnRate: fastBurnRate,
48
+ burnRateMonitoring: fastBurnRate,
28
49
  errorBudget,
29
50
  snsTopic,
30
51
  validEvents: props.validEvents,
31
52
  });
32
- const moderateBurnRate = {
33
- speed: 'Moderate',
53
+ const mediumBurnRateMonitoring = {
54
+ speed: "Medium",
34
55
  burnRate: 6,
35
- longWindow: aws_cdk_lib_1.Duration.hours(6),
36
- shortWindow: aws_cdk_lib_1.Duration.minutes(30),
56
+ longPeriod: aws_cdk_lib_1.Duration.hours(6),
57
+ shortPeriod: aws_cdk_lib_1.Duration.minutes(30),
37
58
  };
38
- const moderateBurnAlarm = new CompositeBurnRateAlarm(scope, {
59
+ const mediumBurnRateAlarm = new CompositeBurnRateAlarm(scope, {
60
+ sloName: props.sloName,
39
61
  badEvents: props.badEvents,
40
- burnRate: moderateBurnRate,
62
+ burnRateMonitoring: mediumBurnRateMonitoring,
41
63
  errorBudget,
42
64
  snsTopic,
43
65
  validEvents: props.validEvents,
66
+ suppressorAlarm: fastBurnAlarm,
44
67
  });
45
- applySuppression(moderateBurnAlarm, fastBurnAlarm, moderateBurnRate.shortWindow);
46
68
  const slowBurnRate = {
47
- speed: 'Slow',
69
+ speed: "Slow",
48
70
  burnRate: 3,
49
- longWindow: aws_cdk_lib_1.Duration.days(1),
50
- shortWindow: aws_cdk_lib_1.Duration.hours(2),
71
+ longPeriod: aws_cdk_lib_1.Duration.days(1),
72
+ shortPeriod: aws_cdk_lib_1.Duration.hours(2),
51
73
  };
52
- const slowBurnAlarm = new CompositeBurnRateAlarm(scope, {
74
+ new CompositeBurnRateAlarm(scope, {
75
+ sloName: props.sloName,
53
76
  badEvents: props.badEvents,
54
- burnRate: moderateBurnRate,
77
+ burnRateMonitoring: slowBurnRate,
55
78
  errorBudget,
79
+ // TODO - use a different topic for tickets or figure out how to route notifications vs tickets using a single topic
56
80
  snsTopic,
57
81
  validEvents: props.validEvents,
82
+ suppressorAlarm: mediumBurnRateAlarm,
58
83
  });
59
- applySuppression(slowBurnAlarm, moderateBurnAlarm, slowBurnRate.shortWindow);
60
84
  }
61
85
  }
62
- exports.ErrorBudgetAlarmExperimental = ErrorBudgetAlarmExperimental;
63
- class IndividualBurnRateAlarm extends aws_cloudwatch_1.Alarm {
86
+ exports.GuErrorBudgetAlarmExperimental = GuErrorBudgetAlarmExperimental;
87
+ class MonitorBurnRateForPeriod extends aws_cloudwatch_1.Alarm {
64
88
  constructor(scope, props) {
65
89
  const undesirableErrorBudgetConsumption = props.errorBudget * props.burnRateSpeed.burnRate;
66
- super(scope, `${props.burnRateSpeed.speed}BurnOver${props.window.toHumanString()}`, {
90
+ super(scope, props.alarmName, {
91
+ alarmName: props.alarmName,
67
92
  metric: new aws_cloudwatch_1.MathExpression({
68
- expression: `badEvents/validEvents`,
69
- label: 'Observed failure rate',
93
+ expression: "badEvents/validEvents",
94
+ label: "Observed failure rate",
70
95
  usingMetrics: {
71
96
  badEvents: props.badEvents,
72
97
  validEvents: props.validEvents,
73
98
  },
74
- period: props.window,
99
+ period: props.period,
75
100
  }),
76
101
  evaluationPeriods: 1,
77
102
  threshold: undesirableErrorBudgetConsumption,
@@ -82,25 +107,34 @@ class IndividualBurnRateAlarm extends aws_cloudwatch_1.Alarm {
82
107
  }
83
108
  class CompositeBurnRateAlarm extends aws_cloudwatch_1.CompositeAlarm {
84
109
  constructor(scope, props) {
110
+ const alarmName = `${props.sloName}${props.burnRateMonitoring.speed}BurnCompositeAlarm`;
85
111
  const sharedIndividualAlarmProps = {
86
112
  badEvents: props.badEvents,
87
113
  burnRateSpeed: {
88
- speed: props.burnRate.speed,
89
- burnRate: props.burnRate.burnRate,
114
+ speed: props.burnRateMonitoring.speed,
115
+ burnRate: props.burnRateMonitoring.burnRate,
90
116
  },
91
117
  errorBudget: props.errorBudget,
92
- validEvents: props.validEvents
118
+ validEvents: props.validEvents,
93
119
  };
94
- super(scope, `${props.burnRate.speed}BurnCompositeAlarm`, {
95
- alarmRule: aws_cloudwatch_1.AlarmRule.allOf(aws_cloudwatch_1.AlarmRule.fromAlarm(new IndividualBurnRateAlarm(scope, {
120
+ super(scope, alarmName, {
121
+ alarmRule: aws_cloudwatch_1.AlarmRule.allOf(aws_cloudwatch_1.AlarmRule.fromAlarm(new MonitorBurnRateForPeriod(scope, {
96
122
  ...sharedIndividualAlarmProps,
97
- window: props.burnRate.longWindow,
98
- }), aws_cloudwatch_1.AlarmState.ALARM), aws_cloudwatch_1.AlarmRule.fromAlarm(new IndividualBurnRateAlarm(scope, {
123
+ alarmName: `ChildAlarmLongPeriod${alarmName}`,
124
+ period: props.burnRateMonitoring.longPeriod,
125
+ }), aws_cloudwatch_1.AlarmState.ALARM), aws_cloudwatch_1.AlarmRule.fromAlarm(new MonitorBurnRateForPeriod(scope, {
99
126
  ...sharedIndividualAlarmProps,
100
- window: props.burnRate.shortWindow,
127
+ alarmName: `ChildAlarmShortPeriod${alarmName}`,
128
+ period: props.burnRateMonitoring.shortPeriod,
101
129
  }), aws_cloudwatch_1.AlarmState.ALARM)),
102
130
  });
103
131
  this.addAlarmAction(new aws_cloudwatch_actions_1.SnsAction(props.snsTopic));
132
+ if (props.suppressorAlarm) {
133
+ const cfnAlarm = this.node.defaultChild;
134
+ cfnAlarm.actionsSuppressor = props.suppressorAlarm.alarmArn;
135
+ cfnAlarm.actionsSuppressorWaitPeriod = 120;
136
+ cfnAlarm.actionsSuppressorExtensionPeriod = props.burnRateMonitoring.shortPeriod.toSeconds();
137
+ }
104
138
  }
105
139
  }
106
140
  //# sourceMappingURL=error-budget-alarm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-budget-alarm.js","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":";;;AAAA,6CAAuC;AAGvC,+DAMoC;AACpC,+EAA+D;AAE/D,iDAA4C;AAqC5C,SAAS,gBAAgB,CACvB,eAA+B,EAC/B,gBAAgC,EAChC,WAAqB;IAErB,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,YAAiC,CAAC;IACxE,QAAQ,CAAC,iBAAiB,GAAG,gBAAgB,CAAC,QAAQ,CAAC;IACvD,QAAQ,CAAC,2BAA2B,GAAG,GAAG,CAAC;IAC3C,QAAQ,CAAC,gCAAgC,GAAG,WAAW,CAAC,SAAS,EAAE,CAAC;AACtE,CAAC;AAED,uCAAuC;AACvC,MAAa,4BAA4B;IAEvC,YAAY,KAAc,EAAE,KAA4B;QAEtD,MAAM,WAAW,GAAG,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;QAExC,MAAM,QAAQ,GAAW,eAAK,CAAC,YAAY,CACzC,KAAK,EACL,cAAc,EACd,eAAe,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,qBAAqB,EAAE,CAC9E,CAAC;QAEF,MAAM,YAAY,GAAa;YAC7B,KAAK,EAAE,MAAM;YACb,QAAQ,EAAE,EAAE;YACZ,UAAU,EAAE,sBAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7B,WAAW,EAAE,sBAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;SACjC,CAAC;QAEF,MAAM,aAAa,GAAG,IAAI,sBAAsB,CAAC,KAAK,EAAE;YACtD,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,YAAY;YACtB,WAAW;YACX,QAAQ;YACR,WAAW,EAAE,KAAK,CAAC,WAAW;SAC/B,CAAC,CAAC;QAEH,MAAM,gBAAgB,GAAa;YACjC,KAAK,EAAE,UAAU;YACjB,QAAQ,EAAE,CAAC;YACX,UAAU,EAAE,sBAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7B,WAAW,EAAE,sBAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;SAClC,CAAC;QAEF,MAAM,iBAAiB,GAAG,IAAI,sBAAsB,CAAC,KAAK,EAAE;YAC1D,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,gBAAgB;YAC1B,WAAW;YACX,QAAQ;YACR,WAAW,EAAE,KAAK,CAAC,WAAW;SAC/B,CAAC,CAAC;QAEH,gBAAgB,CACd,iBAAiB,EACjB,aAAa,EACb,gBAAgB,CAAC,WAAW,CAC7B,CAAC;QAEF,MAAM,YAAY,GAAa;YAC7B,KAAK,EAAE,MAAM;YACb,QAAQ,EAAE,CAAC;YACX,UAAU,EAAE,sBAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5B,WAAW,EAAE,sBAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;SAC/B,CAAC;QAEF,MAAM,aAAa,GAAG,IAAI,sBAAsB,CAAC,KAAK,EAAE;YACtD,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,gBAAgB;YAC1B,WAAW;YACX,QAAQ;YACR,WAAW,EAAE,KAAK,CAAC,WAAW;SAC/B,CAAC,CAAC;QAEH,gBAAgB,CACd,aAAa,EACb,iBAAiB,EACjB,YAAY,CAAC,WAAW,CACzB,CAAC;IAEJ,CAAC;CAEF;AAvED,oEAuEC;AAED,MAAM,uBAAwB,SAAQ,sBAAK;IACzC,YAAY,KAAc,EAAE,KAAmC;QAC7D,MAAM,iCAAiC,GAAG,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC;QAC3F,KAAK,CACH,KAAK,EACL,GAAG,KAAK,CAAC,aAAa,CAAC,KAAK,WAAW,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,EACrE;YACE,MAAM,EAAE,IAAI,+BAAc,CAAC;gBACzB,UAAU,EAAE,uBAAuB;gBACnC,KAAK,EAAE,uBAAuB;gBAC9B,YAAY,EAAE;oBACZ,SAAS,EAAE,KAAK,CAAC,SAAS;oBAC1B,WAAW,EAAE,KAAK,CAAC,WAAW;iBAC/B;gBACD,MAAM,EAAE,KAAK,CAAC,MAAM;aACrB,CAAC;YACF,iBAAiB,EAAE,CAAC;YACpB,SAAS,EAAE,iCAAiC;YAC5C,kBAAkB,EAAE,mCAAkB,CAAC,sBAAsB;YAC7D,gBAAgB,EAAE,iCAAgB,CAAC,aAAa;SACjD,CACF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,sBAAuB,SAAQ,+BAAc;IACjD,YAAY,KAAc,EAAE,KAAkC;QAC5D,MAAM,0BAA0B,GAAG;YACjC,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,aAAa,EAAE;gBACb,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK;gBAC3B,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ;aAClC;YACD,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,WAAW,EAAE,KAAK,CAAC,WAAW;SAC/B,CAAC;QACF,KAAK,CAAC,KAAK,EACT,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,oBAAoB,EAC3C;YACE,SAAS,EAAE,0BAAS,CAAC,KAAK,CACxB,0BAAS,CAAC,SAAS,CACjB,IAAI,uBAAuB,CAAC,KAAK,EAAE;gBACjC,GAAG,0BAA0B;gBAC7B,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,UAAU;aAClC,CAAC,EACF,2BAAU,CAAC,KAAK,CACjB,EACD,0BAAS,CAAC,SAAS,CACjB,IAAI,uBAAuB,CAAC,KAAK,EAAE;gBACjC,GAAG,0BAA0B;gBAC7B,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,WAAW;aACnC,CAAC,EACF,2BAAU,CAAC,KAAK,CACjB,CACF;SACF,CACF,CAAC;QACF,IAAI,CAAC,cAAc,CAAC,IAAI,kCAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,CAAC;CACF","sourcesContent":["import { Duration } from \"aws-cdk-lib\";\nimport type { CfnCompositeAlarm,\n IMetric} from \"aws-cdk-lib/aws-cloudwatch\";\nimport {\n Alarm, AlarmRule, AlarmState,\n ComparisonOperator,\n CompositeAlarm,\n MathExpression,\n TreatMissingData\n} from \"aws-cdk-lib/aws-cloudwatch\";\nimport { SnsAction } from \"aws-cdk-lib/aws-cloudwatch-actions\";\nimport type { ITopic} from \"aws-cdk-lib/aws-sns\";\nimport { Topic } from \"aws-cdk-lib/aws-sns\";\nimport type { GuStack } from \"../../constructs/core\";\n\nexport interface ErrorBudgetAlarmProps {\n snsTopicNameForAlerts: string;\n sloTarget: number;\n badEvents: IMetric; // Should this be IMetric[]?\n validEvents: IMetric; // Should this be IMetric[]?\n // Could accept a MathExpression for more complicated cases?\n}\n\ninterface BurnRateSpeed {\n speed: 'Fast' | 'Moderate' | 'Slow';\n burnRate: number;\n}\n\ninterface BurnRate extends BurnRateSpeed {\n longWindow: Duration;\n shortWindow: Duration;\n}\n\ninterface IndividualBurnRateAlarmProps {\n burnRateSpeed: BurnRateSpeed;\n window: Duration;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n}\n\ninterface CompositeBurnRateAlarmProps {\n burnRate: BurnRate;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n snsTopic: ITopic;\n}\n\nfunction applySuppression(\n alarmToSuppress: CompositeAlarm,\n suppressingAlarm: CompositeAlarm,\n shortWindow: Duration,\n) {\n const cfnAlarm = alarmToSuppress.node.defaultChild as CfnCompositeAlarm;\n cfnAlarm.actionsSuppressor = suppressingAlarm.alarmArn;\n cfnAlarm.actionsSuppressorWaitPeriod = 120;\n cfnAlarm.actionsSuppressorExtensionPeriod = shortWindow.toSeconds();\n}\n\n// Do we need to extend construct here?\nexport class ErrorBudgetAlarmExperimental {\n\n constructor(scope: GuStack, props: ErrorBudgetAlarmProps) {\n\n const errorBudget = 1 - props.sloTarget;\n\n const snsTopic: ITopic = Topic.fromTopicArn(\n scope,\n 'SnsSloAlarms',\n `arn:aws:sns:${scope.region}:${scope.account}:${props.snsTopicNameForAlerts}`,\n );\n\n const fastBurnRate: BurnRate = {\n speed: 'Fast',\n burnRate: 15,\n longWindow: Duration.hours(1),\n shortWindow: Duration.minutes(5),\n };\n\n const fastBurnAlarm = new CompositeBurnRateAlarm(scope, {\n badEvents: props.badEvents,\n burnRate: fastBurnRate,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n });\n\n const moderateBurnRate: BurnRate = {\n speed: 'Moderate',\n burnRate: 6,\n longWindow: Duration.hours(6),\n shortWindow: Duration.minutes(30),\n };\n\n const moderateBurnAlarm = new CompositeBurnRateAlarm(scope, {\n badEvents: props.badEvents,\n burnRate: moderateBurnRate,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n });\n\n applySuppression(\n moderateBurnAlarm,\n fastBurnAlarm,\n moderateBurnRate.shortWindow,\n );\n\n const slowBurnRate: BurnRate = {\n speed: 'Slow',\n burnRate: 3,\n longWindow: Duration.days(1),\n shortWindow: Duration.hours(2),\n };\n\n const slowBurnAlarm = new CompositeBurnRateAlarm(scope, {\n badEvents: props.badEvents,\n burnRate: moderateBurnRate,\n errorBudget,\n snsTopic, // TODO - use a different topic for tickets or figure out how to route notifications vs tickets using a single topic\n validEvents: props.validEvents,\n });\n\n applySuppression(\n slowBurnAlarm,\n moderateBurnAlarm,\n slowBurnRate.shortWindow,\n );\n\n }\n\n}\n\nclass IndividualBurnRateAlarm extends Alarm {\n constructor(scope: GuStack, props: IndividualBurnRateAlarmProps) {\n const undesirableErrorBudgetConsumption = props.errorBudget * props.burnRateSpeed.burnRate;\n super(\n scope,\n `${props.burnRateSpeed.speed}BurnOver${props.window.toHumanString()}`,\n {\n metric: new MathExpression({\n expression: `badEvents/validEvents`,\n label: 'Observed failure rate',\n usingMetrics: {\n badEvents: props.badEvents,\n validEvents: props.validEvents,\n },\n period: props.window,\n }),\n evaluationPeriods: 1,\n threshold: undesirableErrorBudgetConsumption,\n comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,\n treatMissingData: TreatMissingData.NOT_BREACHING,\n },\n );\n }\n}\n\nclass CompositeBurnRateAlarm extends CompositeAlarm {\n constructor(scope: GuStack, props: CompositeBurnRateAlarmProps) {\n const sharedIndividualAlarmProps = {\n badEvents: props.badEvents,\n burnRateSpeed: {\n speed: props.burnRate.speed,\n burnRate: props.burnRate.burnRate,\n },\n errorBudget: props.errorBudget,\n validEvents: props.validEvents\n };\n super(scope,\n `${props.burnRate.speed}BurnCompositeAlarm`,\n {\n alarmRule: AlarmRule.allOf(\n AlarmRule.fromAlarm(\n new IndividualBurnRateAlarm(scope, {\n ...sharedIndividualAlarmProps,\n window: props.burnRate.longWindow,\n }),\n AlarmState.ALARM,\n ),\n AlarmRule.fromAlarm(\n new IndividualBurnRateAlarm(scope, {\n ...sharedIndividualAlarmProps,\n window: props.burnRate.shortWindow,\n }),\n AlarmState.ALARM,\n ),\n ),\n },\n );\n this.addAlarmAction(new SnsAction(props.snsTopic));\n }\n}\n"]}
1
+ {"version":3,"file":"error-budget-alarm.js","sourceRoot":"","sources":["../../../src/experimental/constructs/error-budget-alarm.ts"],"names":[],"mappings":"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","sourcesContent":["import { Duration } from \"aws-cdk-lib\";\nimport type { CfnCompositeAlarm, IMetric } from \"aws-cdk-lib/aws-cloudwatch\";\nimport {\n Alarm,\n AlarmRule,\n AlarmState,\n ComparisonOperator,\n CompositeAlarm,\n MathExpression,\n TreatMissingData,\n} from \"aws-cdk-lib/aws-cloudwatch\";\nimport { SnsAction } from \"aws-cdk-lib/aws-cloudwatch-actions\";\nimport type { ITopic } from \"aws-cdk-lib/aws-sns\";\nimport { Topic } from \"aws-cdk-lib/aws-sns\";\nimport { Construct } from \"constructs\";\nimport type { GuStack } from \"../../constructs/core\";\n\nexport interface ErrorBudgetAlarmProps {\n sloName: string;\n sloTarget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n snsTopicNameForAlerts: string;\n}\n\ninterface BurnRate {\n speed: \"Fast\" | \"Medium\" | \"Slow\";\n burnRate: number;\n}\n\ninterface BurnRateMonitoring extends BurnRate {\n longPeriod: Duration;\n shortPeriod: Duration;\n}\n\ninterface MonitorBurnRateForPeriodProps {\n alarmName: string;\n burnRateSpeed: BurnRate;\n period: Duration;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n}\n\ninterface CompositeBurnRateAlarmProps {\n sloName: string;\n burnRateMonitoring: BurnRateMonitoring;\n errorBudget: number;\n badEvents: IMetric;\n validEvents: IMetric;\n snsTopic: ITopic;\n suppressorAlarm?: CompositeBurnRateAlarm;\n}\n\n/**\n * Use this construct to create alarms that will notify you when you are at risk of missing your SLO targets.\n *\n * These alarms follow the recommendations in the [SRE Workbook](https://sre.google/workbook/alerting-on-slos/) to\n * implement Multiwindow, Multi-Burn-Rate Alerts.\n *\n * The CloudWatch implementation of this strategy creates nine separate CloudWatch alarms. The main alarms that users\n * need to care about are the three composite alarms. These track different burn rates of error budget consumption:\n * fast (e.g. complete outages), medium (e.g. moderate error percentage over hours, rather than minutes) and slow (e.g.\n * a small percentage of errors sustained over a much longer period). Multiple burn rates are used to ensure that\n * we are able to pick up all scenarios which could threaten the SLO, without interrupting developers with an\n * unnecessary level of urgency.\n *\n * Each composite alarm creates two child alarms; these are used to support multiple windows (or periods in CloudWatch\n * terminology). A composite alarm is configured to send notifications if both of its child alarms are in an alarm state.\n * The child alarms themselves have no actions/notifications configured and users should not need to interact with them\n * directly. Multiple windows are helpful for accuracy and reset time. The long window helps us to avoid sending an\n * alert in scenarios where no intervention is required, for example a very brief spike in errors that is resolved\n * automatically (e.g. when AWS replaces a 'bad' EC2 instance). The short window helps to ensure that the alert is reset\n * (or moved back into OK state, in CloudWatch terminology) promptly once a problem has been resolved.\n *\n * The composite alarms have an awareness of priority, meaning that low priority alarm notifications are suppressed if\n * a higher priority alarm is already firing. That is, if you receive an alert about fast error budget consumption, you\n * should not receive an alert from the medium or slow versions of the alarm.\n */\nexport class GuErrorBudgetAlarmExperimental extends Construct {\n constructor(scope: GuStack, props: ErrorBudgetAlarmProps) {\n super(scope, props.sloName); // The assumption here is that `sloName` will be unique (per stack)\n\n const errorBudget = 1 - props.sloTarget;\n\n const snsTopic: ITopic = Topic.fromTopicArn(\n scope,\n `SnsSloAlarmsFor${props.sloName}`,\n `arn:aws:sns:${scope.region}:${scope.account}:${props.snsTopicNameForAlerts}`\n );\n\n const fastBurnRate: BurnRateMonitoring = {\n speed: \"Fast\",\n burnRate: 14.4,\n longPeriod: Duration.hours(1),\n shortPeriod: Duration.minutes(5),\n };\n\n const fastBurnAlarm = new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: fastBurnRate,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n });\n\n const mediumBurnRateMonitoring: BurnRateMonitoring = {\n speed: \"Medium\",\n burnRate: 6,\n longPeriod: Duration.hours(6),\n shortPeriod: Duration.minutes(30),\n };\n\n const mediumBurnRateAlarm = new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: mediumBurnRateMonitoring,\n errorBudget,\n snsTopic,\n validEvents: props.validEvents,\n suppressorAlarm: fastBurnAlarm,\n });\n\n const slowBurnRate: BurnRateMonitoring = {\n speed: \"Slow\",\n burnRate: 3,\n longPeriod: Duration.days(1),\n shortPeriod: Duration.hours(2),\n };\n\n new CompositeBurnRateAlarm(scope, {\n sloName: props.sloName,\n badEvents: props.badEvents,\n burnRateMonitoring: slowBurnRate,\n errorBudget,\n // TODO - use a different topic for tickets or figure out how to route notifications vs tickets using a single topic\n snsTopic,\n validEvents: props.validEvents,\n suppressorAlarm: mediumBurnRateAlarm,\n });\n }\n}\n\nclass MonitorBurnRateForPeriod extends Alarm {\n constructor(scope: GuStack, props: MonitorBurnRateForPeriodProps) {\n const undesirableErrorBudgetConsumption = props.errorBudget * props.burnRateSpeed.burnRate;\n super(scope, props.alarmName, {\n alarmName: props.alarmName,\n metric: new MathExpression({\n expression: \"badEvents/validEvents\",\n label: \"Observed failure rate\",\n usingMetrics: {\n badEvents: props.badEvents,\n validEvents: props.validEvents,\n },\n period: props.period,\n }),\n evaluationPeriods: 1,\n threshold: undesirableErrorBudgetConsumption,\n comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,\n treatMissingData: TreatMissingData.NOT_BREACHING,\n });\n }\n}\n\nclass CompositeBurnRateAlarm extends CompositeAlarm {\n constructor(scope: GuStack, props: CompositeBurnRateAlarmProps) {\n const alarmName = `${props.sloName}${props.burnRateMonitoring.speed}BurnCompositeAlarm`;\n const sharedIndividualAlarmProps = {\n badEvents: props.badEvents,\n burnRateSpeed: {\n speed: props.burnRateMonitoring.speed,\n burnRate: props.burnRateMonitoring.burnRate,\n },\n errorBudget: props.errorBudget,\n validEvents: props.validEvents,\n };\n super(scope, alarmName, {\n alarmRule: AlarmRule.allOf(\n AlarmRule.fromAlarm(\n new MonitorBurnRateForPeriod(scope, {\n ...sharedIndividualAlarmProps,\n alarmName: `ChildAlarmLongPeriod${alarmName}`,\n period: props.burnRateMonitoring.longPeriod,\n }),\n AlarmState.ALARM\n ),\n AlarmRule.fromAlarm(\n new MonitorBurnRateForPeriod(scope, {\n ...sharedIndividualAlarmProps,\n alarmName: `ChildAlarmShortPeriod${alarmName}`,\n period: props.burnRateMonitoring.shortPeriod,\n }),\n AlarmState.ALARM\n )\n ),\n });\n this.addAlarmAction(new SnsAction(props.snsTopic));\n if (props.suppressorAlarm) {\n const cfnAlarm = this.node.defaultChild as CfnCompositeAlarm;\n cfnAlarm.actionsSuppressor = props.suppressorAlarm.alarmArn;\n cfnAlarm.actionsSuppressorWaitPeriod = 120;\n cfnAlarm.actionsSuppressorExtensionPeriod = props.burnRateMonitoring.shortPeriod.toSeconds();\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@guardian/cdk",
3
3
  "description": "Generic Guardian flavoured AWS CDK components",
4
- "version": "48.2.0-beta.1",
4
+ "version": "48.2.1",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "files": [
@@ -35,7 +35,7 @@
35
35
  "@types/lodash.camelcase": "^4.3.7",
36
36
  "@types/lodash.kebabcase": "^4.1.7",
37
37
  "@types/lodash.upperfirst": "^4.3.7",
38
- "@types/node": "18.11.2",
38
+ "@types/node": "18.11.5",
39
39
  "@types/yargs": "^17.0.13",
40
40
  "aws-cdk": "2.45.0",
41
41
  "cz-conventional-changelog": "^3.3.0",
@@ -48,15 +48,15 @@
48
48
  "serve": "^14.0.1",
49
49
  "ts-jest": "^29.0.3",
50
50
  "ts-node": "^10.9.1",
51
- "typedoc": "^0.23.17",
51
+ "typedoc": "^0.23.18",
52
52
  "typescript": "~4.8.4"
53
53
  },
54
54
  "dependencies": {
55
- "@oclif/core": "1.16.5",
55
+ "@oclif/core": "1.19.2",
56
56
  "aws-cdk-lib": "2.45.0",
57
- "aws-sdk": "^2.1235.0",
57
+ "aws-sdk": "^2.1240.0",
58
58
  "chalk": "^4.1.2",
59
- "codemaker": "^1.69.0",
59
+ "codemaker": "^1.70.0",
60
60
  "constructs": "10.1.127",
61
61
  "git-url-parse": "^13.1.0",
62
62
  "js-yaml": "^4.1.0",