Skip to content

feat(cloudwatch): add contributor insights widget #13008

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion packages/@aws-cdk/aws-cloudwatch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ alarm.addAlarmAction(new cw_actions.SnsAction(topic));

### Composite Alarms

[Composite Alarms](https://aws.amazon.com/about-aws/whats-new/2020/03/amazon-cloudwatch-now-allows-you-to-combine-multiple-alarms/)
[Composite Alarms](https://aws.amazon.com/about-aws/whats-new/2020/03/amazon-cloudwatch-now-allows-you-to-combine-multiple-alarms/)
can be created from existing Alarm resources.

```ts
Expand Down Expand Up @@ -375,6 +375,18 @@ dashboard.addWidgets(new LogQueryWidget({
}));
```

### Contributor Insights widget

A `ContributorInsightsWidget` displays data from a Contributor Insights rule:

```ts
dashboard.addWidgets(new cloudwatch.ContributorInsightsWidget({
title: 'Top 50 callers',
ruleName: 'my-contributor-insights-rule',
contributorSelection: cloudwatch.TopContributors.TOP50,
}));
```

### Dashboard Layout

The widgets on a dashboard are visually laid out in a grid that is 24 columns
Expand Down
170 changes: 170 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/lib/contributor-insights-widget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import * as cdk from '@aws-cdk/core';
import { LegendPosition } from './graph';
import { ConcreteWidget } from './widget';
/**
* The number of top contributors to show.
*/
export enum TopContributors {
/**
* The top 10 contributors
*/
TOP10 = 10,
/**
* The top 25 contributors
*/
TOP25 = 25,
/**
* The top 50 contributors
*/
TOP50 = 50,
/**
* The top 100 contributors
*/
TOP100 = 100,
}

/**
* Statistic to use over the aggregation period
*/
export enum OrderStatistic {
/**
* All values submitted for the matching metric added together.
* This statistic can be useful for determining the total volume of a metric.
*/
SUM = 'Sum',
/**
* The highest value observed during the specified period.
* You can use this value to determine high volumes of activity for your application.
*/
MAXIMUM = 'Maximum'
}

/**
* Properties for a Contributor Insights widget
*/
export interface ContributorInsightsWidgetProps {
/**
* Title for the widget
*
* @default No title
*/
readonly title?: string;

/**
* Insights rule to display
*/
readonly ruleName: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is an "insights rule" a resource that is created independently?

In that case, referencing it should follow the same rules as referencing other AWS resources: we don't accept buckets as bucketName: string, we accept bucket as bucket: IBucket.

By that same token, we're probably going to need an IInsightRule and an InsightRule.fromInsightRuleName() to produce one.

You could go as far as completely fill out the L2 for AWS::CloudWatch::InsightRule but I will accept an abstract class that only allows importing existing insight rules as a minimal delta from what we have, to move this PR forward.


/**
* The selection of contributors to display
*
* @default Top 10
*/
readonly contributorSelection?: TopContributors;

/**
* Position of the legend
*
* @default - bottom
*/
readonly legendPosition?: LegendPosition;

/**
* The region the metrics of this widget should be taken from
*
* @default Current region
*/
readonly region?: string;

/**
* Whether the graph should be shown as stacked lines
*
* @default false
*/
readonly stacked?: boolean;

/**
* What function to use for aggregating.
*
* @default Statistic.SUM
*/
readonly statistic?: OrderStatistic;

/**
* The period over which the statistics are applied.
*
* @default Duration.minutes(5)
*/
readonly period?: cdk.Duration;

/**
* Width of the widget, in a grid of 24 units wide
*
* @default 6
*/
readonly width?: number;

/**
* Height of the widget
*
* @default 6
*/
readonly height?: number;

/**
* Account the contributor insights data comes from
*
* @default Deployment account
*/
readonly account?: string;
}

/**
* Display contributor insights
*/
export class ContributorInsightsWidget extends ConcreteWidget {
private readonly props: ContributorInsightsWidgetProps;
private period: number;

constructor(props: ContributorInsightsWidgetProps) {
super(props.width || 6, props.height || 6);

this.period = (props.period || cdk.Duration.minutes(5)).toSeconds();
if (![60, 300, 900, 3600, 21600, 86400].includes(this.period)) {
throw new Error(
`'period' must be 60s, 300s, 900s, 3600s, 21600s, or 86400s, received ${this.period}`,
);
}
this.props = props;
}

public toJson(): any[] {
return [
{
type: 'metric',
width: this.width,
height: this.height,
x: this.x,
y: this.y,
stacked: this.props.stacked || false,
properties: {
accountId: this.props.account,
period: this.period,
view: 'timeSeries',
title: this.props.title,
region: this.props.region || cdk.Aws.REGION,
insightRule: {
maxContributorCount:
this.props.contributorSelection || TopContributors.TOP10,
orderBy: this.props.statistic || OrderStatistic.SUM,
ruleName: this.props.ruleName,
},
legend:
this.props.legendPosition !== undefined
? { position: this.props.legendPosition }
: undefined,
},
},
];
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-cloudwatch/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './log-query';
export * from './text';
export * from './widget';
export * from './alarm-status-widget';
export * from './contributor-insights-widget';

// AWS::CloudWatch CloudFormation Resources:
export * from './cloudwatch.generated';
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@
"QueueName"
]
},
"\"]],\"singleValueFullPrecision\":true}}]}"
"\"]],\"singleValueFullPrecision\":true}},{\"type\":\"metric\",\"width\":6,\"height\":6,\"x\":0,\"y\":53,\"stacked\":false,\"properties\":{\"period\":300,\"view\":\"timeSeries\",\"title\":\"Top 50 callers\",\"region\":\"",
{
"Ref": "AWS::Region"
},
"\",\"insightRule\":{\"maxContributorCount\":50,\"orderBy\":\"Sum\",\"ruleName\":\"my-contributor-insights-rule\"}}}]}"
]
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,10 @@ dashboard.addWidgets(new cloudwatch.SingleValueWidget({
metrics: [sentMessageSizeMetric],
fullPrecision: true,
}));
dashboard.addWidgets(new cloudwatch.ContributorInsightsWidget({
title: 'Top 50 callers',
ruleName: 'my-contributor-insights-rule',
contributorSelection: cloudwatch.TopContributors.TOP50,
}));

app.synth();
34 changes: 33 additions & 1 deletion packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Stack } from '@aws-cdk/core';
import { Test } from 'nodeunit';
import { Alarm, AlarmWidget, Color, GraphWidget, GraphWidgetView, LegendPosition, LogQueryWidget, Metric, Shading, SingleValueWidget, LogQueryVisualizationType } from '../lib';
import { Alarm, AlarmWidget, Color, GraphWidget, GraphWidgetView, LegendPosition, LogQueryWidget, Metric, Shading, SingleValueWidget, LogQueryVisualizationType, ContributorInsightsWidget } from '../lib';

export = {
'add stacked property to graphs'(test: Test) {
Expand Down Expand Up @@ -317,6 +317,38 @@ export = {
test.done();
},


'contributor insights widget'(test: Test) {
// GIVEN
const stack = new Stack();
const ruleName = 'my-contributor-insights-rule';

// WHEN
const widget = new ContributorInsightsWidget({
ruleName,
});

// THEN
test.deepEqual(stack.resolve(widget.toJson()), [{
type: 'metric',
width: 6,
height: 6,
stacked: false,
properties: {
period: 300,
view: 'timeSeries',
region: { Ref: 'AWS::Region' },
insightRule: {
maxContributorCount: 10,
orderBy: 'Sum',
ruleName: 'my-contributor-insights-rule',
},
},
}]);

test.done();
},

'alarm widget'(test: Test) {
// GIVEN
const stack = new Stack();
Expand Down