Skip to content

Commit 7d05875

Browse files
committed
Reorganize getServiceTransactionGroups
1 parent df606c5 commit 7d05875

File tree

7 files changed

+462
-304
lines changed

7 files changed

+462
-304
lines changed

x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ import { ServiceOverviewTable } from '../service_overview_table';
3838
type ServiceTransactionGroupItem = ValuesType<
3939
APIReturnType<
4040
'GET /api/apm/services/{serviceName}/overview_transaction_groups'
41-
>['transaction_groups']
41+
>['transactionGroups']
4242
>;
4343

4444
interface Props {
4545
serviceName: string;
4646
}
4747

48-
type SortField = 'latency' | 'traffic' | 'error_rate' | 'impact';
48+
type SortField = 'latency' | 'traffic' | 'errorRate' | 'impact';
4949
type SortDirection = 'asc' | 'desc';
5050

5151
const PAGE_SIZE = 5;
@@ -118,8 +118,8 @@ export function ServiceOverviewTransactionsTable(props: Props) {
118118
},
119119
}).then((response) => {
120120
return {
121-
items: response.transaction_groups,
122-
totalItemCount: response.total_transaction_groups,
121+
items: response.transactionGroups,
122+
totalItemCount: response.totalTransactionGroups,
123123
tableOptions: {
124124
pageIndex: tableOptions.pageIndex,
125125
sort: {
@@ -154,7 +154,7 @@ export function ServiceOverviewTransactionsTable(props: Props) {
154154
defaultMessage: 'Name',
155155
}
156156
),
157-
render: (_, { name, transaction_type: transactionType }) => {
157+
render: (_, { name, transactionType }) => {
158158
return (
159159
<TransactionGroupLinkWrapper>
160160
<EuiToolTip delay="long" content={name}>
@@ -223,7 +223,7 @@ export function ServiceOverviewTransactionsTable(props: Props) {
223223
}
224224
),
225225
width: px(unit * 8),
226-
render: (_, { error_rate: errorRate }) => {
226+
render: (_, { errorRate }) => {
227227
return (
228228
<SparkPlotWithValueLabel
229229
color="euiColorVis7"
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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 { PromiseReturnType } from '../../../../../observability/typings/common';
8+
import { EventOutcome } from '../../../../common/event_outcome';
9+
import { rangeFilter } from '../../../../common/utils/range_filter';
10+
import {
11+
EVENT_OUTCOME,
12+
SERVICE_NAME,
13+
TRANSACTION_NAME,
14+
TRANSACTION_TYPE,
15+
} from '../../../../common/elasticsearch_fieldnames';
16+
17+
import { ESFilter } from '../../../../../../typings/elasticsearch';
18+
import {
19+
getProcessorEventForAggregatedTransactions,
20+
getTransactionDurationFieldForAggregatedTransactions,
21+
} from '../../helpers/aggregated_transactions';
22+
import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client';
23+
import { getBucketSize } from '../../helpers/get_bucket_size';
24+
25+
export type TransactionGroupTimeseriesData = PromiseReturnType<
26+
typeof getTimeseriesDataForTransactionGroups
27+
>;
28+
29+
export async function getTimeseriesDataForTransactionGroups({
30+
apmEventClient,
31+
start,
32+
end,
33+
serviceName,
34+
transactionNames,
35+
esFilter,
36+
searchAggregatedTransactions,
37+
size,
38+
numBuckets,
39+
}: {
40+
apmEventClient: APMEventClient;
41+
start: number;
42+
end: number;
43+
serviceName: string;
44+
transactionNames: string[];
45+
esFilter: ESFilter[];
46+
searchAggregatedTransactions: boolean;
47+
size: number;
48+
numBuckets: number;
49+
}) {
50+
const { intervalString } = getBucketSize(start, end, numBuckets);
51+
52+
const timeseriesResponse = await apmEventClient.search({
53+
apm: {
54+
events: [
55+
getProcessorEventForAggregatedTransactions(
56+
searchAggregatedTransactions
57+
),
58+
],
59+
},
60+
body: {
61+
size: 0,
62+
query: {
63+
bool: {
64+
filter: [
65+
{ terms: { [TRANSACTION_NAME]: transactionNames } },
66+
{ term: { [SERVICE_NAME]: serviceName } },
67+
{ range: rangeFilter(start, end) },
68+
...esFilter,
69+
],
70+
},
71+
},
72+
aggs: {
73+
transaction_groups: {
74+
terms: {
75+
field: TRANSACTION_NAME,
76+
size,
77+
},
78+
aggs: {
79+
transaction_types: {
80+
terms: {
81+
field: TRANSACTION_TYPE,
82+
},
83+
},
84+
timeseries: {
85+
date_histogram: {
86+
field: '@timestamp',
87+
fixed_interval: intervalString,
88+
min_doc_count: 0,
89+
extended_bounds: {
90+
min: start,
91+
max: end,
92+
},
93+
},
94+
aggs: {
95+
avg_latency: {
96+
avg: {
97+
field: getTransactionDurationFieldForAggregatedTransactions(
98+
searchAggregatedTransactions
99+
),
100+
},
101+
},
102+
transaction_count: {
103+
value_count: {
104+
field: getTransactionDurationFieldForAggregatedTransactions(
105+
searchAggregatedTransactions
106+
),
107+
},
108+
},
109+
[EVENT_OUTCOME]: {
110+
filter: {
111+
term: {
112+
[EVENT_OUTCOME]: EventOutcome.failure,
113+
},
114+
},
115+
aggs: {
116+
transaction_count: {
117+
value_count: {
118+
field: getTransactionDurationFieldForAggregatedTransactions(
119+
searchAggregatedTransactions
120+
),
121+
},
122+
},
123+
},
124+
},
125+
},
126+
},
127+
},
128+
},
129+
},
130+
},
131+
});
132+
133+
return timeseriesResponse.aggregations?.transaction_groups.buckets ?? [];
134+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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+
import { orderBy } from 'lodash';
7+
import { ValuesType } from 'utility-types';
8+
import { PromiseReturnType } from '../../../../../observability/typings/common';
9+
import { EventOutcome } from '../../../../common/event_outcome';
10+
import { ESFilter } from '../../../../../../typings/elasticsearch';
11+
import { rangeFilter } from '../../../../common/utils/range_filter';
12+
import {
13+
EVENT_OUTCOME,
14+
SERVICE_NAME,
15+
TRANSACTION_NAME,
16+
} from '../../../../common/elasticsearch_fieldnames';
17+
import {
18+
getProcessorEventForAggregatedTransactions,
19+
getTransactionDurationFieldForAggregatedTransactions,
20+
} from '../../helpers/aggregated_transactions';
21+
import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client';
22+
import { ServiceOverviewTransactionGroupSortField } from '.';
23+
24+
export type TransactionGroupWithoutTimeseriesData = ValuesType<
25+
PromiseReturnType<typeof getTransactionGroupsForPage>['transactionGroups']
26+
>;
27+
28+
export async function getTransactionGroupsForPage({
29+
apmEventClient,
30+
searchAggregatedTransactions,
31+
serviceName,
32+
start,
33+
end,
34+
esFilter,
35+
sortField,
36+
sortDirection,
37+
pageIndex,
38+
size,
39+
}: {
40+
apmEventClient: APMEventClient;
41+
searchAggregatedTransactions: boolean;
42+
serviceName: string;
43+
start: number;
44+
end: number;
45+
esFilter: ESFilter[];
46+
sortField: ServiceOverviewTransactionGroupSortField;
47+
sortDirection: 'asc' | 'desc';
48+
pageIndex: number;
49+
size: number;
50+
}) {
51+
const response = await apmEventClient.search({
52+
apm: {
53+
events: [
54+
getProcessorEventForAggregatedTransactions(
55+
searchAggregatedTransactions
56+
),
57+
],
58+
},
59+
body: {
60+
size: 0,
61+
query: {
62+
bool: {
63+
filter: [
64+
{ term: { [SERVICE_NAME]: serviceName } },
65+
{ range: rangeFilter(start, end) },
66+
...esFilter,
67+
],
68+
},
69+
},
70+
aggs: {
71+
transaction_groups: {
72+
terms: {
73+
field: TRANSACTION_NAME,
74+
size: 500,
75+
order: {
76+
_count: 'desc',
77+
},
78+
},
79+
aggs: {
80+
avg_latency: {
81+
avg: {
82+
field: getTransactionDurationFieldForAggregatedTransactions(
83+
searchAggregatedTransactions
84+
),
85+
},
86+
},
87+
transaction_count: {
88+
value_count: {
89+
field: getTransactionDurationFieldForAggregatedTransactions(
90+
searchAggregatedTransactions
91+
),
92+
},
93+
},
94+
[EVENT_OUTCOME]: {
95+
filter: {
96+
term: {
97+
[EVENT_OUTCOME]: EventOutcome.failure,
98+
},
99+
},
100+
aggs: {
101+
transaction_count: {
102+
value_count: {
103+
field: getTransactionDurationFieldForAggregatedTransactions(
104+
searchAggregatedTransactions
105+
),
106+
},
107+
},
108+
},
109+
},
110+
},
111+
},
112+
},
113+
},
114+
});
115+
116+
const transactionGroups =
117+
response.aggregations?.transaction_groups.buckets.map((bucket) => {
118+
const errorRate =
119+
bucket.transaction_count.value > 0
120+
? (bucket[EVENT_OUTCOME].transaction_count.value ?? 0) /
121+
bucket.transaction_count.value
122+
: null;
123+
124+
return {
125+
name: bucket.key as string,
126+
latency: bucket.avg_latency.value,
127+
traffic: bucket.transaction_count.value,
128+
errorRate,
129+
};
130+
}) ?? [];
131+
132+
const totalDurationValues = transactionGroups.map(
133+
(group) => (group.latency ?? 0) * group.traffic
134+
);
135+
136+
const minTotalDuration = Math.min(...totalDurationValues);
137+
const maxTotalDuration = Math.max(...totalDurationValues);
138+
139+
const transactionGroupsWithImpact = transactionGroups.map((group) => ({
140+
...group,
141+
impact:
142+
(((group.latency ?? 0) * group.traffic - minTotalDuration) /
143+
(maxTotalDuration - minTotalDuration)) *
144+
100,
145+
}));
146+
147+
// Sort transaction groups first, and only get timeseries for data in view.
148+
// This is to limit the possibility of creating too many buckets.
149+
150+
const sortedAndSlicedTransactionGroups = orderBy(
151+
transactionGroupsWithImpact,
152+
sortField,
153+
[sortDirection]
154+
).slice(pageIndex * size, pageIndex * size + size);
155+
156+
return {
157+
transactionGroups: sortedAndSlicedTransactionGroups,
158+
totalTransactionGroups: transactionGroups.length,
159+
isAggregationAccurate:
160+
(response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) ===
161+
0,
162+
};
163+
}

0 commit comments

Comments
 (0)