-
Notifications
You must be signed in to change notification settings - Fork 4k
/
metric.ts
394 lines (352 loc) · 9.42 KB
/
metric.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
import iam = require('@aws-cdk/aws-iam');
import cdk = require('@aws-cdk/cdk');
import { Alarm, ComparisonOperator, TreatMissingData } from './alarm';
import { normalizeStatistic } from './util.statistic';
export type DimensionHash = {[dim: string]: any};
/**
* Properties for a metric
*/
export interface MetricProps {
/**
* Dimensions of the metric
*
* @default No dimensions
*/
dimensions?: DimensionHash;
/**
* Namespace of the metric.
*/
namespace: string;
/**
* Name of the metric.
*/
metricName: string;
/**
* The period over which the specified statistic is applied.
*
* Specify time in seconds, in multiples of 60.
*
* @default 300
*/
periodSec?: number;
/**
* What function to use for aggregating.
*
* Can be one of the following (case insensitive)
*
* - "minimum" | "min"
* - "maximum" | "max"
* - "average" | "avg"
* - "sum"
* - "samplecount | "n"
* - "pNN.NN"
*
* @default Average
*/
statistic?: string;
/**
* Unit for the metric that is associated with the alarm
*/
unit?: Unit;
/**
* Label for this metric when added to a Graph in a Dashboard
*/
label?: string;
/**
* Color for this metric when added to a Graph in a Dashboard
*/
color?: string;
}
/**
* A metric emitted by a service
*
* The metric is a combination of a metric identifier (namespace, name and dimensions)
* and an aggregation function (statistic, period and unit).
*
* It also contains metadata which is used only in graphs, such as color and label.
* It makes sense to embed this in here, so that compound constructs can attach
* that metadata to metrics they expose.
*
* This class does not represent a resource, so hence is not a construct. Instead,
* Metric is an abstraction that makes it easy to specify metrics for use in both
* alarms and graphs.
*/
export class Metric {
/**
* Grant permissions to the given identity to write metrics.
*
* @param identity The IAM identity to give permissions to.
*/
public static grantPutMetricData(identity?: iam.IPrincipal) {
if (!identity) { return; }
identity.addToPolicy(new iam.PolicyStatement()
.addAllResources()
.addAction("cloudwatch:PutMetricData"));
}
public readonly dimensions?: DimensionHash;
public readonly namespace: string;
public readonly metricName: string;
public readonly periodSec: number;
public readonly statistic: string;
public readonly unit?: Unit;
public readonly label?: string;
public readonly color?: string;
constructor(props: MetricProps) {
if (props.periodSec !== undefined
&& props.periodSec !== 1 && props.periodSec !== 5 && props.periodSec !== 10 && props.periodSec !== 30
&& props.periodSec % 60 !== 0) {
throw new Error("'periodSec' must be 1, 5, 10, 30, or a multiple of 60");
}
this.dimensions = props.dimensions;
this.namespace = props.namespace;
this.metricName = props.metricName;
this.periodSec = props.periodSec !== undefined ? props.periodSec : 300;
// Try parsing, this will throw if it's not a valid stat
this.statistic = normalizeStatistic(props.statistic || "Average");
this.label = props.label;
this.color = props.color;
this.unit = props.unit;
}
/**
* Return a copy of Metric with properties changed.
*
* All properties except namespace and metricName can be changed.
*
* @param props The set of properties to change.
*/
public with(props: MetricCustomization): Metric {
return new Metric({
dimensions: ifUndefined(props.dimensions, this.dimensions),
namespace: this.namespace,
metricName: this.metricName,
periodSec: ifUndefined(props.periodSec, this.periodSec),
statistic: ifUndefined(props.statistic, this.statistic),
unit: ifUndefined(props.unit, this.unit),
label: ifUndefined(props.label, this.label),
color: ifUndefined(props.color, this.color)
});
}
/**
* Make a new Alarm for this metric
*
* Combines both properties that may adjust the metric (aggregation) as well
* as alarm properties.
*/
public newAlarm(scope: cdk.Construct, id: string, props: MetricAarmProps): Alarm {
return new Alarm(scope, id, {
metric: this.with({
statistic: props.statistic,
periodSec: props.periodSec,
}),
alarmName: props.alarmName,
alarmDescription: props.alarmDescription,
comparisonOperator: props.comparisonOperator,
datapointsToAlarm: props.datapointsToAlarm,
threshold: props.threshold,
evaluationPeriods: props.evaluationPeriods,
evaluateLowSampleCountPercentile: props.evaluateLowSampleCountPercentile,
treatMissingData: props.treatMissingData,
actionsEnabled: props.actionsEnabled,
});
}
/**
* Return the dimensions of this Metric as a list of Dimension.
*/
public dimensionsAsList(): Dimension[] {
const dims = this.dimensions;
if (dims === undefined) {
return [];
}
const list = Object.keys(dims).map(key => ({ name: key, value: dims[key] }));
return list;
}
}
/**
* Metric dimension
*/
export interface Dimension {
/**
* Name of the dimension
*/
name: string;
/**
* Value of the dimension
*/
value: any;
}
/**
* Statistic to use over the aggregation period
*/
export enum Statistic {
SampleCount = 'SampleCount',
Average = 'Average',
Sum = 'Sum',
Minimum = 'Minimum',
Maximum = 'Maximum',
}
/**
* Unit for metric
*/
export enum Unit {
Seconds = 'Seconds',
Microseconds = 'Microseconds',
Milliseconds = 'Milliseconds',
Bytes_ = 'Bytes',
Kilobytes = 'Kilobytes',
Megabytes = 'Megabytes',
Gigabytes = 'Gigabytes',
Terabytes = 'Terabytes',
Bits = 'Bits',
Kilobits = 'Kilobits',
Megabits = 'Megabits',
Gigabits = 'Gigabits',
Terabits = 'Terabits',
Percent = 'Percent',
Count = 'Count',
BytesPerSecond = 'Bytes/Second',
KilobytesPerSecond = 'Kilobytes/Second',
MegabytesPerSecond = 'Megabytes/Second',
GigabytesPerSecond = 'Gigabytes/Second',
TerabytesPerSecond = 'Terabytes/Second',
BitsPerSecond = 'Bits/Second',
KilobitsPerSecond = 'Kilobits/Second',
MegabitsPerSecond = 'Megabits/Second',
GigabitsPerSecond = 'Gigabits/Second',
TerabitsPerSecond = 'Terabits/Second',
CountPerSecond = 'Count/Second',
None = 'None'
}
/**
* Properties of a metric that can be changed
*/
export interface MetricCustomization {
/**
* Dimensions of the metric
*
* @default No dimensions
*/
dimensions?: DimensionHash;
/**
* The period over which the specified statistic is applied.
*
* Specify time in seconds, in multiples of 60.
*
* @default 300
*/
periodSec?: number;
/**
* What function to use for aggregating.
*
* Can be one of the following:
*
* - "Minimum" | "min"
* - "Maximum" | "max"
* - "Average" | "avg"
* - "Sum" | "sum"
* - "SampleCount | "n"
* - "pNN.NN"
*
* @default Average
*/
statistic?: string;
/**
* Unit for the metric that is associated with the alarm
*/
unit?: Unit;
/**
* Label for this metric when added to a Graph in a Dashboard
*/
label?: string;
/**
* Color for this metric when added to a Graph in a Dashboard
*/
color?: string;
}
/**
* Properties needed to make an alarm from a metric
*/
export interface MetricAarmProps {
/**
* The period over which the specified statistic is applied.
*
* Specify time in seconds, in multiples of 60.
*
* @default 300
*/
periodSec?: number;
/**
* What function to use for aggregating.
*
* Can be one of the following:
*
* - "Minimum" | "min"
* - "Maximum" | "max"
* - "Average" | "avg"
* - "Sum" | "sum"
* - "SampleCount | "n"
* - "pNN.NN"
*
* @default Average
*/
statistic?: string;
/**
* Name of the alarm
*
* @default Automatically generated name
*/
alarmName?: string;
/**
* Description for the alarm
*
* @default No description
*/
alarmDescription?: string;
/**
* Comparison to use to check if metric is breaching
*
* @default GreaterThanOrEqualToThreshold
*/
comparisonOperator?: ComparisonOperator;
/**
* The value against which the specified statistic is compared.
*/
threshold: number;
/**
* The number of periods over which data is compared to the specified threshold.
*/
evaluationPeriods: number;
/**
* Specifies whether to evaluate the data and potentially change the alarm state if there are too few data points to be statistically significant.
*
* Used only for alarms that are based on percentiles.
*/
evaluateLowSampleCountPercentile?: string;
/**
* Sets how this alarm is to handle missing data points.
*
* @default TreatMissingData.Missing
*/
treatMissingData?: TreatMissingData;
/**
* Whether the actions for this alarm are enabled
*
* @default true
*/
actionsEnabled?: boolean;
/**
* The number of datapoints that must be breaching to trigger the alarm. This is used only if you are setting an "M
* out of N" alarm. In that case, this value is the M. For more information, see Evaluating an Alarm in the Amazon
* CloudWatch User Guide.
*
* @default ``evaluationPeriods``
*
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#alarm-evaluation
*/
datapointsToAlarm?: number;
}
function ifUndefined<T>(x: T | undefined, def: T | undefined): T | undefined {
if (x !== undefined) {
return x;
}
return def;
}