Skip to content

Commit

Permalink
chore: extract and extend Lambda dashboard tests
Browse files Browse the repository at this point in the history
  • Loading branch information
eoinsha committed Nov 18, 2023
1 parent a50cb7d commit 4c1c44b
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 37 deletions.
6 changes: 4 additions & 2 deletions core/dashboards/dashboard.ts
Expand Up @@ -148,7 +148,8 @@ export default function addDashboard (dashboardConfig: SlicWatchInputDashboardCo
namespace: 'AWS/Lambda',
metric,
dimensions: { FunctionName: `\${${funcLogicalId}}` },
stat: stat as Statistic
stat: stat as Statistic,
yAxis: metricConfig.yAxis
})
}
}
Expand Down Expand Up @@ -176,7 +177,8 @@ export default function addDashboard (dashboardConfig: SlicWatchInputDashboardCo
namespace: 'AWS/Lambda',
metric: 'IteratorAge',
dimensions: { FunctionName: `\${${funcLogicalId}}` },
stat: stat as Statistic
stat: stat as Statistic,
yAxis: metricConfig.yAxis
})),
metricConfig as Widgets
)
Expand Down
189 changes: 189 additions & 0 deletions core/dashboards/tests/dashboard-lambda.test.ts
@@ -0,0 +1,189 @@
import { merge } from 'lodash'

import { test } from 'tap'
import { type MetricWidgetProperties } from 'cloudwatch-dashboard-types'

import addDashboard from '../dashboard'
import defaultConfig from '../../inputs/default-config'

import { createTestCloudFormationTemplate, getDashboardFromTemplate, getDashboardWidgetsByTitle } from '../../tests/testing-utils'
import { type ResourceType } from '../../cf-template'

test('dashboard contains configured Lambda Function resources', (t) => {
t.test('dashboards includes Lambda metrics', (t) => {
const template = createTestCloudFormationTemplate()
addDashboard(defaultConfig.dashboard, template)
const { dashboard } = getDashboardFromTemplate(template)

const widgets = getDashboardWidgetsByTitle(dashboard,
/Lambda Duration Average/,
/Lambda Duration p95/,
/Lambda Duration Maximum/,
/Lambda Invocations/,
/Lambda IteratorAge .* Maximum/,
/Lambda ConcurrentExecutions/,
/Lambda Throttles/,
/Lambda Errors/
)

for (const widget of widgets) {
t.ok(widget)
}

const [
durationAvgWidget, durationP95Widget, durationMaxWidget, invocationsWidget, iteratorAgeWidget, concurrentExecutionsWidget, throttlesWidget, errorsWidget
] = widgets

t.match((durationAvgWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Duration', 'FunctionName', '${HelloLambdaFunction}', { stat: 'Average', yAxis: 'left' }]
])
t.match((durationP95Widget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Duration', 'FunctionName', '${HelloLambdaFunction}', { stat: 'p95', yAxis: 'left' }]
])
t.match((durationMaxWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Duration', 'FunctionName', '${HelloLambdaFunction}', { stat: 'Maximum', yAxis: 'left' }]
])
t.match((invocationsWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Invocations', 'FunctionName', '${HelloLambdaFunction}', { stat: 'Sum', yAxis: 'left' }]
])
t.match((iteratorAgeWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'IteratorAge', 'FunctionName', '${StreamProcessorLambdaFunction}', { stat: 'Maximum', yAxis: 'left' }]
])
t.match((concurrentExecutionsWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'ConcurrentExecutions', 'FunctionName', '${HelloLambdaFunction}', { stat: 'Maximum', yAxis: 'left' }]
])
t.match((throttlesWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Throttles', 'FunctionName', '${HelloLambdaFunction}', { stat: 'Sum', yAxis: 'left' }]
])
t.match((errorsWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Errors', 'FunctionName', '${HelloLambdaFunction}', { stat: 'Sum', yAxis: 'left' }]
])

for (const widget of widgets) {
const props: MetricWidgetProperties = widget.properties as MetricWidgetProperties
t.ok((props.metrics?.length ?? 0) > 0)
t.equal(props.period, 300)
t.equal(props.view, 'timeSeries')
}

t.end()
})

t.test('service config overrides take precedence', (t) => {
const template = createTestCloudFormationTemplate()

const config = merge({}, defaultConfig.dashboard, {
widgets: {
Lambda: {
metricPeriod: 900,
width: 8,
height: 12,
Errors: {
Statistic: ['ts80'],
yAxis: 'right'
},
Throttles: {
Statistic: ['Maximum'],
enabled: false
},
Duration: {
Statistic: ['p90', 'Average', 'Maximum']
},
Invocations: {
Statistic: ['ts90']
},
ConcurrentExecutions: {
Statistic: ['p95']
},
IteratorAge: {
Statistic: ['p98']
}
}
}
})

addDashboard(config, template)

const { dashboard } = getDashboardFromTemplate(template)
const widgets = getDashboardWidgetsByTitle(dashboard,
/Lambda Duration Average/,
/Lambda Duration p90/,
/Lambda Duration Maximum/,
/Lambda Invocations/,
/Lambda IteratorAge .* p98/,
/Lambda ConcurrentExecutions/,
/Lambda Throttles/,
/Lambda Errors/
)

const [
durationAvgWidget, durationP90Widget, durationMaxWidget, invocationsWidget, iteratorAgeWidget, concurrentExecutionsWidget, throttlesWidget, errorsWidget
] = widgets

t.match((durationAvgWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Duration', 'FunctionName', '${HelloLambdaFunction}', { stat: 'Average', yAxis: 'left' }]
])

t.match((durationP90Widget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Duration', 'FunctionName', '${HelloLambdaFunction}', { stat: 'p90', yAxis: 'left' }]
])

t.match((durationMaxWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Duration', 'FunctionName', '${HelloLambdaFunction}', { stat: 'Maximum', yAxis: 'left' }]
])

for (const widget of widgets) {
if (widget !== undefined) {
t.match(widget, { width: 8, height: 12 })
t.equal((widget.properties as MetricWidgetProperties).period, 900)
}
}
t.notOk(throttlesWidget)

t.match((invocationsWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Invocations', 'FunctionName', '${HelloLambdaFunction}', { stat: 'ts90', yAxis: 'left' }]
])
t.match((iteratorAgeWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'IteratorAge', 'FunctionName', '${StreamProcessorLambdaFunction}', { stat: 'p98', yAxis: 'left' }]
])
t.match((concurrentExecutionsWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'ConcurrentExecutions', 'FunctionName', '${HelloLambdaFunction}', { stat: 'p95', yAxis: 'left' }]
])
t.match((errorsWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Errors', 'FunctionName', '${HelloLambdaFunction}', { stat: 'ts80', yAxis: 'right' }]
])

t.end()
})

t.test('resource config overrides take precedence', (t) => {
const template = createTestCloudFormationTemplate();

(template.Resources as ResourceType).HelloLambdaFunction.Metadata = {
slicWatch: {
dashboard: {
Errors: {
yAxis: 'right'
}
}
}
}

addDashboard(defaultConfig.dashboard, template)

const { dashboard } = getDashboardFromTemplate(template)
const widgets = getDashboardWidgetsByTitle(dashboard,
/Lambda Errors/
)

const [errorsWidget] = widgets
t.equal((errorsWidget.properties as MetricWidgetProperties).period, 300)

t.match((errorsWidget.properties as MetricWidgetProperties).metrics, [
['AWS/Lambda', 'Errors', 'FunctionName', '${HelloLambdaFunction}', { stat: 'Sum', yAxis: 'right' }]
])

t.end()
})
t.end()
})
34 changes: 0 additions & 34 deletions core/dashboards/tests/dashboard.test.ts
Expand Up @@ -32,40 +32,6 @@ test('A dashboard includes metrics', (t) => {

t.ok(dashboard.start)

t.test('dashboards includes Lambda metrics', (t) => {
const widgets = dashboard.widgets.filter(({ properties }) =>
((properties as MetricWidgetProperties).title ?? '').startsWith('Lambda')
)
t.equal(widgets.length, 8)
const namespaces = new Set()
for (const widget of widgets) {
const widgetProperties = widget.properties as MetricWidgetProperties
for (const metric of widgetProperties.metrics ?? []) {
t.equal(metric.length, 5)
const metricProperties = metric[4] as object
const propKeys = Object.keys(metricProperties)
t.same(propKeys, ['stat'])
namespaces.add(metric[0])
}
}
t.same(namespaces, new Set(['AWS/Lambda']))
const expectedTitles = new Set([
'Lambda Duration Average per Function',
'Lambda Duration p95 per Function',
'Lambda Duration Maximum per Function',
'Lambda Invocations Sum per Function',
'Lambda IteratorAge ${StreamProcessorLambdaFunction} Maximum',
'Lambda ConcurrentExecutions Maximum per Function',
'Lambda Throttles Sum per Function',
'Lambda Errors Sum per Function'
])
const actualTitles = new Set(
widgets.map((widget) => (widget.properties as MetricWidgetProperties).title)
)
t.same(actualTitles, expectedTitles)
t.end()
})

t.test('dashboards includes Step Function metrics', (t) => {
const widgets = dashboard.widgets.filter((widget) => {
const widgetProperties = widget.properties as MetricWidgetProperties
Expand Down
2 changes: 1 addition & 1 deletion core/tests/testing-utils.ts
Expand Up @@ -29,7 +29,7 @@ export function assertCommonAlarmProperties (t, al: AlarmProperties) {

/**
* Derive an alarm 'type' by stripping the last component from the underscore-delimited name
* @param {*} alarmName The alarm name as a string or {'Fn::Sub': ...} object
* @param alarmName The alarm name as a string or {'Fn::Sub': ...} object
* @returns The inferred type
*/
export function alarmNameToType (alarmName) {
Expand Down

0 comments on commit 4c1c44b

Please sign in to comment.