Skip to content

Commit f9b64e6

Browse files
xendoJerzy 'Cedric' Zagorski
andauthored
feat: add EC2Monitoring for network ingress/egress (#658)
Adding network egress and ingress monitoring to the EC2Monitoring. This partially addresses: #515 I tested this by deploying to my infrastructure and eyeballing the dashboard and alarms from the console ``` monitoring.monitorEC2Instances({ ... autoScalingGroup: autoscalingGroup, addNetworkOutTotalBytesExceedThresholdAlarm: { WARNING: { maxNetworkOutBytes: 1_000_000_000, }, }, }); ``` <img width="1266" height="236" alt="ec2-network-monitoring" src="https://github.com/user-attachments/assets/e07af62a-f3dd-49fb-b4b3-44e02317b7c7" /> Fixes # --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_ --------- Co-authored-by: Jerzy 'Cedric' Zagorski <jzagorsk@amazon.com>
1 parent 525411f commit f9b64e6

File tree

8 files changed

+1413
-31
lines changed

8 files changed

+1413
-31
lines changed

API.md

Lines changed: 1005 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {
2+
ComparisonOperator,
3+
TreatMissingData,
4+
} from "aws-cdk-lib/aws-cloudwatch";
5+
import { AlarmFactory, CustomAlarmThreshold } from "../../alarm";
6+
import { MetricWithAlarmSupport } from "../../metric";
7+
8+
export interface NetworkOutThreshold extends CustomAlarmThreshold {
9+
readonly maxNetworkOutBytes: number;
10+
}
11+
12+
export interface NetworkInThreshold extends CustomAlarmThreshold {
13+
readonly maxNetworkInBytes: number;
14+
}
15+
16+
export class EC2AlarmFactory {
17+
protected readonly alarmFactory: AlarmFactory;
18+
19+
constructor(alarmFactory: AlarmFactory) {
20+
this.alarmFactory = alarmFactory;
21+
}
22+
23+
addNetworkOutAlarm(
24+
metric: MetricWithAlarmSupport,
25+
props: NetworkOutThreshold,
26+
disambiguator?: string,
27+
) {
28+
return this.alarmFactory.addAlarm(metric, {
29+
treatMissingData:
30+
props.treatMissingDataOverride ?? TreatMissingData.MISSING,
31+
comparisonOperator:
32+
props.comparisonOperatorOverride ??
33+
ComparisonOperator.GREATER_THAN_THRESHOLD,
34+
...props,
35+
disambiguator,
36+
threshold: props.maxNetworkOutBytes,
37+
alarmNameSuffix: "NetworkOut",
38+
alarmDescription: "Network out too high",
39+
});
40+
}
41+
42+
addNetworkInAlarm(
43+
metric: MetricWithAlarmSupport,
44+
props: NetworkInThreshold,
45+
disambiguator?: string,
46+
) {
47+
return this.alarmFactory.addAlarm(metric, {
48+
treatMissingData:
49+
props.treatMissingDataOverride ?? TreatMissingData.MISSING,
50+
comparisonOperator:
51+
props.comparisonOperatorOverride ??
52+
ComparisonOperator.GREATER_THAN_THRESHOLD,
53+
...props,
54+
disambiguator,
55+
threshold: props.maxNetworkInBytes,
56+
alarmNameSuffix: "NetworkIn",
57+
alarmDescription: "Network in too high",
58+
});
59+
}
60+
}

lib/common/monitoring/alarms/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from "./AuroraAlarmFactory";
44
export * from "./CustomAlarmFactory";
55
export * from "./ConnectionAlarmFactory";
66
export * from "./DynamoAlarmFactory";
7+
export * from "./EC2AlarmFactory";
78
export * from "./ElastiCacheAlarmFactory";
89
export * from "./ErrorAlarmFactory";
910
export * from "./KinesisAlarmFactory";

lib/monitoring/aws-ec2/EC2MetricFactory.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
BaseMetricFactoryProps,
77
MetricFactory,
88
MetricStatistic,
9+
MetricWithAlarmSupport,
910
} from "../../common";
1011

1112
const EC2Namespace = "AWS/EC2";
@@ -17,7 +18,8 @@ export interface IEC2MetricFactoryStrategy {
1718
statistic: MetricStatistic,
1819
region?: string,
1920
account?: string,
20-
): IMetric[];
21+
label?: string,
22+
): MetricWithAlarmSupport[];
2123
}
2224

2325
/**
@@ -36,12 +38,13 @@ class AutoScalingGroupStrategy implements IEC2MetricFactoryStrategy {
3638
statistic: MetricStatistic,
3739
region?: string,
3840
account?: string,
41+
label?: string,
3942
) {
4043
return [
4144
metricFactory.createMetric(
4245
metricName,
4346
statistic,
44-
undefined,
47+
label,
4548
resolveDimensions(this.autoScalingGroup, undefined),
4649
undefined,
4750
EC2Namespace,
@@ -71,14 +74,15 @@ class SelectedInstancesStrategy implements IEC2MetricFactoryStrategy {
7174
statistic: MetricStatistic,
7275
region?: string,
7376
account?: string,
77+
label?: string,
7478
) {
7579
return this.instanceIds.map((instanceId) => {
7680
return metricFactory.createMetric(
7781
metricName,
7882
statistic,
7983
`${metricName} (${instanceId})`,
8084
resolveDimensions(this.autoScalingGroup, instanceId),
81-
undefined,
85+
label,
8286
EC2Namespace,
8387
undefined,
8488
region,
@@ -98,14 +102,15 @@ class AllInstancesStrategy implements IEC2MetricFactoryStrategy {
98102
statistic: MetricStatistic,
99103
region?: string,
100104
account?: string,
105+
label?: string,
101106
) {
102107
return [
103108
metricFactory.createMetricSearch(
104109
`MetricName="${metricName}"`,
105110
{ InstanceId: undefined as unknown as string },
106111
statistic,
107112
EC2Namespace,
108-
undefined,
113+
label,
109114
undefined,
110115
region,
111116
account,
@@ -226,6 +231,22 @@ export class EC2MetricFactory extends BaseMetricFactory<EC2MetricFactoryProps> {
226231
return this.metric("NetworkOut", MetricStatistic.AVERAGE);
227232
}
228233

234+
/**
235+
* The number of bytes received on all network interfaces by the instance.
236+
* This metric identifies the volume of incoming network traffic to a single instance.
237+
*/
238+
metricSumNetworkInRateBytes() {
239+
return this.metric("NetworkIn", MetricStatistic.SUM, "Total NetworkIn");
240+
}
241+
242+
/**
243+
* The number of bytes sent out on all network interfaces by the instance.
244+
* This metric identifies the volume of outgoing network traffic from a single instance.
245+
*/
246+
metricSumNetworkOutRateBytes() {
247+
return this.metric("NetworkOut", MetricStatistic.SUM, "Total NetworkOut");
248+
}
249+
229250
private createDiskMetrics(metricName: string, statistic: MetricStatistic) {
230251
const classicMetrics = this.strategy.createMetrics(
231252
this.metricFactory,
@@ -253,11 +274,18 @@ export class EC2MetricFactory extends BaseMetricFactory<EC2MetricFactoryProps> {
253274
});
254275
}
255276

256-
private metric(metricName: string, statistic: MetricStatistic) {
277+
private metric(
278+
metricName: string,
279+
statistic: MetricStatistic,
280+
label?: string,
281+
) {
257282
return this.strategy.createMetrics(
258283
this.metricFactory,
259284
metricName,
260285
statistic,
286+
undefined,
287+
undefined,
288+
label,
261289
);
262290
}
263291
}

lib/monitoring/aws-ec2/EC2Monitoring.ts

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,69 @@
1-
import { GraphWidget, IMetric, IWidget } from "aws-cdk-lib/aws-cloudwatch";
1+
import {
2+
GraphWidget,
3+
HorizontalAnnotation,
4+
IMetric,
5+
IWidget,
6+
} from "aws-cdk-lib/aws-cloudwatch";
27

38
import { EC2MetricFactory, EC2MetricFactoryProps } from "./EC2MetricFactory";
49
import {
510
BaseMonitoringProps,
611
CountAxisFromZero,
712
DefaultGraphWidgetHeight,
813
DefaultSummaryWidgetHeight,
14+
MetricWithAlarmSupport,
915
Monitoring,
1016
MonitoringScope,
1117
PercentageAxisFromZeroToHundred,
1218
QuarterWidth,
1319
SizeAxisBytesFromZero,
1420
ThirdWidth,
1521
} from "../../common";
22+
import {
23+
EC2AlarmFactory,
24+
NetworkInThreshold,
25+
NetworkOutThreshold,
26+
} from "../../common/monitoring/alarms/EC2AlarmFactory";
1627
import {
1728
MonitoringHeaderWidget,
1829
MonitoringNamingStrategy,
1930
} from "../../dashboard";
2031

2132
export interface EC2MonitoringOptions
2233
extends EC2MetricFactoryProps,
23-
BaseMonitoringProps {}
34+
BaseMonitoringProps {
35+
readonly addNetworkOutTotalBytesExceedThresholdAlarm?: Record<
36+
string,
37+
NetworkOutThreshold
38+
>;
39+
40+
readonly addNetworkInTotalBytesExceedThresholdAlarm?: Record<
41+
string,
42+
NetworkInThreshold
43+
>;
44+
}
2445

2546
export interface EC2MonitoringProps extends EC2MonitoringOptions {}
2647

