[6.x] [APM] Split API into transactions and transaction_groups (#26349) | [APM] Sort by score to get transaction samples with sampled:true (#26389) | [APM] Move impact calculation to Elasticsearch (#26436) (#26581)
* [APM] Split API into transactions and transaction_groups (#26349) * [APM] Sort by score to get transaction samples with sampled:true (#26389) * [APM] Move impact calculation to Elasticsearch (#26436) * [APM] Move impact calculation to Elasticsearch * Renamed “durationSum” to “sum” and went back to single, mutable impact
This commit is contained in:
parent
7ca581c4a9
commit
43183614b0
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { resolve } from 'path';
|
||||
import { initTransactionsApi } from './server/routes/transactions';
|
||||
import { initTransactionGroupsApi } from './server/routes/transaction_groups';
|
||||
import { initServicesApi } from './server/routes/services';
|
||||
import { initErrorsApi } from './server/routes/errors';
|
||||
import { initStatusApi } from './server/routes/status_check';
|
||||
|
@ -64,6 +65,7 @@ export function apm(kibana) {
|
|||
|
||||
init(server) {
|
||||
initTransactionsApi(server);
|
||||
initTransactionGroupsApi(server);
|
||||
initTracesApi(server);
|
||||
initServicesApi(server);
|
||||
initErrorsApi(server);
|
||||
|
|
|
@ -8,9 +8,8 @@ import React from 'react';
|
|||
import { shallow } from 'enzyme';
|
||||
import { ServiceOverview } from '../view';
|
||||
import { STATUS } from '../../../../constants';
|
||||
import * as apmRestServices from '../../../../services/rest/apm';
|
||||
|
||||
jest.mock('../../../../services/rest/apm');
|
||||
import * as apmRestServices from '../../../../services/rest/apm/status_check';
|
||||
jest.mock('../../../../services/rest/apm/status_check');
|
||||
|
||||
describe('Service Overview -> View', () => {
|
||||
let mockAgentStatus;
|
||||
|
@ -22,7 +21,6 @@ describe('Service Overview -> View', () => {
|
|||
dataFound: true
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/namespace
|
||||
apmRestServices.loadAgentStatus = jest.fn(() =>
|
||||
Promise.resolve(mockAgentStatus)
|
||||
);
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { Component } from 'react';
|
|||
import { RRRRenderResponse } from 'react-redux-request';
|
||||
import { IUrlParams } from 'x-pack/plugins/apm/public/store/urlParams';
|
||||
import { IServiceListItem } from 'x-pack/plugins/apm/server/lib/services/get_services';
|
||||
import { loadAgentStatus } from '../../../services/rest/apm';
|
||||
import { loadAgentStatus } from '../../../services/rest/apm/status_check';
|
||||
import { ServiceListRequest } from '../../../store/reactReduxRequest/serviceList';
|
||||
import { EmptyMessage } from '../../shared/EmptyMessage';
|
||||
import { SetupInstructionsLink } from '../../shared/SetupInstructionsLink';
|
||||
|
|
|
@ -1,314 +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.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { camelizeKeys } from 'humps';
|
||||
import { ServiceAPIResponse } from 'x-pack/plugins/apm/server/lib/services/get_service';
|
||||
import { ServiceListAPIResponse } from 'x-pack/plugins/apm/server/lib/services/get_services';
|
||||
import { TraceListAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_top_traces';
|
||||
import { TraceAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_trace';
|
||||
import { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts';
|
||||
import { ITransactionDistributionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/distribution';
|
||||
import { TransactionListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_top_transactions';
|
||||
import { TransactionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_transaction';
|
||||
import { SpanListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/spans/get_spans';
|
||||
import { Span } from 'x-pack/plugins/apm/typings/Span';
|
||||
import { Transaction } from 'x-pack/plugins/apm/typings/Transaction';
|
||||
import { IUrlParams } from '../../store/urlParams';
|
||||
// @ts-ignore
|
||||
import { convertKueryToEsQuery } from '../kuery';
|
||||
import { callApi } from './callApi';
|
||||
// @ts-ignore
|
||||
import { getAPMIndexPattern } from './savedObjects';
|
||||
|
||||
export async function loadLicense() {
|
||||
return callApi({
|
||||
pathname: `/api/xpack/v1/info`
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadServerStatus() {
|
||||
return callApi({
|
||||
pathname: `/api/apm/status/server`
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadAgentStatus() {
|
||||
return callApi<{ dataFound: boolean }>({
|
||||
pathname: `/api/apm/status/agent`
|
||||
});
|
||||
}
|
||||
|
||||
export async function getEncodedEsQuery(kuery?: string) {
|
||||
if (!kuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexPattern = await getAPMIndexPattern();
|
||||
|
||||
if (!indexPattern) {
|
||||
return;
|
||||
}
|
||||
|
||||
const esFilterQuery = convertKueryToEsQuery(kuery, indexPattern);
|
||||
return encodeURIComponent(JSON.stringify(esFilterQuery));
|
||||
}
|
||||
|
||||
export async function loadServiceList({ start, end, kuery }: IUrlParams) {
|
||||
return callApi<ServiceListAPIResponse>({
|
||||
pathname: `/api/apm/services`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadServiceDetails({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery
|
||||
}: IUrlParams) {
|
||||
return callApi<ServiceAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadTraceList({ start, end, kuery }: IUrlParams) {
|
||||
const groups = await callApi<TraceListAPIResponse>({
|
||||
pathname: '/api/apm/traces',
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
|
||||
return groups.map(group => {
|
||||
group.sample = addVersion(group.sample);
|
||||
return group;
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadTransactionList({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
transactionType
|
||||
}: IUrlParams) {
|
||||
const groups = await callApi<TransactionListAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}/transactions`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery),
|
||||
transaction_type: transactionType
|
||||
}
|
||||
});
|
||||
|
||||
return groups.map(group => {
|
||||
group.sample = addVersion(group.sample);
|
||||
return group;
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadTransactionDistribution({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
transactionName,
|
||||
transactionId,
|
||||
kuery
|
||||
}: IUrlParams) {
|
||||
return callApi<ITransactionDistributionAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}/transactions/distribution`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
transaction_name: transactionName,
|
||||
transaction_id: transactionId,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addVersion<T extends Span | Transaction | null | undefined>(
|
||||
item: T
|
||||
): T {
|
||||
if (item != null) {
|
||||
item.version = item.hasOwnProperty('trace') ? 'v2' : 'v1';
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function addSpanId(hit: Span, i: number) {
|
||||
if (!hit.span.id) {
|
||||
hit.span.id = i;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
export async function loadSpans({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
transactionId
|
||||
}: IUrlParams) {
|
||||
const hits = await callApi<SpanListAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}/spans`,
|
||||
query: {
|
||||
start,
|
||||
end
|
||||
}
|
||||
});
|
||||
|
||||
return hits.map(addVersion).map(addSpanId);
|
||||
}
|
||||
|
||||
export async function loadTrace({ traceId, start, end }: IUrlParams) {
|
||||
const hits = await callApi<TraceAPIResponse>(
|
||||
{
|
||||
pathname: `/api/apm/traces/${traceId}`,
|
||||
query: {
|
||||
start,
|
||||
end
|
||||
}
|
||||
},
|
||||
{
|
||||
camelcase: false
|
||||
}
|
||||
);
|
||||
|
||||
return hits.map(addVersion);
|
||||
}
|
||||
|
||||
export async function loadTransaction({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
transactionId,
|
||||
traceId,
|
||||
kuery
|
||||
}: IUrlParams) {
|
||||
const result = await callApi<TransactionAPIResponse>(
|
||||
{
|
||||
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`,
|
||||
query: {
|
||||
traceId,
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
},
|
||||
{
|
||||
camelcase: false
|
||||
}
|
||||
);
|
||||
|
||||
return addVersion(result);
|
||||
}
|
||||
|
||||
export async function loadCharts({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
transactionType,
|
||||
transactionName
|
||||
}: IUrlParams) {
|
||||
return callApi<TimeSeriesAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}/transactions/charts`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery),
|
||||
transaction_type: transactionType,
|
||||
transaction_name: transactionName
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface ErrorGroupListParams extends IUrlParams {
|
||||
size: number;
|
||||
sortField: string;
|
||||
sortDirection: string;
|
||||
}
|
||||
|
||||
export async function loadErrorGroupList({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
size,
|
||||
sortField,
|
||||
sortDirection
|
||||
}: ErrorGroupListParams) {
|
||||
return callApi({
|
||||
pathname: `/api/apm/services/${serviceName}/errors`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
size,
|
||||
sortField,
|
||||
sortDirection,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadErrorGroupDetails({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
errorGroupId
|
||||
}: IUrlParams) {
|
||||
// TODO: add types when error section is converted to ts
|
||||
const res = await callApi<any>(
|
||||
{
|
||||
pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
},
|
||||
{
|
||||
camelcase: false
|
||||
}
|
||||
);
|
||||
const camelizedRes: any = camelizeKeys(res);
|
||||
if (res.error.context) {
|
||||
camelizedRes.error.context = res.error.context;
|
||||
}
|
||||
return camelizedRes;
|
||||
}
|
||||
|
||||
export async function loadErrorDistribution({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
errorGroupId
|
||||
}: IUrlParams) {
|
||||
return callApi({
|
||||
pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}/distribution`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
36
x-pack/plugins/apm/public/services/rest/apm/apm.ts
Normal file
36
x-pack/plugins/apm/public/services/rest/apm/apm.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { Span } from 'x-pack/plugins/apm/typings/Span';
|
||||
import { Transaction } from 'x-pack/plugins/apm/typings/Transaction';
|
||||
// @ts-ignore
|
||||
import { convertKueryToEsQuery } from '../../kuery';
|
||||
// @ts-ignore
|
||||
import { getAPMIndexPattern } from '../savedObjects';
|
||||
|
||||
export async function getEncodedEsQuery(kuery?: string) {
|
||||
if (!kuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexPattern = await getAPMIndexPattern();
|
||||
if (!indexPattern) {
|
||||
return;
|
||||
}
|
||||
|
||||
const esFilterQuery = convertKueryToEsQuery(kuery, indexPattern);
|
||||
return encodeURIComponent(JSON.stringify(esFilterQuery));
|
||||
}
|
||||
|
||||
export function addVersion<T extends Span | Transaction | null | undefined>(
|
||||
item: T
|
||||
): T {
|
||||
if (item != null) {
|
||||
item.version = item.hasOwnProperty('trace') ? 'v2' : 'v1';
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
83
x-pack/plugins/apm/public/services/rest/apm/error_groups.ts
Normal file
83
x-pack/plugins/apm/public/services/rest/apm/error_groups.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 { camelizeKeys } from 'humps';
|
||||
import { IUrlParams } from '../../../store/urlParams';
|
||||
import { callApi } from '../callApi';
|
||||
import { getEncodedEsQuery } from './apm';
|
||||
|
||||
interface ErrorGroupListParams extends IUrlParams {
|
||||
size: number;
|
||||
sortField: string;
|
||||
sortDirection: string;
|
||||
}
|
||||
|
||||
export async function loadErrorGroupList({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
size,
|
||||
sortField,
|
||||
sortDirection
|
||||
}: ErrorGroupListParams) {
|
||||
return callApi({
|
||||
pathname: `/api/apm/services/${serviceName}/errors`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
size,
|
||||
sortField,
|
||||
sortDirection,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadErrorGroupDetails({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
errorGroupId
|
||||
}: IUrlParams) {
|
||||
// TODO: add types when error section is converted to ts
|
||||
const res = await callApi<any>(
|
||||
{
|
||||
pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
},
|
||||
{
|
||||
camelcase: false
|
||||
}
|
||||
);
|
||||
const camelizedRes: any = camelizeKeys(res);
|
||||
if (res.error.context) {
|
||||
camelizedRes.error.context = res.error.context;
|
||||
}
|
||||
return camelizedRes;
|
||||
}
|
||||
|
||||
export async function loadErrorDistribution({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
errorGroupId
|
||||
}: IUrlParams) {
|
||||
return callApi({
|
||||
pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}/distribution`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
38
x-pack/plugins/apm/public/services/rest/apm/services.ts
Normal file
38
x-pack/plugins/apm/public/services/rest/apm/services.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { ServiceAPIResponse } from 'x-pack/plugins/apm/server/lib/services/get_service';
|
||||
import { ServiceListAPIResponse } from 'x-pack/plugins/apm/server/lib/services/get_services';
|
||||
import { IUrlParams } from '../../../store/urlParams';
|
||||
import { callApi } from '../callApi';
|
||||
import { getEncodedEsQuery } from './apm';
|
||||
|
||||
export async function loadServiceList({ start, end, kuery }: IUrlParams) {
|
||||
return callApi<ServiceListAPIResponse>({
|
||||
pathname: `/api/apm/services`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadServiceDetails({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery
|
||||
}: IUrlParams) {
|
||||
return callApi<ServiceAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
19
x-pack/plugins/apm/public/services/rest/apm/status_check.ts
Normal file
19
x-pack/plugins/apm/public/services/rest/apm/status_check.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { callApi } from '../callApi';
|
||||
|
||||
export async function loadServerStatus() {
|
||||
return callApi({
|
||||
pathname: `/api/apm/status/server`
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadAgentStatus() {
|
||||
return callApi<{ dataFound: boolean }>({
|
||||
pathname: `/api/apm/status/agent`
|
||||
});
|
||||
}
|
44
x-pack/plugins/apm/public/services/rest/apm/traces.ts
Normal file
44
x-pack/plugins/apm/public/services/rest/apm/traces.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { TraceListAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_top_traces';
|
||||
import { TraceAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_trace';
|
||||
import { IUrlParams } from '../../../store/urlParams';
|
||||
import { callApi } from '../callApi';
|
||||
import { addVersion, getEncodedEsQuery } from './apm';
|
||||
|
||||
export async function loadTrace({ traceId, start, end }: IUrlParams) {
|
||||
const hits = await callApi<TraceAPIResponse>(
|
||||
{
|
||||
pathname: `/api/apm/traces/${traceId}`,
|
||||
query: {
|
||||
start,
|
||||
end
|
||||
}
|
||||
},
|
||||
{
|
||||
camelcase: false
|
||||
}
|
||||
);
|
||||
|
||||
return hits.map(addVersion);
|
||||
}
|
||||
|
||||
export async function loadTraceList({ start, end, kuery }: IUrlParams) {
|
||||
const groups = await callApi<TraceListAPIResponse>({
|
||||
pathname: '/api/apm/traces',
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
|
||||
return groups.map(group => {
|
||||
group.sample = addVersion(group.sample);
|
||||
return group;
|
||||
});
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts';
|
||||
import { ITransactionDistributionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/distribution';
|
||||
import { TransactionListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_top_transactions';
|
||||
import { IUrlParams } from '../../../store/urlParams';
|
||||
import { callApi } from '../callApi';
|
||||
import { addVersion, getEncodedEsQuery } from './apm';
|
||||
|
||||
export async function loadTransactionList({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
transactionType = 'request'
|
||||
}: IUrlParams) {
|
||||
const groups = await callApi<TransactionListAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}/transaction_groups/${transactionType}`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
|
||||
return groups.map(group => {
|
||||
group.sample = addVersion(group.sample);
|
||||
return group;
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadTransactionDistribution({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
transactionName,
|
||||
transactionType = 'request',
|
||||
transactionId,
|
||||
kuery
|
||||
}: Required<IUrlParams>) {
|
||||
return callApi<ITransactionDistributionAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}/transaction_groups/${transactionType}/${encodeURIComponent(
|
||||
transactionName
|
||||
)}/distribution`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
transactionId,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadDetailsCharts({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
transactionType = 'request',
|
||||
transactionName
|
||||
}: Required<IUrlParams>) {
|
||||
return callApi<TimeSeriesAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}/transaction_groups/${transactionType}/${encodeURIComponent(
|
||||
transactionName
|
||||
)}/charts`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadOverviewCharts({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
kuery,
|
||||
transactionType = 'request'
|
||||
}: IUrlParams) {
|
||||
return callApi<TimeSeriesAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}/transaction_groups/${transactionType}/charts`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
});
|
||||
}
|
62
x-pack/plugins/apm/public/services/rest/apm/transactions.ts
Normal file
62
x-pack/plugins/apm/public/services/rest/apm/transactions.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { TransactionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_transaction';
|
||||
import { SpanListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/spans/get_spans';
|
||||
import { Span } from 'x-pack/plugins/apm/typings/Span';
|
||||
import { IUrlParams } from '../../../store/urlParams';
|
||||
import { callApi } from '../callApi';
|
||||
import { addVersion, getEncodedEsQuery } from './apm';
|
||||
|
||||
export async function loadSpans({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
transactionId
|
||||
}: IUrlParams) {
|
||||
const hits = await callApi<SpanListAPIResponse>({
|
||||
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}/spans`,
|
||||
query: {
|
||||
start,
|
||||
end
|
||||
}
|
||||
});
|
||||
|
||||
return hits.map(addVersion).map(addSpanId);
|
||||
}
|
||||
|
||||
function addSpanId(hit: Span, i: number) {
|
||||
if (!hit.span.id) {
|
||||
hit.span.id = i;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
export async function loadTransaction({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
transactionId,
|
||||
traceId,
|
||||
kuery
|
||||
}: IUrlParams) {
|
||||
const result = await callApi<TransactionAPIResponse>(
|
||||
{
|
||||
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`,
|
||||
query: {
|
||||
traceId,
|
||||
start,
|
||||
end,
|
||||
esFilterQuery: await getEncodedEsQuery(kuery)
|
||||
}
|
||||
},
|
||||
{
|
||||
camelcase: false
|
||||
}
|
||||
);
|
||||
|
||||
return addVersion(result);
|
||||
}
|
13
x-pack/plugins/apm/public/services/rest/xpack.ts
Normal file
13
x-pack/plugins/apm/public/services/rest/xpack.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 { callApi } from './callApi';
|
||||
|
||||
export async function loadLicense() {
|
||||
return callApi({
|
||||
pathname: `/api/xpack/v1/info`
|
||||
});
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import * as rest from '../../../services/rest/apm';
|
||||
import * as rest from '../../../services/rest/apm/services';
|
||||
import { getServiceList, ServiceListRequest } from '../serviceList';
|
||||
import { mountWithStore } from '../../../utils/testHelpers';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Request } from 'react-redux-request';
|
||||
import { loadErrorDistribution } from '../../services/rest/apm';
|
||||
import { loadErrorDistribution } from '../../services/rest/apm/error_groups';
|
||||
import { createInitialDataSelector } from './helpers';
|
||||
|
||||
const ID = 'errorDistribution';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import { createInitialDataSelector } from './helpers';
|
||||
import { Request } from 'react-redux-request';
|
||||
import { loadErrorGroupDetails } from '../../services/rest/apm';
|
||||
import { loadErrorGroupDetails } from '../../services/rest/apm/error_groups';
|
||||
|
||||
const ID = 'errorGroupDetails';
|
||||
const INITIAL_DATA = {};
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import { createInitialDataSelector } from './helpers';
|
||||
import { Request } from 'react-redux-request';
|
||||
import { loadErrorGroupList } from '../../services/rest/apm';
|
||||
import { loadErrorGroupList } from '../../services/rest/apm/error_groups';
|
||||
|
||||
const ID = 'errorGroupList';
|
||||
const INITIAL_DATA = [];
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import React from 'react';
|
||||
import { createInitialDataSelector } from './helpers';
|
||||
import { Request } from 'react-redux-request';
|
||||
import { loadLicense } from '../../services/rest/apm';
|
||||
import { loadLicense } from '../../services/rest/xpack';
|
||||
|
||||
const ID = 'license';
|
||||
const INITIAL_DATA = {
|
||||
|
|
|
@ -9,7 +9,7 @@ import _ from 'lodash';
|
|||
import PropTypes from 'prop-types';
|
||||
import { createInitialDataSelector } from './helpers';
|
||||
import { Request } from 'react-redux-request';
|
||||
import { loadServiceDetails } from '../../services/rest/apm';
|
||||
import { loadServiceDetails } from '../../services/rest/apm/services';
|
||||
|
||||
const ID = 'serviceDetails';
|
||||
const INITIAL_DATA = { types: [] };
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import { Request, RRRRender, RRRRenderResponse } from 'react-redux-request';
|
||||
import { IServiceListItem } from 'x-pack/plugins/apm/server/lib/services/get_services';
|
||||
import { loadServiceList } from '../../services/rest/apm';
|
||||
import { loadServiceList } from '../../services/rest/apm/services';
|
||||
import { IReduxState } from '../rootReducer';
|
||||
import { IUrlParams } from '../urlParams';
|
||||
// @ts-ignore
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import { Request } from 'react-redux-request';
|
||||
import { createSelector } from 'reselect';
|
||||
import { loadTraceList } from '../../services/rest/apm';
|
||||
import { loadTraceList } from '../../services/rest/apm/traces';
|
||||
import { createInitialDataSelector } from './helpers';
|
||||
|
||||
const ID = 'traceList';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import { Request, RRRRender } from 'react-redux-request';
|
||||
import { Transaction } from 'x-pack/plugins/apm/typings/Transaction';
|
||||
import { loadTransaction } from '../../services/rest/apm';
|
||||
import { loadTransaction } from '../../services/rest/apm/transactions';
|
||||
import { IReduxState } from '../rootReducer';
|
||||
import { IUrlParams } from '../urlParams';
|
||||
// @ts-ignore
|
||||
|
|
|
@ -8,7 +8,7 @@ import React from 'react';
|
|||
import { Request, RRRRender } from 'react-redux-request';
|
||||
import { createSelector } from 'reselect';
|
||||
import { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts';
|
||||
import { loadCharts } from '../../services/rest/apm';
|
||||
import { loadDetailsCharts } from '../../services/rest/apm/transaction_groups';
|
||||
import { IReduxState } from '../rootReducer';
|
||||
import { getCharts } from '../selectors/chartSelectors';
|
||||
import { getUrlParams, IUrlParams } from '../urlParams';
|
||||
|
@ -61,7 +61,7 @@ export function TransactionDetailsChartsRequest({ urlParams, render }: Props) {
|
|||
return (
|
||||
<Request
|
||||
id={ID}
|
||||
fn={loadCharts}
|
||||
fn={loadDetailsCharts}
|
||||
args={[
|
||||
{ serviceName, start, end, transactionType, transactionName, kuery }
|
||||
]}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import { Request, RRRRender, RRRRenderResponse } from 'react-redux-request';
|
||||
import { ITransactionDistributionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/distribution';
|
||||
import { loadTransactionDistribution } from '../../services/rest/apm';
|
||||
import { loadTransactionDistribution } from '../../services/rest/apm/transaction_groups';
|
||||
import { IReduxState } from '../rootReducer';
|
||||
import { IUrlParams } from '../urlParams';
|
||||
// @ts-ignore
|
||||
|
@ -41,6 +41,7 @@ export function TransactionDistributionRequest({
|
|||
}) {
|
||||
const {
|
||||
serviceName,
|
||||
transactionType,
|
||||
transactionId,
|
||||
start,
|
||||
end,
|
||||
|
@ -48,7 +49,7 @@ export function TransactionDistributionRequest({
|
|||
kuery
|
||||
} = urlParams;
|
||||
|
||||
if (!(serviceName && start && end && transactionName)) {
|
||||
if (!(serviceName && transactionType && start && end && transactionName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -57,7 +58,15 @@ export function TransactionDistributionRequest({
|
|||
id={ID}
|
||||
fn={loadTransactionDistribution}
|
||||
args={[
|
||||
{ serviceName, transactionId, start, end, transactionName, kuery }
|
||||
{
|
||||
serviceName,
|
||||
transactionType,
|
||||
transactionId,
|
||||
start,
|
||||
end,
|
||||
transactionName,
|
||||
kuery
|
||||
}
|
||||
]}
|
||||
selector={getTransactionDistribution}
|
||||
render={render}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Request } from 'react-redux-request';
|
||||
import { loadTransactionList } from '../../services/rest/apm';
|
||||
import { loadTransactionList } from '../../services/rest/apm/transaction_groups';
|
||||
import { createInitialDataSelector } from './helpers';
|
||||
|
||||
const ID = 'transactionList';
|
||||
|
|
|
@ -9,7 +9,7 @@ import React from 'react';
|
|||
import { Request, RRRRender } from 'react-redux-request';
|
||||
import { createSelector } from 'reselect';
|
||||
import { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts';
|
||||
import { loadCharts } from '../../services/rest/apm';
|
||||
import { loadOverviewCharts } from '../../services/rest/apm/transaction_groups';
|
||||
import { IReduxState } from '../rootReducer';
|
||||
import { getCharts } from '../selectors/chartSelectors';
|
||||
import { getUrlParams, IUrlParams } from '../urlParams';
|
||||
|
@ -61,7 +61,7 @@ export function TransactionOverviewChartsRequest({ urlParams, render }: Props) {
|
|||
return (
|
||||
<Request
|
||||
id={ID}
|
||||
fn={loadCharts}
|
||||
fn={loadOverviewCharts}
|
||||
args={[{ serviceName, start, end, transactionType, kuery }]}
|
||||
selector={getTransactionOverviewCharts}
|
||||
render={render}
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
getWaterfall,
|
||||
IWaterfall
|
||||
} from '../../components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers';
|
||||
import { loadSpans } from '../../services/rest/apm';
|
||||
import { loadSpans } from '../../services/rest/apm/transactions';
|
||||
import { IUrlParams } from '../urlParams';
|
||||
// @ts-ignore
|
||||
import { createInitialDataSelector } from './helpers';
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
getWaterfall,
|
||||
IWaterfall
|
||||
} from '../../components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers';
|
||||
import { loadTrace } from '../../services/rest/apm';
|
||||
import { loadTrace } from '../../services/rest/apm/traces';
|
||||
import { IUrlParams } from '../urlParams';
|
||||
// @ts-ignore
|
||||
import { createInitialDataSelector } from './helpers';
|
||||
|
|
|
@ -26,6 +26,9 @@ Array [
|
|||
"top_hits": Object {
|
||||
"size": 1,
|
||||
"sort": Array [
|
||||
Object {
|
||||
"_score": "desc",
|
||||
},
|
||||
Object {
|
||||
"@timestamp": Object {
|
||||
"order": "desc",
|
||||
|
@ -34,11 +37,16 @@ Array [
|
|||
],
|
||||
},
|
||||
},
|
||||
"sum": Object {
|
||||
"sum": Object {
|
||||
"field": "transaction.duration.us",
|
||||
},
|
||||
},
|
||||
},
|
||||
"terms": Object {
|
||||
"field": "transaction.name.keyword",
|
||||
"order": Object {
|
||||
"avg": "desc",
|
||||
"sum": "desc",
|
||||
},
|
||||
"size": 100,
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@ exports[`transactionGroupsTransformer should match snapshot 1`] = `
|
|||
Array [
|
||||
Object {
|
||||
"averageResponseTime": 255966.30555555556,
|
||||
"impact": 4.369340653255684,
|
||||
"impact": 4.3693406535517445,
|
||||
"name": "POST /api/orders",
|
||||
"p95": 320238.5,
|
||||
"sample": Object {
|
||||
|
@ -255,7 +255,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 33265.03326147213,
|
||||
"impact": 10.256357027900046,
|
||||
"impact": 10.256357027376065,
|
||||
"name": "GET /api/orders",
|
||||
"p95": 58827.489999999976,
|
||||
"sample": Object {
|
||||
|
@ -376,7 +376,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 32900.72714285714,
|
||||
"impact": 2.179120743402716,
|
||||
"impact": 2.1791207411745854,
|
||||
"name": "GET /log-message",
|
||||
"p95": 40444,
|
||||
"sample": Object {
|
||||
|
@ -497,7 +497,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 32554.36257814184,
|
||||
"impact": 14.344171564855404,
|
||||
"impact": 14.344171563678346,
|
||||
"name": "GET /api/stats",
|
||||
"p95": 59356.73611111111,
|
||||
"sample": Object {
|
||||
|
@ -624,7 +624,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 32387.73641304348,
|
||||
"impact": 2.2558112391673664,
|
||||
"impact": 2.2558112380477584,
|
||||
"name": "GET /log-error",
|
||||
"p95": 40061.1,
|
||||
"sample": Object {
|
||||
|
@ -745,7 +745,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 32159.926322043968,
|
||||
"impact": 10.279049521913821,
|
||||
"impact": 10.27904952170656,
|
||||
"name": "GET /api/customers",
|
||||
"p95": 59845.85714285714,
|
||||
"sample": Object {
|
||||
|
@ -872,7 +872,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 27516.89144558744,
|
||||
"impact": 9.651458993728006,
|
||||
"impact": 9.651458992731666,
|
||||
"name": "GET /api/products/top",
|
||||
"p95": 56064.679999999986,
|
||||
"sample": Object {
|
||||
|
@ -1000,7 +1000,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 21331.714285714286,
|
||||
"impact": 0.28817488008070574,
|
||||
"impact": 0.28817487960409877,
|
||||
"name": "POST /api",
|
||||
"p95": 30938,
|
||||
"sample": Object {
|
||||
|
@ -1123,7 +1123,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 17189.329210275926,
|
||||
"impact": 3.424381788267164,
|
||||
"impact": 3.424381787142002,
|
||||
"name": "GET /api/products/:id/customers",
|
||||
"p95": 39284.79999999999,
|
||||
"sample": Object {
|
||||
|
@ -1250,7 +1250,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 12763.68806073154,
|
||||
"impact": 1.747992435179465,
|
||||
"impact": 1.7479924334286208,
|
||||
"name": "GET /api/types/:id",
|
||||
"p95": 30576.749999999996,
|
||||
"sample": Object {
|
||||
|
@ -1371,7 +1371,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 12683.190864600327,
|
||||
"impact": 4.4239778511514745,
|
||||
"impact": 4.4239778504968,
|
||||
"name": "GET /api/products",
|
||||
"p95": 35009.67999999999,
|
||||
"sample": Object {
|
||||
|
@ -1492,7 +1492,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 11257.757916666667,
|
||||
"impact": 2.558180605423081,
|
||||
"impact": 2.558180605569336,
|
||||
"name": "GET /api/types",
|
||||
"p95": 35222.944444444445,
|
||||
"sample": Object {
|
||||
|
@ -1613,7 +1613,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 10584.05144193297,
|
||||
"impact": 1.2808106158729446,
|
||||
"impact": 1.280810614916383,
|
||||
"name": "GET /api/orders/:id",
|
||||
"p95": 26555.399999999998,
|
||||
"sample": Object {
|
||||
|
@ -1732,7 +1732,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 10548.218597063622,
|
||||
"impact": 1.8338764008269306,
|
||||
"impact": 1.8338763992340905,
|
||||
"name": "GET /api/products/:id",
|
||||
"p95": 28413.383333333328,
|
||||
"sample": Object {
|
||||
|
@ -1853,7 +1853,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 9868.217894736843,
|
||||
"impact": 1.7722323979309487,
|
||||
"impact": 1.7722323960215767,
|
||||
"name": "GET /api/customers/:id",
|
||||
"p95": 27486.5,
|
||||
"sample": Object {
|
||||
|
@ -2111,7 +2111,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 4694.005586592179,
|
||||
"impact": 0.1498514997591876,
|
||||
"impact": 0.1498515000753004,
|
||||
"name": "GET /is-it-coffee-time",
|
||||
"p95": 11022.99999999992,
|
||||
"sample": Object {
|
||||
|
@ -2233,7 +2233,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 4549.889880952381,
|
||||
"impact": 0.1354336505457395,
|
||||
"impact": 0.13543365054509587,
|
||||
"name": "GET /throw-error",
|
||||
"p95": 7719.700000000001,
|
||||
"sample": Object {
|
||||
|
@ -2355,7 +2355,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 3504.5108924806746,
|
||||
"impact": 2.36009934580083,
|
||||
"impact": 2.3600993453143766,
|
||||
"name": "GET *",
|
||||
"p95": 11431.738095238095,
|
||||
"sample": Object {
|
||||
|
@ -2482,7 +2482,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 2742.4615384615386,
|
||||
"impact": 0.08501029113483448,
|
||||
"impact": 0.08501028923348058,
|
||||
"name": "OPTIONS unknown route",
|
||||
"p95": 4370.000000000002,
|
||||
"sample": Object {
|
||||
|
@ -2605,7 +2605,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 2651.8784461553205,
|
||||
"impact": 15.770246498769827,
|
||||
"impact": 15.770246496477105,
|
||||
"name": "GET static file",
|
||||
"p95": 6140.579335038363,
|
||||
"sample": Object {
|
||||
|
@ -2713,7 +2713,7 @@ baz",
|
|||
},
|
||||
Object {
|
||||
"averageResponseTime": 1422.926672899693,
|
||||
"impact": 1.002712481568783,
|
||||
"impact": 1.0027124806135428,
|
||||
"name": "GET unknown route",
|
||||
"p95": 2311.885238095238,
|
||||
"sample": Object {
|
||||
|
|
|
@ -16,14 +16,9 @@ import { Setup } from '../helpers/setup_request';
|
|||
interface Bucket {
|
||||
key: string;
|
||||
doc_count: number;
|
||||
avg: {
|
||||
value: number;
|
||||
};
|
||||
p95: {
|
||||
values: {
|
||||
'95.0': number;
|
||||
};
|
||||
};
|
||||
avg: { value: number };
|
||||
p95: { values: { '95.0': number } };
|
||||
sum: { value: number };
|
||||
sample: {
|
||||
hits: {
|
||||
total: number;
|
||||
|
@ -57,20 +52,24 @@ export function transactionGroupsFetcher(
|
|||
transactions: {
|
||||
terms: {
|
||||
field: `${TRANSACTION_NAME}.keyword`,
|
||||
order: { avg: 'desc' },
|
||||
order: { sum: 'desc' },
|
||||
size: 100
|
||||
},
|
||||
aggs: {
|
||||
sample: {
|
||||
top_hits: {
|
||||
size: 1,
|
||||
sort: [{ '@timestamp': { order: 'desc' } }]
|
||||
sort: [
|
||||
{ _score: 'desc' }, // sort by _score to ensure that buckets with sampled:true ends up on top
|
||||
{ '@timestamp': { order: 'desc' } }
|
||||
]
|
||||
}
|
||||
},
|
||||
avg: { avg: { field: TRANSACTION_DURATION } },
|
||||
p95: {
|
||||
percentiles: { field: TRANSACTION_DURATION, percents: [95] }
|
||||
}
|
||||
},
|
||||
sum: { sum: { field: TRANSACTION_DURATION } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 180,
|
||||
avg: { value: 255966.30555555556 },
|
||||
p95: { values: { '95.0': 320238.5 } },
|
||||
sum: { value: 46073935 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 180,
|
||||
|
@ -140,6 +141,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 21911,
|
||||
avg: { value: 48021.972616494 },
|
||||
p95: { values: { '95.0': 67138.18364917398 } },
|
||||
sum: { value: 1052209442 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 21911,
|
||||
|
@ -259,6 +261,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 3247,
|
||||
avg: { value: 33265.03326147213 },
|
||||
p95: { values: { '95.0': 58827.489999999976 } },
|
||||
sum: { value: 108011563 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 3247,
|
||||
|
@ -374,6 +377,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 700,
|
||||
avg: { value: 32900.72714285714 },
|
||||
p95: { values: { '95.0': 40444 } },
|
||||
sum: { value: 23030509 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 700,
|
||||
|
@ -489,6 +493,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 4639,
|
||||
avg: { value: 32554.36257814184 },
|
||||
p95: { values: { '95.0': 59356.73611111111 } },
|
||||
sum: { value: 151019688 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 4639,
|
||||
|
@ -609,6 +614,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 736,
|
||||
avg: { value: 32387.73641304348 },
|
||||
p95: { values: { '95.0': 40061.1 } },
|
||||
sum: { value: 23837374 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 736,
|
||||
|
@ -724,6 +730,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 3366,
|
||||
avg: { value: 32159.926322043968 },
|
||||
p95: { values: { '95.0': 59845.85714285714 } },
|
||||
sum: { value: 108250312 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 3366,
|
||||
|
@ -844,6 +851,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 3694,
|
||||
avg: { value: 27516.89144558744 },
|
||||
p95: { values: { '95.0': 56064.679999999986 } },
|
||||
sum: { value: 101647397 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 3694,
|
||||
|
@ -965,6 +973,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 147,
|
||||
avg: { value: 21331.714285714286 },
|
||||
p95: { values: { '95.0': 30938 } },
|
||||
sum: { value: 3135762 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 147,
|
||||
|
@ -1082,6 +1091,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 2102,
|
||||
avg: { value: 17189.329210275926 },
|
||||
p95: { values: { '95.0': 39284.79999999999 } },
|
||||
sum: { value: 36131970 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 2102,
|
||||
|
@ -1203,6 +1213,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 1449,
|
||||
avg: { value: 12763.68806073154 },
|
||||
p95: { values: { '95.0': 30576.749999999996 } },
|
||||
sum: { value: 18494584 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 1449,
|
||||
|
@ -1318,6 +1329,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 3678,
|
||||
avg: { value: 12683.190864600327 },
|
||||
p95: { values: { '95.0': 35009.67999999999 } },
|
||||
sum: { value: 46648776 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 3678,
|
||||
|
@ -1433,6 +1445,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 2400,
|
||||
avg: { value: 11257.757916666667 },
|
||||
p95: { values: { '95.0': 35222.944444444445 } },
|
||||
sum: { value: 27018619 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 2400,
|
||||
|
@ -1548,6 +1561,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 1283,
|
||||
avg: { value: 10584.05144193297 },
|
||||
p95: { values: { '95.0': 26555.399999999998 } },
|
||||
sum: { value: 13579338 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 1283,
|
||||
|
@ -1661,6 +1675,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 1839,
|
||||
avg: { value: 10548.218597063622 },
|
||||
p95: { values: { '95.0': 28413.383333333328 } },
|
||||
sum: { value: 19398174 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 1839,
|
||||
|
@ -1776,6 +1791,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 1900,
|
||||
avg: { value: 9868.217894736843 },
|
||||
p95: { values: { '95.0': 27486.5 } },
|
||||
sum: { value: 18749614 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 1900,
|
||||
|
@ -1896,6 +1912,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 20,
|
||||
avg: { value: 5192.9 },
|
||||
p95: { values: { '95.0': 13230.5 } },
|
||||
sum: { value: 103858 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 20,
|
||||
|
@ -2021,6 +2038,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 358,
|
||||
avg: { value: 4694.005586592179 },
|
||||
p95: { values: { '95.0': 11022.99999999992 } },
|
||||
sum: { value: 1680454 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 358,
|
||||
|
@ -2137,6 +2155,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 336,
|
||||
avg: { value: 4549.889880952381 },
|
||||
p95: { values: { '95.0': 7719.700000000001 } },
|
||||
sum: { value: 1528763 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 336,
|
||||
|
@ -2253,6 +2272,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 7115,
|
||||
avg: { value: 3504.5108924806746 },
|
||||
p95: { values: { '95.0': 11431.738095238095 } },
|
||||
sum: { value: 24934595 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 7115,
|
||||
|
@ -2375,6 +2395,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 364,
|
||||
avg: { value: 2742.4615384615386 },
|
||||
p95: { values: { '95.0': 4370.000000000002 } },
|
||||
sum: { value: 998256 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 364,
|
||||
|
@ -2492,6 +2513,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 62606,
|
||||
avg: { value: 2651.8784461553205 },
|
||||
p95: { values: { '95.0': 6140.579335038363 } },
|
||||
sum: { value: 166023502 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 62606,
|
||||
|
@ -2596,6 +2618,7 @@ export const transactionGroupsResponse = ({
|
|||
doc_count: 7487,
|
||||
avg: { value: 1422.926672899693 },
|
||||
p95: { values: { '95.0': 2311.885238095238 } },
|
||||
sum: { value: 10653452 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 7487,
|
||||
|
|
|
@ -19,12 +19,13 @@ describe('transactionGroupsTransformer', () => {
|
|||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
fit('should transform response correctly', () => {
|
||||
it('should transform response correctly', () => {
|
||||
const bucket = {
|
||||
key: 'POST /api/orders',
|
||||
doc_count: 180,
|
||||
avg: { value: 255966.30555555556 },
|
||||
p95: { values: { '95.0': 320238.5 } },
|
||||
sum: { value: 3000000000 },
|
||||
sample: {
|
||||
hits: {
|
||||
total: 180,
|
||||
|
@ -54,4 +55,27 @@ describe('transactionGroupsTransformer', () => {
|
|||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should calculate impact from sum', () => {
|
||||
const getBucket = (sum: number) => ({
|
||||
key: 'POST /api/orders',
|
||||
doc_count: 180,
|
||||
avg: { value: 300000 },
|
||||
p95: { values: { '95.0': 320000 } },
|
||||
sum: { value: sum },
|
||||
sample: { hits: { total: 180, hits: [{ _source: 'sample source' }] } }
|
||||
});
|
||||
|
||||
const response = ({
|
||||
aggregations: {
|
||||
transactions: { buckets: [getBucket(10), getBucket(20), getBucket(50)] }
|
||||
}
|
||||
} as unknown) as ESResponse;
|
||||
|
||||
expect(
|
||||
transactionGroupsTransformer({ response, start: 100, end: 20000 }).map(
|
||||
bucket => bucket.impact
|
||||
)
|
||||
).toEqual([0, 25, 100]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -44,7 +44,7 @@ export function transactionGroupsTransformer({
|
|||
const results = buckets.map(bucket => {
|
||||
const averageResponseTime = bucket.avg.value;
|
||||
const transactionsPerMinute = bucket.doc_count / minutes;
|
||||
const impact = Math.round(averageResponseTime * transactionsPerMinute);
|
||||
const impact = bucket.sum.value;
|
||||
const sample = bucket.sample.hits.hits[0]._source;
|
||||
|
||||
return {
|
||||
|
|
|
@ -8,13 +8,15 @@ import { SearchParams } from 'elasticsearch';
|
|||
import {
|
||||
SERVICE_NAME,
|
||||
TRANSACTION_DURATION,
|
||||
TRANSACTION_NAME
|
||||
TRANSACTION_NAME,
|
||||
TRANSACTION_TYPE
|
||||
} from '../../../../common/constants';
|
||||
import { Setup } from '../../helpers/setup_request';
|
||||
|
||||
export async function calculateBucketSize(
|
||||
serviceName: string,
|
||||
transactionName: string,
|
||||
transactionType: string,
|
||||
setup: Setup
|
||||
) {
|
||||
const { start, end, esFilterQuery, client, config } = setup;
|
||||
|
@ -27,6 +29,7 @@ export async function calculateBucketSize(
|
|||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [TRANSACTION_TYPE]: transactionType } },
|
||||
{ term: { [`${TRANSACTION_NAME}.keyword`]: transactionName } },
|
||||
{
|
||||
range: {
|
||||
|
|
|
@ -11,7 +11,8 @@ import {
|
|||
TRANSACTION_DURATION,
|
||||
TRANSACTION_ID,
|
||||
TRANSACTION_NAME,
|
||||
TRANSACTION_SAMPLED
|
||||
TRANSACTION_SAMPLED,
|
||||
TRANSACTION_TYPE
|
||||
} from 'x-pack/plugins/apm/common/constants';
|
||||
import { Setup } from 'x-pack/plugins/apm/server/lib/helpers/setup_request';
|
||||
import { Transaction } from 'x-pack/plugins/apm/typings/Transaction';
|
||||
|
@ -38,6 +39,7 @@ export type ESResponse = AggregationSearchResponse<void, Aggs>;
|
|||
export function bucketFetcher(
|
||||
serviceName: string,
|
||||
transactionName: string,
|
||||
transactionType: string,
|
||||
transactionId: string,
|
||||
bucketSize: number,
|
||||
setup: Setup
|
||||
|
@ -52,6 +54,7 @@ export function bucketFetcher(
|
|||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [TRANSACTION_TYPE]: transactionType } },
|
||||
{ term: { [`${TRANSACTION_NAME}.keyword`]: transactionName } },
|
||||
{
|
||||
range: {
|
||||
|
|
|
@ -11,6 +11,7 @@ import { bucketTransformer } from './transform';
|
|||
export async function getBuckets(
|
||||
serviceName: string,
|
||||
transactionName: string,
|
||||
transactionType: string,
|
||||
transactionId: string,
|
||||
bucketSize: number,
|
||||
setup: Setup
|
||||
|
@ -18,6 +19,7 @@ export async function getBuckets(
|
|||
const response = await bucketFetcher(
|
||||
serviceName,
|
||||
transactionName,
|
||||
transactionType,
|
||||
transactionId,
|
||||
bucketSize,
|
||||
setup
|
||||
|
|
|
@ -19,18 +19,21 @@ export interface ITransactionDistributionAPIResponse {
|
|||
export async function getDistribution(
|
||||
serviceName: string,
|
||||
transactionName: string,
|
||||
transactionType: string,
|
||||
transactionId: string,
|
||||
setup: Setup
|
||||
): Promise<ITransactionDistributionAPIResponse> {
|
||||
const bucketSize = await calculateBucketSize(
|
||||
serviceName,
|
||||
transactionName,
|
||||
transactionType,
|
||||
setup
|
||||
);
|
||||
|
||||
const { defaultSample, buckets, totalHits } = await getBuckets(
|
||||
serviceName,
|
||||
transactionName,
|
||||
transactionType,
|
||||
transactionId,
|
||||
bucketSize,
|
||||
setup
|
||||
|
|
110
x-pack/plugins/apm/server/routes/transaction_groups.ts
Normal file
110
x-pack/plugins/apm/server/routes/transaction_groups.ts
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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 Boom from 'boom';
|
||||
import { Server } from 'hapi';
|
||||
import Joi from 'joi';
|
||||
import { withDefaultValidators } from '../lib/helpers/input_validation';
|
||||
import { setupRequest } from '../lib/helpers/setup_request';
|
||||
import { getChartsData } from '../lib/transactions/charts';
|
||||
import { getDistribution } from '../lib/transactions/distribution';
|
||||
import { getTopTransactions } from '../lib/transactions/get_top_transactions';
|
||||
|
||||
const defaultErrorHandler = (err: Error) => {
|
||||
// tslint:disable-next-line
|
||||
console.error(err.stack);
|
||||
throw Boom.boomify(err, { statusCode: 400 });
|
||||
};
|
||||
|
||||
export function initTransactionGroupsApi(server: Server) {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path:
|
||||
'/api/apm/services/{serviceName}/transaction_groups/{transactionType}',
|
||||
options: {
|
||||
validate: {
|
||||
query: withDefaultValidators({
|
||||
query: Joi.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: req => {
|
||||
const { serviceName, transactionType } = req.params;
|
||||
const setup = setupRequest(req);
|
||||
|
||||
return getTopTransactions({
|
||||
serviceName,
|
||||
transactionType,
|
||||
setup
|
||||
}).catch(defaultErrorHandler);
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `/api/apm/services/{serviceName}/transaction_groups/{transactionType}/charts`,
|
||||
options: {
|
||||
validate: {
|
||||
query: withDefaultValidators()
|
||||
}
|
||||
},
|
||||
handler: req => {
|
||||
const setup = setupRequest(req);
|
||||
const { serviceName, transactionType } = req.params;
|
||||
|
||||
return getChartsData({
|
||||
serviceName,
|
||||
transactionType,
|
||||
setup
|
||||
}).catch(defaultErrorHandler);
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `/api/apm/services/{serviceName}/transaction_groups/{transactionType}/{transactionName}/charts`,
|
||||
options: {
|
||||
validate: {
|
||||
query: withDefaultValidators()
|
||||
}
|
||||
},
|
||||
handler: req => {
|
||||
const setup = setupRequest(req);
|
||||
const { serviceName, transactionType, transactionName } = req.params;
|
||||
|
||||
return getChartsData({
|
||||
serviceName,
|
||||
transactionType,
|
||||
transactionName,
|
||||
setup
|
||||
}).catch(defaultErrorHandler);
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `/api/apm/services/{serviceName}/transaction_groups/{transactionType}/{transactionName}/distribution`,
|
||||
options: {
|
||||
validate: {
|
||||
query: withDefaultValidators({
|
||||
transactionId: Joi.string().default('')
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: req => {
|
||||
const setup = setupRequest(req);
|
||||
const { serviceName, transactionType, transactionName } = req.params;
|
||||
const { transactionId } = req.query as { transactionId: string };
|
||||
return getDistribution(
|
||||
serviceName,
|
||||
transactionName,
|
||||
transactionType,
|
||||
transactionId,
|
||||
setup
|
||||
).catch(defaultErrorHandler);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -9,13 +9,9 @@ import { Server } from 'hapi';
|
|||
import Joi from 'joi';
|
||||
import { withDefaultValidators } from '../lib/helpers/input_validation';
|
||||
import { setupRequest } from '../lib/helpers/setup_request';
|
||||
import { getChartsData } from '../lib/transactions/charts';
|
||||
import { getDistribution } from '../lib/transactions/distribution';
|
||||
import { getTopTransactions } from '../lib/transactions/get_top_transactions';
|
||||
import { getTransaction } from '../lib/transactions/get_transaction';
|
||||
import { getSpans } from '../lib/transactions/spans/get_spans';
|
||||
|
||||
const ROOT = '/api/apm/services/{serviceName}/transactions';
|
||||
const defaultErrorHandler = (err: Error) => {
|
||||
// tslint:disable-next-line
|
||||
console.error(err.stack);
|
||||
|
@ -25,37 +21,11 @@ const defaultErrorHandler = (err: Error) => {
|
|||
export function initTransactionsApi(server: Server) {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: ROOT,
|
||||
path: `/api/apm/services/{serviceName}/transactions/{transactionId}`,
|
||||
options: {
|
||||
validate: {
|
||||
query: withDefaultValidators({
|
||||
transaction_type: Joi.string().default('request'),
|
||||
query: Joi.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: req => {
|
||||
const { serviceName } = req.params;
|
||||
const { transaction_type: transactionType } = req.query as {
|
||||
transaction_type: string;
|
||||
};
|
||||
const setup = setupRequest(req);
|
||||
|
||||
return getTopTransactions({
|
||||
serviceName,
|
||||
transactionType,
|
||||
setup
|
||||
}).catch(defaultErrorHandler);
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `${ROOT}/{transactionId}`,
|
||||
options: {
|
||||
validate: {
|
||||
query: withDefaultValidators({
|
||||
traceId: Joi.string().allow('')
|
||||
traceId: Joi.string().allow('') // TODO: this should be a path param and made required by 7.0
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -69,9 +39,10 @@ export function initTransactionsApi(server: Server) {
|
|||
}
|
||||
});
|
||||
|
||||
// TODO: this can be removed by 7.0 when v1 compatability can be dropped
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `${ROOT}/{transactionId}/spans`,
|
||||
path: `/api/apm/services/{serviceName}/transactions/{transactionId}/spans`,
|
||||
options: {
|
||||
validate: {
|
||||
query: withDefaultValidators()
|
||||
|
@ -83,65 +54,4 @@ export function initTransactionsApi(server: Server) {
|
|||
return getSpans(transactionId, setup).catch(defaultErrorHandler);
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `${ROOT}/charts`,
|
||||
options: {
|
||||
validate: {
|
||||
query: withDefaultValidators({
|
||||
transaction_type: Joi.string().default('request'),
|
||||
transaction_name: Joi.string(),
|
||||
query: Joi.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: req => {
|
||||
const setup = setupRequest(req);
|
||||
const { serviceName } = req.params;
|
||||
const { transaction_type: transactionType } = req.query as {
|
||||
transaction_type: string;
|
||||
};
|
||||
const { transaction_name: transactionName } = req.query as {
|
||||
transaction_name: string;
|
||||
};
|
||||
|
||||
return getChartsData({
|
||||
serviceName,
|
||||
transactionType,
|
||||
transactionName,
|
||||
setup
|
||||
}).catch(defaultErrorHandler);
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `${ROOT}/distribution`,
|
||||
options: {
|
||||
validate: {
|
||||
query: withDefaultValidators({
|
||||
transaction_name: Joi.string().required(),
|
||||
transaction_id: Joi.string().default('')
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: req => {
|
||||
const setup = setupRequest(req);
|
||||
const { serviceName } = req.params;
|
||||
const {
|
||||
transaction_name: transactionName,
|
||||
transaction_id: transactionId
|
||||
} = req.query as {
|
||||
transaction_name: string;
|
||||
transaction_id: string;
|
||||
};
|
||||
return getDistribution(
|
||||
serviceName,
|
||||
transactionName,
|
||||
transactionId,
|
||||
setup
|
||||
).catch(defaultErrorHandler);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue