Skip to content

Commit

Permalink
fix: remove duplicate metrics from API GW widgets
Browse files Browse the repository at this point in the history
  • Loading branch information
eoinsha committed Dec 5, 2023
1 parent b6d3605 commit 763a843
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 103 deletions.
8 changes: 5 additions & 3 deletions core/alarms/api-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,15 @@ export default function createApiGatewayAlarms (
apiGwAlarmsConfig: SlicWatchApiGwAlarmsConfig<SlicWatchMergedConfig>, alarmActionsConfig: AlarmActionsConfig, compiledTemplate: Template
): CloudFormationResources {
const resources: CloudFormationResources = {}
const configuredResources = getResourceAlarmConfigurationsByType('AWS::ApiGateway::RestApi', compiledTemplate, apiGwAlarmsConfig)
const configuredResources = getResourceAlarmConfigurationsByType(
'AWS::ApiGateway::RestApi', compiledTemplate, apiGwAlarmsConfig
)

for (const [apiLogicalId, apiResource] of Object.entries(configuredResources.resources)) {
for (const metric of executionMetrics) {
const mergedConfig: SlicWatchMergedConfig = configuredResources.alarmConfigurations[apiLogicalId][metric]
if (mergedConfig.enabled) {
const { enabled, ...rest } = mergedConfig
const { enabled, ...rest } = mergedConfig
if (enabled) {
const apiName = resolveRestApiNameAsCfn(apiResource, apiLogicalId)
const apiNameForSub = resolveRestApiNameForSub(apiResource, apiLogicalId)
const apiAlarmProperties: AlarmProperties = {
Expand Down
2 changes: 1 addition & 1 deletion core/alarms/tests/api-gateway.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ test('API Gateway alarms are created', (t) => {
t.end()
})

test('API Gateway resource configuration overrides take precedence', (t) => {
test('resource config overrides take precedence', (t) => {
const testConfig = createTestConfig(defaultConfig.alarms)
const template = createTestCloudFormationTemplate();

Expand Down
4 changes: 2 additions & 2 deletions core/alarms/tests/sqs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ test('queue resource configuration overrides take precedence', (t) => {
alarms: {
Period: 900,
AgeOfOldestMessage: {
Statistic: 'P99',
Statistic: 'p99',
Threshold: 51,
enabled: true // this one is disabled by default
},
Expand All @@ -190,7 +190,7 @@ test('queue resource configuration overrides take precedence', (t) => {
const inFlightMessagesFifoAlarm = Object.entries(alarmResources).filter(([key, value]) => key.includes('fifo') && value?.Properties?.MetricName === 'ApproximateNumberOfMessagesNotVisible')[0][1]
const inFlightMessagesRegularAlarm = Object.entries(alarmResources).filter(([key, value]) => key.includes('regular') && value?.Properties?.MetricName === 'ApproximateNumberOfMessagesNotVisible')[0][1]
t.equal(ageOfOldestMessageAlarm?.Properties?.Threshold, 51)
t.equal(ageOfOldestMessageAlarm?.Properties?.Statistic, 'P99')
t.equal(ageOfOldestMessageAlarm?.Properties?.Statistic, 'p99')
t.equal(ageOfOldestMessageAlarm?.Properties?.Period, 900)
t.equal(inFlightMessagesFifoAlarm?.Properties?.Period, 60)
t.equal(inFlightMessagesFifoAlarm?.Properties?.Threshold, Math.floor(0.8 * 20000))
Expand Down
2 changes: 1 addition & 1 deletion core/cf-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { merge } from 'lodash'

const logger = getLogger()

export type ResourceType = Record<string, Resource>
export type ResourceType<T = Resource> = Record<string, T>

/**
* Take a CloudFormation reference to a Lambda Function name and attempt to resolve this function's
Expand Down
9 changes: 6 additions & 3 deletions core/dashboards/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export default function addDashboard (dashboardConfig: SlicWatchInputDashboardCo
/**
* Create a metric for the specified metrics
*
* TODO - allow Widget Metric Properties to specify x and y axes configuration
*
* @param {string} title The metric title
* @param {Array.<object>} metricDefs The metric definitions to render
* @param {Object} config Cascaded widget/metric configuration
Expand Down Expand Up @@ -213,9 +215,9 @@ export default function addDashboard (dashboardConfig: SlicWatchInputDashboardCo
const apiWidgets: WidgetWithSize[] = []
for (const [logicalId, res] of Object.entries(configuredResources.resources)) {
const apiName: string = resolveRestApiNameForSub(res, logicalId) // e.g., ${AWS::Stack} (Ref), ${OtherResource.Name} (GetAtt)
const widgetMetrics: MetricDefs[] = []
const mergedConfig = configuredResources.dashConfigurations[logicalId]
for (const [metric, metricConfig] of Object.entries(getConfiguredMetrics(mergedConfig))) {
const widgetMetrics: MetricDefs[] = []
if (metricConfig.enabled) {
for (const stat of metricConfig.Statistic) {
widgetMetrics.push({
Expand All @@ -224,15 +226,16 @@ export default function addDashboard (dashboardConfig: SlicWatchInputDashboardCo
dimensions: {
ApiName: apiName
},
stat
stat,
yAxis: metricConfig.yAxis
})
}
}
if (widgetMetrics.length > 0) {
const metricStatWidget = createMetricWidget(
`${metric} API ${apiName}`,
widgetMetrics,
apiGwDashConfig
metricConfig
)
apiWidgets.push(metricStatWidget)
}
Expand Down
120 changes: 120 additions & 0 deletions core/dashboards/tests/dashboard-apigw.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
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 API Gateway resources', (t) => {
t.test('includes API metrics', (t) => {
const template = createTestCloudFormationTemplate()
addDashboard(defaultConfig.dashboard, template)

const { dashboard } = getDashboardFromTemplate(template)
const widgets = getDashboardWidgetsByTitle(dashboard,
/4XXError API /,
/5XXError API /,
/Count API /,
/Latency API /
)
const [code4xxWidget, code5xxWidget, countWidget, latencyWidget] = widgets

t.same((code4xxWidget.properties as MetricWidgetProperties).metrics, [
['AWS/ApiGateway', '4XXError', 'ApiName', 'dev-serverless-test-project', { stat: 'Sum', yAxis: 'left' }]
])

t.same((code5xxWidget.properties as MetricWidgetProperties).metrics, [
['AWS/ApiGateway', '5XXError', 'ApiName', 'dev-serverless-test-project', { stat: 'Sum', yAxis: 'left' }]
])

t.same((countWidget.properties as MetricWidgetProperties).metrics, [
['AWS/ApiGateway', 'Count', 'ApiName', 'dev-serverless-test-project', { stat: 'Sum', yAxis: 'left' }]
])

t.same((latencyWidget.properties as MetricWidgetProperties).metrics, [
['AWS/ApiGateway', 'Latency', 'ApiName', 'dev-serverless-test-project', { stat: 'Average', yAxis: 'left' }],
['AWS/ApiGateway', 'Latency', 'ApiName', 'dev-serverless-test-project', { stat: 'p95', 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('resource config overrides take precedence', (t) => {
const template = createTestCloudFormationTemplate();

(template.Resources as ResourceType).ApiGatewayRestApi.Metadata = {
slicWatch: {
dashboard: {
metricPeriod: 900,
width: 24,
height: 12,
'5XXError': {
enabled: true,
Statistic: ['p95']
},
'4XXError': {
enabled: false,
Statistic: ['p10']
},
Count: {
Statistic: ['Maximum', 'Minimum']
},
Latency: {
yAxis: 'right',
width: 24,
height: 24,
Statistic: ['Average', 'p50']
}
}
}
}

addDashboard(defaultConfig.dashboard, template)

const { dashboard } = getDashboardFromTemplate(template)
const widgets = getDashboardWidgetsByTitle(dashboard,
/4XXError API /,
/5XXError API /,
/Count API /,
/Latency API /
)
const [code4xxWidget, code5xxWidget, countWidget, latencyWidget] = widgets

t.notOk(code4xxWidget)

t.same((code5xxWidget.properties as MetricWidgetProperties).metrics, [
['AWS/ApiGateway', '5XXError', 'ApiName', 'dev-serverless-test-project', { stat: 'p95', yAxis: 'left' }]
])
t.match(code5xxWidget, { width: 24, height: 12 })

t.same((countWidget.properties as MetricWidgetProperties).metrics, [
['AWS/ApiGateway', 'Count', 'ApiName', 'dev-serverless-test-project', { stat: 'Maximum', yAxis: 'left' }],
['AWS/ApiGateway', 'Count', 'ApiName', 'dev-serverless-test-project', { stat: 'Minimum', yAxis: 'left' }]
])

t.same((latencyWidget.properties as MetricWidgetProperties).metrics, [
['AWS/ApiGateway', 'Latency', 'ApiName', 'dev-serverless-test-project', { stat: 'Average', yAxis: 'right' }],
['AWS/ApiGateway', 'Latency', 'ApiName', 'dev-serverless-test-project', { stat: 'p50', yAxis: 'right' }]
])
t.match(latencyWidget, { width: 24, height: 24 })

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

t.end()
})
t.end()
})
Loading

0 comments on commit 763a843

Please sign in to comment.