mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[APM] Remove confusing transaction group abstraction (#41886)
This commit is contained in:
parent
25b17d4960
commit
9885200c3e
12 changed files with 260 additions and 146 deletions
|
@ -5,11 +5,11 @@
|
|||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { TransactionListAPIResponse } from '../../server/lib/transactions/get_top_transactions';
|
||||
import { loadTransactionList } from '../services/rest/apm/transaction_groups';
|
||||
import { IUrlParams } from '../context/UrlParamsContext/types';
|
||||
import { useUiFilters } from '../context/UrlParamsContext';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import { TransactionGroupListAPIResponse } from '../../server/lib/transaction_groups';
|
||||
|
||||
const getRelativeImpact = (
|
||||
impact: number,
|
||||
|
@ -21,7 +21,7 @@ const getRelativeImpact = (
|
|||
1
|
||||
);
|
||||
|
||||
function getWithRelativeImpact(items: TransactionListAPIResponse) {
|
||||
function getWithRelativeImpact(items: TransactionGroupListAPIResponse) {
|
||||
const impacts = items
|
||||
.map(({ impact }) => impact)
|
||||
.filter(impact => impact !== null) as number[];
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { TraceListAPIResponse } from '../../../../server/lib/traces/get_top_traces';
|
||||
import { TraceAPIResponse } from '../../../../server/lib/traces/get_trace';
|
||||
import { callApi } from '../callApi';
|
||||
import { getUiFiltersES } from '../../ui_filters/get_ui_filters_es';
|
||||
import { UIFilters } from '../../../../typings/ui-filters';
|
||||
import { TransactionGroupListAPIResponse } from '../../../../server/lib/transaction_groups';
|
||||
|
||||
export async function loadTrace({
|
||||
traceId,
|
||||
|
@ -37,7 +37,7 @@ export async function loadTraceList({
|
|||
end: string;
|
||||
uiFilters: UIFilters;
|
||||
}) {
|
||||
return callApi<TraceListAPIResponse>({
|
||||
return callApi<TransactionGroupListAPIResponse>({
|
||||
pathname: '/api/apm/traces',
|
||||
query: {
|
||||
start,
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
import { TransactionBreakdownAPIResponse } from '../../../../server/lib/transactions/breakdown';
|
||||
import { TimeSeriesAPIResponse } from '../../../../server/lib/transactions/charts';
|
||||
import { ITransactionDistributionAPIResponse } from '../../../../server/lib/transactions/distribution';
|
||||
import { TransactionListAPIResponse } from '../../../../server/lib/transactions/get_top_transactions';
|
||||
import { callApi } from '../callApi';
|
||||
import { getUiFiltersES } from '../../ui_filters/get_ui_filters_es';
|
||||
import { UIFilters } from '../../../../typings/ui-filters';
|
||||
import { TransactionGroupListAPIResponse } from '../../../../server/lib/transaction_groups';
|
||||
|
||||
export async function loadTransactionList({
|
||||
serviceName,
|
||||
|
@ -25,7 +25,7 @@ export async function loadTransactionList({
|
|||
transactionType: string;
|
||||
uiFilters: UIFilters;
|
||||
}) {
|
||||
return await callApi<TransactionListAPIResponse>({
|
||||
return await callApi<TransactionGroupListAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}/transaction_groups`,
|
||||
query: {
|
||||
start,
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
PARENT_ID,
|
||||
PROCESSOR_EVENT,
|
||||
TRANSACTION_SAMPLED
|
||||
} from '../../../common/elasticsearch_fieldnames';
|
||||
import { PromiseReturnType } from '../../../typings/common';
|
||||
import { rangeFilter } from '../helpers/range_filter';
|
||||
import { Setup } from '../helpers/setup_request';
|
||||
import { getTransactionGroups } from '../transaction_groups';
|
||||
|
||||
export type TraceListAPIResponse = PromiseReturnType<typeof getTopTraces>;
|
||||
export async function getTopTraces(setup: Setup) {
|
||||
const { start, end, uiFiltersES } = setup;
|
||||
|
||||
const bodyQuery = {
|
||||
bool: {
|
||||
// no parent ID means this transaction is a "root" transaction, i.e. a trace
|
||||
must_not: { exists: { field: PARENT_ID } },
|
||||
filter: [
|
||||
{ range: rangeFilter(start, end) },
|
||||
{ term: { [PROCESSOR_EVENT]: 'transaction' } },
|
||||
...uiFiltersES
|
||||
],
|
||||
should: [{ term: { [TRANSACTION_SAMPLED]: true } }]
|
||||
}
|
||||
};
|
||||
|
||||
return getTransactionGroups(setup, bodyQuery);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`transactionGroupsFetcher should call client with correct query 1`] = `
|
||||
exports[`transactionGroupsFetcher type: top_traces should call client.search with correct query 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
|
@ -52,7 +52,145 @@ Array [
|
|||
},
|
||||
},
|
||||
"query": Object {
|
||||
"my": "bodyQuery",
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
"format": "epoch_millis",
|
||||
"gte": 1528113600000,
|
||||
"lte": 1528977600000,
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"processor.event": "transaction",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"service.environment": "test",
|
||||
},
|
||||
},
|
||||
],
|
||||
"must_not": Array [
|
||||
Object {
|
||||
"exists": Object {
|
||||
"field": "parent.id",
|
||||
},
|
||||
},
|
||||
],
|
||||
"should": Array [
|
||||
Object {
|
||||
"term": Object {
|
||||
"transaction.sampled": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"size": 0,
|
||||
},
|
||||
"index": "myIndex",
|
||||
},
|
||||
],
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`transactionGroupsFetcher type: top_transactions should call client.search with correct query 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"body": Object {
|
||||
"aggs": Object {
|
||||
"transactions": Object {
|
||||
"aggs": Object {
|
||||
"avg": Object {
|
||||
"avg": Object {
|
||||
"field": "transaction.duration.us",
|
||||
},
|
||||
},
|
||||
"p95": Object {
|
||||
"percentiles": Object {
|
||||
"field": "transaction.duration.us",
|
||||
"percents": Array [
|
||||
95,
|
||||
],
|
||||
},
|
||||
},
|
||||
"sample": Object {
|
||||
"top_hits": Object {
|
||||
"size": 1,
|
||||
"sort": Array [
|
||||
Object {
|
||||
"_score": "desc",
|
||||
},
|
||||
Object {
|
||||
"@timestamp": Object {
|
||||
"order": "desc",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"sum": Object {
|
||||
"sum": Object {
|
||||
"field": "transaction.duration.us",
|
||||
},
|
||||
},
|
||||
},
|
||||
"terms": Object {
|
||||
"field": "transaction.name",
|
||||
"order": Object {
|
||||
"sum": "desc",
|
||||
},
|
||||
"size": 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
"format": "epoch_millis",
|
||||
"gte": 1528113600000,
|
||||
"lte": 1528977600000,
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"processor.event": "transaction",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"service.environment": "test",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"service.name": "opbeans-node",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"transaction.type": "request",
|
||||
},
|
||||
},
|
||||
],
|
||||
"must_not": Array [],
|
||||
"should": Array [
|
||||
Object {
|
||||
"term": Object {
|
||||
"transaction.sampled": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"size": 0,
|
||||
},
|
||||
|
|
|
@ -4,42 +4,51 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ESResponse, transactionGroupsFetcher } from './fetcher';
|
||||
import { transactionGroupsFetcher } from './fetcher';
|
||||
|
||||
function getSetup() {
|
||||
return {
|
||||
start: 1528113600000,
|
||||
end: 1528977600000,
|
||||
client: {
|
||||
search: jest.fn()
|
||||
} as any,
|
||||
config: {
|
||||
get: jest.fn<any, string[]>((key: string) => {
|
||||
switch (key) {
|
||||
case 'apm_oss.transactionIndices':
|
||||
return 'myIndex';
|
||||
case 'xpack.apm.ui.transactionGroupBucketSize':
|
||||
return 100;
|
||||
}
|
||||
}),
|
||||
has: () => true
|
||||
},
|
||||
uiFiltersES: [{ term: { 'service.environment': 'test' } }]
|
||||
};
|
||||
}
|
||||
|
||||
describe('transactionGroupsFetcher', () => {
|
||||
let res: ESResponse;
|
||||
let clientSpy: jest.Mock;
|
||||
beforeEach(async () => {
|
||||
clientSpy = jest.fn().mockResolvedValue('ES response');
|
||||
|
||||
const setup = {
|
||||
start: 1528113600000,
|
||||
end: 1528977600000,
|
||||
client: {
|
||||
search: clientSpy
|
||||
} as any,
|
||||
config: {
|
||||
get: jest.fn<any, string[]>((key: string) => {
|
||||
switch (key) {
|
||||
case 'apm_oss.transactionIndices':
|
||||
return 'myIndex';
|
||||
case 'xpack.apm.ui.transactionGroupBucketSize':
|
||||
return 100;
|
||||
}
|
||||
}),
|
||||
has: () => true
|
||||
},
|
||||
uiFiltersES: [{ term: { 'service.environment': 'test' } }]
|
||||
};
|
||||
const bodyQuery = { my: 'bodyQuery' };
|
||||
res = await transactionGroupsFetcher(setup, bodyQuery);
|
||||
describe('type: top_traces', () => {
|
||||
it('should call client.search with correct query', async () => {
|
||||
const setup = getSetup();
|
||||
await transactionGroupsFetcher({ type: 'top_traces' }, setup);
|
||||
expect(setup.client.search.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call client with correct query', () => {
|
||||
expect(clientSpy.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return correct response', () => {
|
||||
expect(res).toBe('ES response');
|
||||
describe('type: top_transactions', () => {
|
||||
it('should call client.search with correct query', async () => {
|
||||
const setup = getSetup();
|
||||
await transactionGroupsFetcher(
|
||||
{
|
||||
type: 'top_transactions',
|
||||
serviceName: 'opbeans-node',
|
||||
transactionType: 'request'
|
||||
},
|
||||
setup
|
||||
);
|
||||
expect(setup.client.search.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,19 +6,60 @@
|
|||
|
||||
import {
|
||||
TRANSACTION_DURATION,
|
||||
TRANSACTION_NAME
|
||||
TRANSACTION_NAME,
|
||||
PROCESSOR_EVENT,
|
||||
PARENT_ID,
|
||||
TRANSACTION_SAMPLED,
|
||||
SERVICE_NAME,
|
||||
TRANSACTION_TYPE
|
||||
} from '../../../common/elasticsearch_fieldnames';
|
||||
import { PromiseReturnType, StringMap } from '../../../typings/common';
|
||||
import { PromiseReturnType } from '../../../typings/common';
|
||||
import { Setup } from '../helpers/setup_request';
|
||||
import { rangeFilter } from '../helpers/range_filter';
|
||||
import { BoolQuery } from '../../../typings/elasticsearch';
|
||||
|
||||
interface TopTransactionOptions {
|
||||
type: 'top_transactions';
|
||||
serviceName: string;
|
||||
transactionType: string;
|
||||
}
|
||||
|
||||
interface TopTraceOptions {
|
||||
type: 'top_traces';
|
||||
}
|
||||
|
||||
export type Options = TopTransactionOptions | TopTraceOptions;
|
||||
|
||||
export type ESResponse = PromiseReturnType<typeof transactionGroupsFetcher>;
|
||||
export function transactionGroupsFetcher(setup: Setup, bodyQuery: StringMap) {
|
||||
const { client, config } = setup;
|
||||
export function transactionGroupsFetcher(options: Options, setup: Setup) {
|
||||
const { client, config, start, end, uiFiltersES } = setup;
|
||||
|
||||
const bool: BoolQuery = {
|
||||
must_not: [],
|
||||
// prefer sampled transactions
|
||||
should: [{ term: { [TRANSACTION_SAMPLED]: true } }],
|
||||
filter: [
|
||||
{ range: rangeFilter(start, end) },
|
||||
{ term: { [PROCESSOR_EVENT]: 'transaction' } },
|
||||
...uiFiltersES
|
||||
]
|
||||
};
|
||||
|
||||
if (options.type === 'top_traces') {
|
||||
// A transaction without `parent.id` is considered a "root" transaction, i.e. a trace
|
||||
bool.must_not.push({ exists: { field: PARENT_ID } });
|
||||
} else {
|
||||
bool.filter.push({ term: { [SERVICE_NAME]: options.serviceName } });
|
||||
bool.filter.push({ term: { [TRANSACTION_TYPE]: options.transactionType } });
|
||||
}
|
||||
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.transactionIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
query: bodyQuery,
|
||||
query: {
|
||||
bool
|
||||
},
|
||||
aggs: {
|
||||
transactions: {
|
||||
terms: {
|
||||
|
|
|
@ -4,14 +4,17 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { StringMap } from '../../../typings/common';
|
||||
import { Setup } from '../helpers/setup_request';
|
||||
import { transactionGroupsFetcher } from './fetcher';
|
||||
import { transactionGroupsFetcher, Options } from './fetcher';
|
||||
import { transactionGroupsTransformer } from './transform';
|
||||
import { PromiseReturnType } from '../../../typings/common';
|
||||
|
||||
export async function getTransactionGroups(setup: Setup, bodyQuery: StringMap) {
|
||||
export type TransactionGroupListAPIResponse = PromiseReturnType<
|
||||
typeof getTransactionGroupList
|
||||
>;
|
||||
export async function getTransactionGroupList(options: Options, setup: Setup) {
|
||||
const { start, end } = setup;
|
||||
const response = await transactionGroupsFetcher(setup, bodyQuery);
|
||||
const response = await transactionGroupsFetcher(options, setup);
|
||||
return transactionGroupsTransformer({
|
||||
response,
|
||||
start,
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
PROCESSOR_EVENT,
|
||||
SERVICE_NAME,
|
||||
TRANSACTION_TYPE
|
||||
} from '../../../../common/elasticsearch_fieldnames';
|
||||
import { PromiseReturnType } from '../../../../typings/common';
|
||||
import { rangeFilter } from '../../helpers/range_filter';
|
||||
import { Setup } from '../../helpers/setup_request';
|
||||
import { getTransactionGroups } from '../../transaction_groups';
|
||||
|
||||
export interface IOptions {
|
||||
setup: Setup;
|
||||
transactionType?: string;
|
||||
serviceName: string;
|
||||
}
|
||||
|
||||
export type TransactionListAPIResponse = PromiseReturnType<
|
||||
typeof getTopTransactions
|
||||
>;
|
||||
export async function getTopTransactions({
|
||||
setup,
|
||||
transactionType,
|
||||
serviceName
|
||||
}: IOptions) {
|
||||
const { start, end, uiFiltersES } = setup;
|
||||
|
||||
const bodyQuery = {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [PROCESSOR_EVENT]: 'transaction' } },
|
||||
{ range: rangeFilter(start, end) },
|
||||
...uiFiltersES
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if (transactionType) {
|
||||
bodyQuery.bool.filter.push({
|
||||
term: { [TRANSACTION_TYPE]: transactionType }
|
||||
});
|
||||
}
|
||||
|
||||
return getTransactionGroups(setup, bodyQuery);
|
||||
}
|
|
@ -5,12 +5,11 @@
|
|||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
|
||||
import { InternalCoreSetup } from 'src/core/server';
|
||||
import { withDefaultValidators } from '../lib/helpers/input_validation';
|
||||
import { setupRequest } from '../lib/helpers/setup_request';
|
||||
import { getTopTraces } from '../lib/traces/get_top_traces';
|
||||
import { getTrace } from '../lib/traces/get_trace';
|
||||
import { getTransactionGroupList } from '../lib/transaction_groups';
|
||||
|
||||
const ROOT = '/api/apm/traces';
|
||||
const defaultErrorHandler = (err: Error) => {
|
||||
|
@ -34,8 +33,9 @@ export function initTracesApi(core: InternalCoreSetup) {
|
|||
},
|
||||
handler: req => {
|
||||
const setup = setupRequest(req);
|
||||
|
||||
return getTopTraces(setup).catch(defaultErrorHandler);
|
||||
return getTransactionGroupList({ type: 'top_traces' }, setup).catch(
|
||||
defaultErrorHandler
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ import { withDefaultValidators } from '../lib/helpers/input_validation';
|
|||
import { setupRequest } from '../lib/helpers/setup_request';
|
||||
import { getTransactionCharts } from '../lib/transactions/charts';
|
||||
import { getTransactionDistribution } from '../lib/transactions/distribution';
|
||||
import { getTopTransactions } from '../lib/transactions/get_top_transactions';
|
||||
import { getTransactionBreakdown } from '../lib/transactions/breakdown';
|
||||
import { getTransactionGroupList } from '../lib/transaction_groups';
|
||||
|
||||
const defaultErrorHandler = (err: Error) => {
|
||||
// eslint-disable-next-line
|
||||
|
@ -36,14 +36,17 @@ export function initTransactionGroupsApi(core: InternalCoreSetup) {
|
|||
},
|
||||
handler: req => {
|
||||
const { serviceName } = req.params;
|
||||
const { transactionType } = req.query as { transactionType?: string };
|
||||
const { transactionType } = req.query as { transactionType: string };
|
||||
const setup = setupRequest(req);
|
||||
|
||||
return getTopTransactions({
|
||||
serviceName,
|
||||
transactionType,
|
||||
return getTransactionGroupList(
|
||||
{
|
||||
type: 'top_transactions',
|
||||
serviceName,
|
||||
transactionType
|
||||
},
|
||||
setup
|
||||
}).catch(defaultErrorHandler);
|
||||
).catch(defaultErrorHandler);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -6,6 +6,12 @@
|
|||
|
||||
import { StringMap, IndexAsString } from './common';
|
||||
|
||||
export interface BoolQuery {
|
||||
must_not: Array<Record<string, any>>;
|
||||
should: Array<Record<string, any>>;
|
||||
filter: Array<Record<string, any>>;
|
||||
}
|
||||
|
||||
declare module 'elasticsearch' {
|
||||
// extending SearchResponse to be able to have typed aggregations
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue