Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: feat(cloudwatch): enable expressions and multiple metrics #4942

Closed
wants to merge 11 commits into from
63 changes: 48 additions & 15 deletions packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Construct, IResource, Lazy, Resource, Stack } from '@aws-cdk/core';
import { IAlarmAction } from './alarm-action';
import { CfnAlarm } from './cloudwatch.generated';
import { HorizontalAnnotation } from './graph';
import { CreateAlarmOptions } from './metric';
import { BaseAlarmProps, CreateAlarmOptions } from './metric';
import { IMetric } from './metric-types';
import { parseStatistic } from './util.statistic';

Expand All @@ -18,10 +18,20 @@ export interface IAlarm extends IResource {
readonly alarmName: string;
}

/**
* TODO
*/
export interface ComplexAlarmProps extends BaseAlarmProps {
/**
* TODO
*/
readonly alarmTimeSeries: CfnAlarm.MetricDataQueryProperty[]
}

/**
* Properties for Alarms
*/
export interface AlarmProps extends CreateAlarmOptions {
export interface SimpleAlarmProps extends CreateAlarmOptions {
/**
* The metric to add the alarm on
*
Expand All @@ -31,6 +41,8 @@ export interface AlarmProps extends CreateAlarmOptions {
readonly metric: IMetric;
}

export type AlarmProps = SimpleAlarmProps | ComplexAlarmProps;

/**
* Comparison operator for evaluating alarms
*/
Expand Down Expand Up @@ -73,6 +85,10 @@ export enum TreatMissingData {
MISSING = 'missing'
}

function isComplexAlarm(props: ComplexAlarmProps | SimpleAlarmProps): props is ComplexAlarmProps {
return (props as ComplexAlarmProps).alarmTimeSeries !== undefined;
}

/**
* An alarm on a CloudWatch metric
*/
Expand Down Expand Up @@ -103,7 +119,13 @@ export class Alarm extends Resource implements IAlarm {
/**
* The metric object this alarm was based on
*/
public readonly metric: IMetric;
public readonly metric?: IMetric;

/**
* The list of metrics. Used if the alarms requires more than one metric.
* @attribute todo
*/
public readonly alarmTimeSeries?: CfnAlarm.MetricDataQueryProperty[];

private alarmActionArns?: string[];
private insufficientDataActionArns?: string[];
Expand All @@ -121,7 +143,27 @@ export class Alarm extends Resource implements IAlarm {

const comparisonOperator = props.comparisonOperator || ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD;

const config = props.metric.toAlarmConfig();
let config;
let label;
// TODO: refactor
if (isComplexAlarm(props)) {
this.alarmTimeSeries = props.alarmTimeSeries;
config = dropUndef({...props, metrics: props.alarmTimeSeries, alarmTimeSeries: undefined});
} else {
this.metric = props.metric;
const alarmConfig = props.metric.toAlarmConfig();
config = {
...dropUndef(alarmConfig),
// Alarm overrides
...dropUndef({
period: props.period && props.period.toSeconds(),
statistic: renderIfSimpleStatistic(props.statistic),
extendedStatistic: renderIfExtendedStatistic(props.statistic)
})
};

label = `${this.metric} ${OPERATOR_SYMBOLS[comparisonOperator]} ${props.threshold} for ${props.evaluationPeriods} datapoints within ${describePeriod(props.evaluationPeriods * alarmConfig.period)}`;
}

const alarm = new CfnAlarm(this, 'Resource', {
// Meta
Expand All @@ -142,14 +184,7 @@ export class Alarm extends Resource implements IAlarm {
insufficientDataActions: Lazy.listValue({ produce: (() => this.insufficientDataActionArns) }),
okActions: Lazy.listValue({ produce: () => this.okActionArns }),

// Metric
...dropUndef(config),
...dropUndef({
// Alarm overrides
period: props.period && props.period.toSeconds(),
statistic: renderIfSimpleStatistic(props.statistic),
extendedStatistic: renderIfExtendedStatistic(props.statistic),
})
...config
});

this.alarmArn = this.getResourceArnAttribute(alarm.attrArn, {
Expand All @@ -160,10 +195,8 @@ export class Alarm extends Resource implements IAlarm {
});
this.alarmName = this.getResourceNameAttribute(alarm.ref);

this.metric = props.metric;
this.annotation = {
// tslint:disable-next-line:max-line-length
label: `${this.metric} ${OPERATOR_SYMBOLS[comparisonOperator]} ${props.threshold} for ${props.evaluationPeriods} datapoints within ${describePeriod(props.evaluationPeriods * config.period)}`,
label,
value: props.threshold,
};
}
Expand Down
76 changes: 76 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/lib/expression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { CfnAlarm } from "./cloudwatch.generated";
import { AlarmTimeSeriesProps, ITimeSeries, TimeSeriesJson, ToJsonProps } from "./timeseries";
import MetricDataQueryProperty = CfnAlarm.MetricDataQueryProperty;

/**
* TODO
*/
export interface IExpression extends ITimeSeries {
/**
* Math or search expression
*/
expression: string
/**
* Unique ID
*/
id: string
/**
* Label for this expression when added to a Graph in a Dashboard
*/
label?: string
}

/**
* TODO
*/
export interface ExpressionProps {
/**
* TODO
*/
readonly expression: string
/**
* TODO
*/
readonly id: string
/**
* TODO
* @default TODO
*/
readonly label?: string
}

/**
* TODOs
*/
export class Expression implements IExpression {
/**
* TODO
*/
public expression: string;
/**
* TODO
*/
public id: string;
/**
* TODO
* @default TODO
*/
public label?: string;

constructor(props: ExpressionProps) {
this.expression = props.expression;
this.id = props.id;
this.label = props.label;
}

public toJson(_props: ToJsonProps = {}): TimeSeriesJson {
return [this];
}

public toAlarmTimeSeries(props: AlarmTimeSeriesProps = {}): MetricDataQueryProperty {
return {
...this,
returnData: props.returnData
};
}
}
52 changes: 11 additions & 41 deletions packages/@aws-cdk/aws-cloudwatch/lib/graph.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import cdk = require('@aws-cdk/core');
import { IAlarm } from "./alarm";
import { IMetric } from "./metric-types";
import { ConcreteWidget } from "./widget";
import { IAlarm } from './alarm';
import { TimeSeriesJson } from './timeseries';
import { ConcreteWidget } from './widget';

/**
* Basic properties for widgets that display metrics
Expand Down Expand Up @@ -120,14 +120,10 @@ export class AlarmWidget extends ConcreteWidget {
*/
export interface GraphWidgetProps extends MetricWidgetProps {
/**
* Metrics to display on left Y axis
* Time series to display
* @default no time series will be displayed
*/
readonly left?: IMetric[];

/**
* Metrics to display on right Y axis
*/
readonly right?: IMetric[];
readonly timeSeries?: TimeSeriesJson[];

/**
* Annotations for the left Y axis
Expand Down Expand Up @@ -169,8 +165,7 @@ export class GraphWidget extends ConcreteWidget {
public toJson(): any[] {
const horizontalAnnoations = (this.props.leftAnnotations || []).map(mapAnnotation('left')).concat(
(this.props.rightAnnotations || []).map(mapAnnotation('right')));
const metrics = (this.props.left || []).map(m => metricJson(m, 'left')).concat(
(this.props.right || []).map(m => metricJson(m, 'right')));

return [{
type: 'metric',
width: this.width,
Expand All @@ -182,7 +177,7 @@ export class GraphWidget extends ConcreteWidget {
title: this.props.title,
region: this.props.region || cdk.Aws.REGION,
stacked: this.props.stacked,
metrics: metrics.length > 0 ? metrics : undefined,
metrics: this.props.timeSeries,
annotations: horizontalAnnoations.length > 0 ? { horizontal: horizontalAnnoations } : undefined,
yAxis: {
left: this.props.leftYAxis !== undefined ? this.props.leftYAxis : undefined,
Expand All @@ -198,9 +193,9 @@ export class GraphWidget extends ConcreteWidget {
*/
export interface SingleValueWidgetProps extends MetricWidgetProps {
/**
* Metrics to display
* Time series to display
*/
readonly metrics: IMetric[];
readonly timeSeries: TimeSeriesJson[];

/**
* Whether to show the value from the entire time range.
Expand Down Expand Up @@ -232,7 +227,7 @@ export class SingleValueWidget extends ConcreteWidget {
view: 'singleValue',
title: this.props.title,
region: this.props.region || cdk.Aws.REGION,
metrics: this.props.metrics.map(m => metricJson(m, 'left')),
metrics: this.props.timeSeries,
setPeriodToTimeRange: this.props.setPeriodToTimeRange
}
}];
Expand Down Expand Up @@ -299,28 +294,3 @@ function mapAnnotation(yAxis: string): ((x: HorizontalAnnotation) => any) {
return { ...a, yAxis };
};
}

/**
* Return the JSON structure which represents this metric in a graph
*
* This will be called by GraphWidget, no need for clients to call this.
*/
function metricJson(metric: IMetric, yAxis: string): any[] {
const config = metric.toGraphConfig();

// Namespace and metric Name
const ret: any[] = [
config.namespace,
config.metricName,
];

// Dimensions
for (const dim of (config.dimensions || [])) {
ret.push(dim.name, dim.value);
}

// Options
ret.push({ yAxis, ...config.renderingProperties });

return ret;
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export * from './metric';
export * from './metric-types';
export * from './text';
export * from './widget';
export * from './timeseries';
export * from './expression';

// AWS::CloudWatch CloudFormation Resources:
export * from './cloudwatch.generated';
Loading