2748
export class EC2Monitoring extends Monitoring {
2849
readonly family: string;
2950
readonly title: string;
3051

52+
readonly ec2AlarmFactory: EC2AlarmFactory;
53+
3154
readonly cpuUtilisationMetrics: IMetric[];
3255
readonly diskReadBytesMetrics: IMetric[];
3356
readonly diskWriteBytesMetrics: IMetric[];
3457
readonly diskReadOpsMetrics: IMetric[];
3558
readonly diskWriteOpsMetrics: IMetric[];
36-
readonly networkInMetrics: IMetric[];
37-
readonly networkOutMetrics: IMetric[];
59+
readonly networkInMetrics: MetricWithAlarmSupport[];
60+
readonly networkOutMetrics: MetricWithAlarmSupport[];
61+
62+
readonly networkInSumMetrics: MetricWithAlarmSupport[];
63+
readonly networkOutSumMetrics: MetricWithAlarmSupport[];
64+
65+
readonly networkInSumLimitAnnotations: HorizontalAnnotation[];
66+
readonly networkOutSumLimitAnnotations: HorizontalAnnotation[];
3867

3968
constructor(scope: MonitoringScope, props: EC2MonitoringProps) {
4069
super(scope, props);
@@ -48,11 +77,28 @@ export class EC2Monitoring extends Monitoring {
4877
});
4978
this.family = props.autoScalingGroup ? "EC2 Auto Scaling Group" : "EC2";
5079
this.title = namingStrategy.resolveHumanReadableName();
80+
this.networkOutSumLimitAnnotations = [];
81+
this.networkInSumLimitAnnotations = [];
5182

5283
const metricFactory = new EC2MetricFactory(
5384
scope.createMetricFactory(),
5485
props,
5586
);
87+
88+
// using different fallback alarm construct name
89+
// as alarms don't allow whitespace
90+
const fallbackAlarmConstructName = props.autoScalingGroup
91+
? props.autoScalingGroup.autoScalingGroupName
92+
: "All-Instances";
93+
const namingAlarmStrategy = new MonitoringNamingStrategy({
94+
...props,
95+
fallbackConstructName: fallbackAlarmConstructName,
96+
});
97+
const alarmFactory = this.createAlarmFactory(
98+
namingAlarmStrategy.resolveAlarmFriendlyName(),
99+
);
100+
this.ec2AlarmFactory = new EC2AlarmFactory(alarmFactory);
101+
56102
this.cpuUtilisationMetrics =
57103
metricFactory.metricAverageCpuUtilisationPercent();
58104
this.diskReadBytesMetrics = metricFactory.metricAverageDiskReadBytes();
@@ -61,6 +107,47 @@ export class EC2Monitoring extends Monitoring {
61107
this.diskWriteOpsMetrics = metricFactory.metricAverageDiskWriteOps();
62108
this.networkInMetrics = metricFactory.metricAverageNetworkInRateBytes();
63109
this.networkOutMetrics = metricFactory.metricAverageNetworkOutRateBytes();
110+
111+
this.networkInSumMetrics = metricFactory.metricSumNetworkInRateBytes();
112+
this.networkOutSumMetrics = metricFactory.metricSumNetworkOutRateBytes();
113+
114+
for (const disambiguator in props.addNetworkInTotalBytesExceedThresholdAlarm) {
115+
const alarmProps =
116+
props.addNetworkInTotalBytesExceedThresholdAlarm[disambiguator];
117+
118+
const createdAlarms = this.networkInMetrics.map((metric) => {
119+
const createdAlarm = this.ec2AlarmFactory.addNetworkInAlarm(
120+
metric,
121+
alarmProps,
122+
disambiguator,
123+
);
124+
this.addAlarm(createdAlarm);
125+
return createdAlarm;
126+
});
127+
128+
if (createdAlarms.length > 0) {
129+
this.networkInSumLimitAnnotations.push(createdAlarms[0].annotation);
130+
}
131+
}
132+
133+
for (const disambiguator in props.addNetworkOutTotalBytesExceedThresholdAlarm) {
134+
const alarmProps =
135+
props.addNetworkOutTotalBytesExceedThresholdAlarm[disambiguator];
136+
const createdAlarms = this.networkOutSumMetrics.map((metric) => {
137+
const createdAlarm = this.ec2AlarmFactory.addNetworkOutAlarm(
138+
metric,
139+
alarmProps,
140+
disambiguator,
141+
);
142+
this.addAlarm(createdAlarm);
143+
return createdAlarm;
144+
});
145+
146+
if (createdAlarms.length > 0) {
147+
this.networkOutSumLimitAnnotations.push(createdAlarms[0].annotation);
148+
}
149+
}
150+
props.useCreatedAlarms?.consume(this.createdAlarms());
64151
}
65152

66153
summaryWidgets(): IWidget[] {
@@ -135,6 +222,12 @@ export class EC2Monitoring extends Monitoring {
135222
title: "Network",
136223
left: [...this.networkInMetrics, ...this.networkOutMetrics],
137224
leftYAxis: SizeAxisBytesFromZero,
225+
right: [...this.networkInSumMetrics, ...this.networkOutSumMetrics],
226+
rightYAxis: SizeAxisBytesFromZero,
227+
rightAnnotations: [
228+
...this.networkInSumLimitAnnotations,
229+
...this.networkOutSumLimitAnnotations,
230+
],
138231
});
139232
}
140233
}

test/facade/__snapshots__/MonitoringAspect.test.ts.snap

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)