Skip to content

Commit 02b9ebc

Browse files
smithcauemarcondeselasticmachine
authored
[APM] Add error rates to Service Map popovers (#69520) (#71994)
Make the `getErrorRate` function used in the error rate charts additionally take `service.environment` as a filter and have it return the `average` of the values. Call that function in the API for the service map metrics. Fixes #68160. Co-authored-by: cauemarcondes <caue.marcondes@elastic.co> Co-authored-by: cauemarcondes <caue.marcondes@elastic.co> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 897cc34 commit 02b9ebc

File tree

15 files changed

+568
-351
lines changed

15 files changed

+568
-351
lines changed

x-pack/plugins/apm/common/service_map.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ export interface Connection {
3636
destination: ConnectionNode;
3737
}
3838

39-
export interface ServiceNodeMetrics {
39+
export interface ServiceNodeStats {
4040
avgMemoryUsage: number | null;
4141
avgCpuUsage: number | null;
4242
transactionStats: {
4343
avgTransactionDuration: number | null;
4444
avgRequestsPerMinute: number | null;
4545
};
46-
avgErrorsPerMinute: number | null;
46+
avgErrorRate: number | null;
4747
}
4848

4949
export function isValidPlatinumLicense(license: ILicense) {

x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import cytoscape from 'cytoscape';
1414
import React, { MouseEvent } from 'react';
1515
import { Buttons } from './Buttons';
1616
import { Info } from './Info';
17-
import { ServiceMetricFetcher } from './ServiceMetricFetcher';
17+
import { ServiceStatsFetcher } from './ServiceStatsFetcher';
1818
import { popoverWidth } from '../cytoscapeOptions';
1919

2020
interface ContentsProps {
@@ -70,7 +70,7 @@ export function Contents({
7070
</FlexColumnItem>
7171
<FlexColumnItem>
7272
{isService ? (
73-
<ServiceMetricFetcher
73+
<ServiceStatsFetcher
7474
serviceName={selectedNodeServiceName}
7575
serviceAnomalyStats={selectedNodeData.serviceAnomalyStats}
7676
/>

x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ export function Info(data: InfoProps) {
3838

3939
const listItems = [
4040
{
41-
title: i18n.translate('xpack.apm.serviceMap.typePopoverMetric', {
41+
title: i18n.translate('xpack.apm.serviceMap.typePopoverStat', {
4242
defaultMessage: 'Type',
4343
}),
4444
description: type,
4545
},
4646
{
47-
title: i18n.translate('xpack.apm.serviceMap.subtypePopoverMetric', {
47+
title: i18n.translate('xpack.apm.serviceMap.subtypePopoverStat', {
4848
defaultMessage: 'Subtype',
4949
}),
5050
description: subtype,

x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx

Lines changed: 122 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,128 @@
55
*/
66

77
import { storiesOf } from '@storybook/react';
8+
import cytoscape from 'cytoscape';
9+
import { HttpSetup } from 'kibana/public';
810
import React from 'react';
9-
import { ServiceMetricList } from './ServiceMetricList';
11+
import { EuiThemeProvider } from '../../../../../../observability/public';
12+
import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext';
13+
import { MockUrlParamsContextProvider } from '../../../../context/UrlParamsContext/MockUrlParamsContextProvider';
14+
import { createCallApmApi } from '../../../../services/rest/createCallApmApi';
15+
import { CytoscapeContext } from '../Cytoscape';
16+
import { Popover } from './';
17+
import { ServiceStatsList } from './ServiceStatsList';
1018

11-
storiesOf('app/ServiceMap/Popover/ServiceMetricList', module)
12-
.add('example', () => (
13-
<ServiceMetricList
14-
avgErrorsPerMinute={15.738888706725826}
15-
transactionStats={{
16-
avgTransactionDuration: 61634.38905590272,
19+
storiesOf('app/ServiceMap/Popover', module)
20+
.addDecorator((storyFn) => {
21+
const node = {
22+
data: { id: 'example service', 'service.name': 'example service' },
23+
};
24+
const cy = cytoscape({ elements: [node] });
25+
const httpMock = ({
26+
get: async () => ({
27+
avgCpuUsage: 0.32809666568309237,
28+
avgErrorRate: 0.556068173242986,
29+
avgMemoryUsage: 0.5504868173242986,
1730
avgRequestsPerMinute: 164.47222031860858,
18-
}}
19-
avgCpuUsage={0.32809666568309237}
20-
avgMemoryUsage={0.5504868173242986}
21-
/>
22-
))
23-
.add('some null values', () => (
24-
<ServiceMetricList
25-
avgErrorsPerMinute={7.615972134074397}
26-
transactionStats={{
27-
avgTransactionDuration: 238792.54809512055,
28-
avgRequestsPerMinute: 8.439583235652972,
29-
}}
30-
avgCpuUsage={null}
31-
avgMemoryUsage={null}
32-
/>
33-
))
34-
.add('all null values', () => (
35-
<ServiceMetricList
36-
avgErrorsPerMinute={null}
37-
transactionStats={{
38-
avgTransactionDuration: null,
39-
avgRequestsPerMinute: null,
40-
}}
41-
avgCpuUsage={null}
42-
avgMemoryUsage={null}
43-
/>
44-
));
31+
avgTransactionDuration: 61634.38905590272,
32+
}),
33+
} as unknown) as HttpSetup;
34+
35+
createCallApmApi(httpMock);
36+
37+
setImmediate(() => {
38+
cy.$('example service').select();
39+
});
40+
41+
return (
42+
<EuiThemeProvider>
43+
<MockUrlParamsContextProvider>
44+
<MockApmPluginContextWrapper>
45+
<CytoscapeContext.Provider value={cy}>
46+
<div style={{ height: 325 }}>{storyFn()}</div>
47+
</CytoscapeContext.Provider>
48+
</MockApmPluginContextWrapper>
49+
</MockUrlParamsContextProvider>
50+
</EuiThemeProvider>
51+
);
52+
})
53+
.add(
54+
'example',
55+
() => {
56+
return <Popover />;
57+
},
58+
{
59+
info: {
60+
propTablesExclude: [
61+
CytoscapeContext.Provider,
62+
MockApmPluginContextWrapper,
63+
MockUrlParamsContextProvider,
64+
EuiThemeProvider,
65+
],
66+
source: false,
67+
},
68+
}
69+
);
70+
71+
storiesOf('app/ServiceMap/Popover/ServiceStatsList', module)
72+
.addDecorator((storyFn) => <EuiThemeProvider>{storyFn()}</EuiThemeProvider>)
73+
.add(
74+
'example',
75+
() => (
76+
<ServiceStatsList
77+
avgCpuUsage={0.32809666568309237}
78+
avgMemoryUsage={0.5504868173242986}
79+
transactionStats={{
80+
avgRequestsPerMinute: 164.47222031860858,
81+
avgTransactionDuration: 61634.38905590272,
82+
}}
83+
avgErrorRate={0.556068173242986}
84+
/>
85+
),
86+
{ info: { propTablesExclude: [EuiThemeProvider] } }
87+
)
88+
.add(
89+
'loading',
90+
() => (
91+
<ServiceStatsList
92+
avgCpuUsage={null}
93+
avgErrorRate={null}
94+
avgMemoryUsage={null}
95+
transactionStats={{
96+
avgRequestsPerMinute: null,
97+
avgTransactionDuration: null,
98+
}}
99+
/>
100+
),
101+
{ info: { propTablesExclude: [EuiThemeProvider] } }
102+
)
103+
.add(
104+
'some null values',
105+
() => (
106+
<ServiceStatsList
107+
avgCpuUsage={null}
108+
avgErrorRate={0.615972134074397}
109+
avgMemoryUsage={null}
110+
transactionStats={{
111+
avgRequestsPerMinute: 8.439583235652972,
112+
avgTransactionDuration: 238792.54809512055,
113+
}}
114+
/>
115+
),
116+
{ info: { propTablesExclude: [EuiThemeProvider] } }
117+
)
118+
.add(
119+
'all null values',
120+
() => (
121+
<ServiceStatsList
122+
avgCpuUsage={null}
123+
avgErrorRate={null}
124+
avgMemoryUsage={null}
125+
transactionStats={{
126+
avgRequestsPerMinute: null,
127+
avgTransactionDuration: null,
128+
}}
129+
/>
130+
),
131+
{ info: { propTablesExclude: [EuiThemeProvider] } }
132+
);

x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricFetcher.tsx renamed to x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,44 @@ import {
1313
} from '@elastic/eui';
1414
import { i18n } from '@kbn/i18n';
1515
import { isNumber } from 'lodash';
16-
import { ServiceNodeMetrics } from '../../../../../common/service_map';
16+
import { ServiceNodeStats } from '../../../../../common/service_map';
17+
import { ServiceStatsList } from './ServiceStatsList';
1718
import { useFetcher, FETCH_STATUS } from '../../../../hooks/useFetcher';
1819
import { useUrlParams } from '../../../../hooks/useUrlParams';
19-
import { ServiceMetricList } from './ServiceMetricList';
2020
import { AnomalyDetection } from './AnomalyDetection';
2121
import { ServiceAnomalyStats } from '../../../../../common/anomaly_detection';
2222

23-
interface ServiceMetricFetcherProps {
23+
interface ServiceStatsFetcherProps {
24+
environment?: string;
2425
serviceName: string;
2526
serviceAnomalyStats: ServiceAnomalyStats | undefined;
2627
}
2728

28-
export function ServiceMetricFetcher({
29+
export function ServiceStatsFetcher({
2930
serviceName,
3031
serviceAnomalyStats,
31-
}: ServiceMetricFetcherProps) {
32+
}: ServiceStatsFetcherProps) {
3233
const {
33-
urlParams: { start, end, environment },
34+
urlParams: { start, end },
35+
uiFilters,
3436
} = useUrlParams();
3537

3638
const {
37-
data = { transactionStats: {} } as ServiceNodeMetrics,
39+
data = { transactionStats: {} } as ServiceNodeStats,
3840
status,
3941
} = useFetcher(
4042
(callApmApi) => {
4143
if (serviceName && start && end) {
4244
return callApmApi({
4345
pathname: '/api/apm/service-map/service/{serviceName}',
44-
params: { path: { serviceName }, query: { start, end, environment } },
46+
params: {
47+
path: { serviceName },
48+
query: { start, end, uiFilters: JSON.stringify(uiFilters) },
49+
},
4550
});
4651
}
4752
},
48-
[serviceName, start, end, environment],
53+
[serviceName, start, end, uiFilters],
4954
{
5055
preservePreviousData: false,
5156
}
@@ -60,20 +65,20 @@ export function ServiceMetricFetcher({
6065

6166
const {
6267
avgCpuUsage,
63-
avgErrorsPerMinute,
68+
avgErrorRate,
6469
avgMemoryUsage,
6570
transactionStats: { avgRequestsPerMinute, avgTransactionDuration },
6671
} = data;
6772

6873
const hasServiceData = [
6974
avgCpuUsage,
70-
avgErrorsPerMinute,
75+
avgErrorRate,
7176
avgMemoryUsage,
7277
avgRequestsPerMinute,
7378
avgTransactionDuration,
7479
].some((stat) => isNumber(stat));
7580

76-
if (environment && !hasServiceData) {
81+
if (!hasServiceData) {
7782
return (
7883
<EuiText color="subdued">
7984
{i18n.translate('xpack.apm.serviceMap.popoverMetrics.noDataText', {
@@ -93,7 +98,7 @@ export function ServiceMetricFetcher({
9398
<EuiHorizontalRule margin="xs" />
9499
</>
95100
)}
96-
<ServiceMetricList {...data} />
101+
<ServiceStatsList {...data} />
97102
</>
98103
);
99104
}

x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx renamed to x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsList.tsx

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n';
88
import { isNumber } from 'lodash';
99
import React from 'react';
1010
import styled from 'styled-components';
11-
import { ServiceNodeMetrics } from '../../../../../common/service_map';
11+
import { ServiceNodeStats } from '../../../../../common/service_map';
1212
import { asDuration, asPercent, tpmUnit } from '../../../../utils/formatters';
1313

1414
export const ItemRow = styled('tr')`
@@ -24,18 +24,18 @@ export const ItemDescription = styled('td')`
2424
text-align: right;
2525
`;
2626

27-
type ServiceMetricListProps = ServiceNodeMetrics;
27+
type ServiceStatsListProps = ServiceNodeStats;
2828

29-
export function ServiceMetricList({
30-
avgErrorsPerMinute,
29+
export function ServiceStatsList({
30+
transactionStats,
31+
avgErrorRate,
3132
avgCpuUsage,
3233
avgMemoryUsage,
33-
transactionStats,
34-
}: ServiceMetricListProps) {
34+
}: ServiceStatsListProps) {
3535
const listItems = [
3636
{
3737
title: i18n.translate(
38-
'xpack.apm.serviceMap.avgTransDurationPopoverMetric',
38+
'xpack.apm.serviceMap.avgTransDurationPopoverStat',
3939
{
4040
defaultMessage: 'Trans. duration (avg.)',
4141
}
@@ -58,27 +58,21 @@ export function ServiceMetricList({
5858
: null,
5959
},
6060
{
61-
title: i18n.translate(
62-
'xpack.apm.serviceMap.avgErrorsPerMinutePopoverMetric',
63-
{
64-
defaultMessage: 'Errors per minute (avg.)',
65-
}
66-
),
67-
description: avgErrorsPerMinute?.toFixed(2),
61+
title: i18n.translate('xpack.apm.serviceMap.errorRatePopoverStat', {
62+
defaultMessage: 'Error rate (avg.)',
63+
}),
64+
description: isNumber(avgErrorRate) ? asPercent(avgErrorRate, 1) : null,
6865
},
6966
{
70-
title: i18n.translate('xpack.apm.serviceMap.avgCpuUsagePopoverMetric', {
67+
title: i18n.translate('xpack.apm.serviceMap.avgCpuUsagePopoverStat', {
7168
defaultMessage: 'CPU usage (avg.)',
7269
}),
7370
description: isNumber(avgCpuUsage) ? asPercent(avgCpuUsage, 1) : null,
7471
},
7572
{
76-
title: i18n.translate(
77-
'xpack.apm.serviceMap.avgMemoryUsagePopoverMetric',
78-
{
79-
defaultMessage: 'Memory usage (avg.)',
80-
}
81-
),
73+
title: i18n.translate('xpack.apm.serviceMap.avgMemoryUsagePopoverStat', {
74+
defaultMessage: 'Memory usage (avg.)',
75+
}),
8276
description: isNumber(avgMemoryUsage)
8377
? asPercent(avgMemoryUsage, 1)
8478
: null,
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { Logger } from 'src/core/server';
8+
import { UIFilters } from '../../../../typings/ui_filters';
9+
10+
export function getParsedUiFilters({
11+
uiFilters,
12+
logger,
13+
}: {
14+
uiFilters: string;
15+
logger: Logger;
16+
}): UIFilters {
17+
try {
18+
return JSON.parse(uiFilters);
19+
} catch (error) {
20+
logger.error(error);
21+
}
22+
return {};
23+
}

0 commit comments

Comments
 (0)