Skip to content

Commit

Permalink
Reorganize getServiceTransactionGroups
Browse files Browse the repository at this point in the history
  • Loading branch information
dgieselaar committed Nov 19, 2020
1 parent df606c5 commit 7d05875
Show file tree
Hide file tree
Showing 7 changed files with 462 additions and 304 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ import { ServiceOverviewTable } from '../service_overview_table';
type ServiceTransactionGroupItem = ValuesType<
APIReturnType<
'GET /api/apm/services/{serviceName}/overview_transaction_groups'
>['transaction_groups']
>['transactionGroups']
>;

interface Props {
serviceName: string;
}

type SortField = 'latency' | 'traffic' | 'error_rate' | 'impact';
type SortField = 'latency' | 'traffic' | 'errorRate' | 'impact';
type SortDirection = 'asc' | 'desc';

const PAGE_SIZE = 5;
Expand Down Expand Up @@ -118,8 +118,8 @@ export function ServiceOverviewTransactionsTable(props: Props) {
},
}).then((response) => {
return {
items: response.transaction_groups,
totalItemCount: response.total_transaction_groups,
items: response.transactionGroups,
totalItemCount: response.totalTransactionGroups,
tableOptions: {
pageIndex: tableOptions.pageIndex,
sort: {
Expand Down Expand Up @@ -154,7 +154,7 @@ export function ServiceOverviewTransactionsTable(props: Props) {
defaultMessage: 'Name',
}
),
render: (_, { name, transaction_type: transactionType }) => {
render: (_, { name, transactionType }) => {
return (
<TransactionGroupLinkWrapper>
<EuiToolTip delay="long" content={name}>
Expand Down Expand Up @@ -223,7 +223,7 @@ export function ServiceOverviewTransactionsTable(props: Props) {
}
),
width: px(unit * 8),
render: (_, { error_rate: errorRate }) => {
render: (_, { errorRate }) => {
return (
<SparkPlotWithValueLabel
color="euiColorVis7"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { PromiseReturnType } from '../../../../../observability/typings/common';
import { EventOutcome } from '../../../../common/event_outcome';
import { rangeFilter } from '../../../../common/utils/range_filter';
import {
EVENT_OUTCOME,
SERVICE_NAME,
TRANSACTION_NAME,
TRANSACTION_TYPE,
} from '../../../../common/elasticsearch_fieldnames';

import { ESFilter } from '../../../../../../typings/elasticsearch';
import {
getProcessorEventForAggregatedTransactions,
getTransactionDurationFieldForAggregatedTransactions,
} from '../../helpers/aggregated_transactions';
import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client';
import { getBucketSize } from '../../helpers/get_bucket_size';

export type TransactionGroupTimeseriesData = PromiseReturnType<
typeof getTimeseriesDataForTransactionGroups
>;

export async function getTimeseriesDataForTransactionGroups({
apmEventClient,
start,
end,
serviceName,
transactionNames,
esFilter,
searchAggregatedTransactions,
size,
numBuckets,
}: {
apmEventClient: APMEventClient;
start: number;
end: number;
serviceName: string;
transactionNames: string[];
esFilter: ESFilter[];
searchAggregatedTransactions: boolean;
size: number;
numBuckets: number;
}) {
const { intervalString } = getBucketSize(start, end, numBuckets);

const timeseriesResponse = await apmEventClient.search({
apm: {
events: [
getProcessorEventForAggregatedTransactions(
searchAggregatedTransactions
),
],
},
body: {
size: 0,
query: {
bool: {
filter: [
{ terms: { [TRANSACTION_NAME]: transactionNames } },
{ term: { [SERVICE_NAME]: serviceName } },
{ range: rangeFilter(start, end) },
...esFilter,
],
},
},
aggs: {
transaction_groups: {
terms: {
field: TRANSACTION_NAME,
size,
},
aggs: {
transaction_types: {
terms: {
field: TRANSACTION_TYPE,
},
},
timeseries: {
date_histogram: {
field: '@timestamp',
fixed_interval: intervalString,
min_doc_count: 0,
extended_bounds: {
min: start,
max: end,
},
},
aggs: {
avg_latency: {
avg: {
field: getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
),
},
},
transaction_count: {
value_count: {
field: getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
),
},
},
[EVENT_OUTCOME]: {
filter: {
term: {
[EVENT_OUTCOME]: EventOutcome.failure,
},
},
aggs: {
transaction_count: {
value_count: {
field: getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
),
},
},
},
},
},
},
},
},
},
},
});

return timeseriesResponse.aggregations?.transaction_groups.buckets ?? [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { orderBy } from 'lodash';
import { ValuesType } from 'utility-types';
import { PromiseReturnType } from '../../../../../observability/typings/common';
import { EventOutcome } from '../../../../common/event_outcome';
import { ESFilter } from '../../../../../../typings/elasticsearch';
import { rangeFilter } from '../../../../common/utils/range_filter';
import {
EVENT_OUTCOME,
SERVICE_NAME,
TRANSACTION_NAME,
} from '../../../../common/elasticsearch_fieldnames';
import {
getProcessorEventForAggregatedTransactions,
getTransactionDurationFieldForAggregatedTransactions,
} from '../../helpers/aggregated_transactions';
import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client';
import { ServiceOverviewTransactionGroupSortField } from '.';

export type TransactionGroupWithoutTimeseriesData = ValuesType<
PromiseReturnType<typeof getTransactionGroupsForPage>['transactionGroups']
>;

export async function getTransactionGroupsForPage({
apmEventClient,
searchAggregatedTransactions,
serviceName,
start,
end,
esFilter,
sortField,
sortDirection,
pageIndex,
size,
}: {
apmEventClient: APMEventClient;
searchAggregatedTransactions: boolean;
serviceName: string;
start: number;
end: number;
esFilter: ESFilter[];
sortField: ServiceOverviewTransactionGroupSortField;
sortDirection: 'asc' | 'desc';
pageIndex: number;
size: number;
}) {
const response = await apmEventClient.search({
apm: {
events: [
getProcessorEventForAggregatedTransactions(
searchAggregatedTransactions
),
],
},
body: {
size: 0,
query: {
bool: {
filter: [
{ term: { [SERVICE_NAME]: serviceName } },
{ range: rangeFilter(start, end) },
...esFilter,
],
},
},
aggs: {
transaction_groups: {
terms: {
field: TRANSACTION_NAME,
size: 500,
order: {
_count: 'desc',
},
},
aggs: {
avg_latency: {
avg: {
field: getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
),
},
},
transaction_count: {
value_count: {
field: getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
),
},
},
[EVENT_OUTCOME]: {
filter: {
term: {
[EVENT_OUTCOME]: EventOutcome.failure,
},
},
aggs: {
transaction_count: {
value_count: {
field: getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
),
},
},
},
},
},
},
},
},
});

const transactionGroups =
response.aggregations?.transaction_groups.buckets.map((bucket) => {
const errorRate =
bucket.transaction_count.value > 0
? (bucket[EVENT_OUTCOME].transaction_count.value ?? 0) /
bucket.transaction_count.value
: null;

return {
name: bucket.key as string,
latency: bucket.avg_latency.value,
traffic: bucket.transaction_count.value,
errorRate,
};
}) ?? [];

const totalDurationValues = transactionGroups.map(
(group) => (group.latency ?? 0) * group.traffic
);

const minTotalDuration = Math.min(...totalDurationValues);
const maxTotalDuration = Math.max(...totalDurationValues);

const transactionGroupsWithImpact = transactionGroups.map((group) => ({
...group,
impact:
(((group.latency ?? 0) * group.traffic - minTotalDuration) /
(maxTotalDuration - minTotalDuration)) *
100,
}));

// Sort transaction groups first, and only get timeseries for data in view.
// This is to limit the possibility of creating too many buckets.

const sortedAndSlicedTransactionGroups = orderBy(
transactionGroupsWithImpact,
sortField,
[sortDirection]
).slice(pageIndex * size, pageIndex * size + size);

return {
transactionGroups: sortedAndSlicedTransactionGroups,
totalTransactionGroups: transactionGroups.length,
isAggregationAccurate:
(response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) ===
0,
};
}
Loading

0 comments on commit 7d05875

Please sign in to comment.