mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
* [APM]: Inferred types for aggregations Previously, aggregations returned by the ESClient were 'any' by default, and the return type had to be explicitly defined by the consumer to get any type safety. This leads to both type duplication and errors because of wrong assumptions. This change infers the aggregation return type from the parameters passed to ESClient.search. * Fix idx error * Safeguard against querying against non-existing indices in functional tests * Improve metric typings * Automatically infer params from function arguments * Remove unnecessary type hints
This commit is contained in:
parent
3551d6a3f3
commit
d3853ee9c6
38 changed files with 598 additions and 823 deletions
|
@ -22,14 +22,20 @@ const getRelativeImpact = (
|
|||
);
|
||||
|
||||
function getWithRelativeImpact(items: TransactionListAPIResponse) {
|
||||
const impacts = items.map(({ impact }) => impact);
|
||||
const impacts = items
|
||||
.map(({ impact }) => impact)
|
||||
.filter(impact => impact !== null) as number[];
|
||||
|
||||
const impactMin = Math.min(...impacts);
|
||||
const impactMax = Math.max(...impacts);
|
||||
|
||||
return items.map(item => {
|
||||
return {
|
||||
...item,
|
||||
impactRelative: getRelativeImpact(item.impact, impactMin, impactMax)
|
||||
impactRelative:
|
||||
item.impact !== null
|
||||
? getRelativeImpact(item.impact, impactMin, impactMax)
|
||||
: null
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { BucketAgg, ESFilter } from 'elasticsearch';
|
||||
import { ESFilter } from 'elasticsearch';
|
||||
import {
|
||||
ERROR_GROUP_ID,
|
||||
PROCESSOR_EVENT,
|
||||
|
@ -61,13 +61,8 @@ export async function getBuckets({
|
|||
}
|
||||
};
|
||||
|
||||
interface Aggs {
|
||||
distribution: {
|
||||
buckets: Array<BucketAgg<number>>;
|
||||
};
|
||||
}
|
||||
const resp = await client.search(params);
|
||||
|
||||
const resp = await client.search<void, Aggs>(params);
|
||||
const buckets = resp.aggregations.distribution.buckets.map(bucket => ({
|
||||
key: bucket.key,
|
||||
count: bucket.doc_count
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SearchParams } from 'elasticsearch';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import {
|
||||
ERROR_CULPRIT,
|
||||
|
@ -37,7 +36,10 @@ export async function getErrorGroups({
|
|||
}) {
|
||||
const { start, end, uiFiltersES, client, config } = setup;
|
||||
|
||||
const params: SearchParams = {
|
||||
// sort buckets by last occurrence of error
|
||||
const sortByLatestOccurrence = sortField === 'latestOccurrenceAt';
|
||||
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.errorIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
|
@ -56,7 +58,11 @@ export async function getErrorGroups({
|
|||
terms: {
|
||||
field: ERROR_GROUP_ID,
|
||||
size: 500,
|
||||
order: { _count: sortDirection }
|
||||
order: sortByLatestOccurrence
|
||||
? {
|
||||
max_timestamp: sortDirection
|
||||
}
|
||||
: { _count: sortDirection }
|
||||
},
|
||||
aggs: {
|
||||
sample: {
|
||||
|
@ -72,24 +78,22 @@ export async function getErrorGroups({
|
|||
sort: [{ '@timestamp': 'desc' }],
|
||||
size: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
...(sortByLatestOccurrence
|
||||
? {
|
||||
max_timestamp: {
|
||||
max: {
|
||||
field: '@timestamp'
|
||||
}
|
||||
}
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// sort buckets by last occurrence of error
|
||||
if (sortField === 'latestOccurrenceAt') {
|
||||
params.body.aggs.error_groups.terms.order = {
|
||||
max_timestamp: sortDirection
|
||||
};
|
||||
|
||||
params.body.aggs.error_groups.aggs.max_timestamp = {
|
||||
max: { field: '@timestamp' }
|
||||
};
|
||||
}
|
||||
|
||||
interface SampleError {
|
||||
'@timestamp': APMError['@timestamp'];
|
||||
error: {
|
||||
|
@ -105,44 +109,27 @@ export async function getErrorGroups({
|
|||
};
|
||||
}
|
||||
|
||||
interface Bucket {
|
||||
key: string;
|
||||
doc_count: number;
|
||||
sample: {
|
||||
hits: {
|
||||
total: number;
|
||||
max_score: number | null;
|
||||
hits: Array<{
|
||||
_source: SampleError;
|
||||
}>;
|
||||
const resp = await client.search(params);
|
||||
|
||||
// aggregations can be undefined when no matching indices are found.
|
||||
// this is an exception rather than the rule so the ES type does not account for this.
|
||||
const hits = (idx(resp, _ => _.aggregations.error_groups.buckets) || []).map(
|
||||
bucket => {
|
||||
const source = bucket.sample.hits.hits[0]._source as SampleError;
|
||||
const message =
|
||||
idx(source, _ => _.error.log.message) ||
|
||||
idx(source, _ => _.error.exception[0].message);
|
||||
|
||||
return {
|
||||
message,
|
||||
occurrenceCount: bucket.doc_count,
|
||||
culprit: idx(source, _ => _.error.culprit),
|
||||
groupId: idx(source, _ => _.error.grouping_key),
|
||||
latestOccurrenceAt: source['@timestamp'],
|
||||
handled: idx(source, _ => _.error.exception[0].handled)
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface Aggs {
|
||||
error_groups: {
|
||||
buckets: Bucket[];
|
||||
};
|
||||
}
|
||||
|
||||
const resp = await client.search<void, Aggs>(params);
|
||||
const buckets = idx(resp, _ => _.aggregations.error_groups.buckets) || [];
|
||||
|
||||
const hits = buckets.map(bucket => {
|
||||
const source = bucket.sample.hits.hits[0]._source;
|
||||
const message =
|
||||
idx(source, _ => _.error.log.message) ||
|
||||
idx(source, _ => _.error.exception[0].message);
|
||||
|
||||
return {
|
||||
message,
|
||||
occurrenceCount: bucket.doc_count,
|
||||
culprit: idx(source, _ => _.error.culprit),
|
||||
groupId: idx(source, _ => _.error.grouping_key),
|
||||
latestOccurrenceAt: source['@timestamp'],
|
||||
handled: idx(source, _ => _.error.exception[0].handled)
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return hits;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SearchParams } from 'elasticsearch';
|
||||
import {
|
||||
PROCESSOR_EVENT,
|
||||
TRACE_ID,
|
||||
|
@ -17,25 +16,14 @@ export interface ErrorsPerTransaction {
|
|||
[transactionId: string]: number;
|
||||
}
|
||||
|
||||
interface TraceErrorsAggBucket {
|
||||
key: string;
|
||||
doc_count: number;
|
||||
}
|
||||
|
||||
interface TraceErrorsAggResponse {
|
||||
transactions: {
|
||||
buckets: TraceErrorsAggBucket[];
|
||||
};
|
||||
}
|
||||
|
||||
export async function getTraceErrorsPerTransaction(
|
||||
traceId: string,
|
||||
setup: Setup
|
||||
): Promise<ErrorsPerTransaction> {
|
||||
const { start, end, client, config } = setup;
|
||||
|
||||
const params: SearchParams = {
|
||||
index: [config.get('apm_oss.errorIndices')],
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.errorIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
|
@ -57,7 +45,7 @@ export async function getTraceErrorsPerTransaction(
|
|||
}
|
||||
};
|
||||
|
||||
const resp = await client.search<never, TraceErrorsAggResponse>(params);
|
||||
const resp = await client.search(params);
|
||||
|
||||
return resp.aggregations.transactions.buckets.reduce(
|
||||
(acc, bucket) => ({
|
||||
|
|
|
@ -90,10 +90,10 @@ export function getESClient(req: Legacy.Request) {
|
|||
const query = (req.query as unknown) as APMRequestQuery;
|
||||
|
||||
return {
|
||||
search: async <Hits = unknown, Aggs = unknown>(
|
||||
params: SearchParams,
|
||||
search: async <Hits = unknown, U extends SearchParams = {}>(
|
||||
params: U,
|
||||
apmOptions?: APMOptions
|
||||
): Promise<AggregationSearchResponse<Hits, Aggs>> => {
|
||||
): Promise<AggregationSearchResponse<Hits, U>> => {
|
||||
const nextParams = await getParamsForSearchRequest(
|
||||
req,
|
||||
params,
|
||||
|
@ -112,7 +112,7 @@ export function getESClient(req: Legacy.Request) {
|
|||
}
|
||||
|
||||
return cluster.callWithRequest(req, 'search', nextParams) as Promise<
|
||||
AggregationSearchResponse<Hits, Aggs>
|
||||
AggregationSearchResponse<Hits, U>
|
||||
>;
|
||||
},
|
||||
index: <Body>(params: IndexDocumentParams<Body>) => {
|
||||
|
|
|
@ -1,64 +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 {
|
||||
SERVICE_AGENT_NAME,
|
||||
PROCESSOR_EVENT,
|
||||
SERVICE_NAME,
|
||||
METRIC_JAVA_HEAP_MEMORY_MAX,
|
||||
METRIC_JAVA_HEAP_MEMORY_COMMITTED,
|
||||
METRIC_JAVA_HEAP_MEMORY_USED
|
||||
} from '../../../../../../common/elasticsearch_fieldnames';
|
||||
import { Setup } from '../../../../helpers/setup_request';
|
||||
import { MetricsAggs, MetricSeriesKeys, AggValue } from '../../../types';
|
||||
import { getMetricsDateHistogramParams } from '../../../../helpers/metrics';
|
||||
import { rangeFilter } from '../../../../helpers/range_filter';
|
||||
|
||||
export interface HeapMemoryMetrics extends MetricSeriesKeys {
|
||||
heapMemoryMax: AggValue;
|
||||
heapMemoryCommitted: AggValue;
|
||||
heapMemoryUsed: AggValue;
|
||||
}
|
||||
|
||||
export async function fetch(setup: Setup, serviceName: string) {
|
||||
const { start, end, uiFiltersES, client, config } = setup;
|
||||
|
||||
const aggs = {
|
||||
heapMemoryMax: { avg: { field: METRIC_JAVA_HEAP_MEMORY_MAX } },
|
||||
heapMemoryCommitted: {
|
||||
avg: { field: METRIC_JAVA_HEAP_MEMORY_COMMITTED }
|
||||
},
|
||||
heapMemoryUsed: { avg: { field: METRIC_JAVA_HEAP_MEMORY_USED } }
|
||||
};
|
||||
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.metricsIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [PROCESSOR_EVENT]: 'metric' } },
|
||||
{ term: { [SERVICE_AGENT_NAME]: 'java' } },
|
||||
{
|
||||
range: rangeFilter(start, end)
|
||||
},
|
||||
...uiFiltersES
|
||||
]
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
timeseriesData: {
|
||||
date_histogram: getMetricsDateHistogramParams(start, end),
|
||||
aggs
|
||||
},
|
||||
...aggs
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return client.search<void, MetricsAggs<HeapMemoryMetrics>>(params);
|
||||
}
|
|
@ -6,49 +6,62 @@
|
|||
|
||||
import theme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
METRIC_JAVA_HEAP_MEMORY_MAX,
|
||||
METRIC_JAVA_HEAP_MEMORY_COMMITTED,
|
||||
METRIC_JAVA_HEAP_MEMORY_USED,
|
||||
SERVICE_AGENT_NAME
|
||||
} from '../../../../../../common/elasticsearch_fieldnames';
|
||||
import { Setup } from '../../../../helpers/setup_request';
|
||||
import { fetch, HeapMemoryMetrics } from './fetcher';
|
||||
import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics';
|
||||
import { ChartBase } from '../../../types';
|
||||
import { transformDataToMetricsChart } from '../../../transform_metrics_chart';
|
||||
|
||||
// TODO: i18n for titles
|
||||
const series = {
|
||||
heapMemoryUsed: {
|
||||
title: i18n.translate('xpack.apm.agentMetrics.java.heapMemorySeriesUsed', {
|
||||
defaultMessage: 'Avg. used'
|
||||
}),
|
||||
color: theme.euiColorVis0
|
||||
},
|
||||
heapMemoryCommitted: {
|
||||
title: i18n.translate(
|
||||
'xpack.apm.agentMetrics.java.heapMemorySeriesCommitted',
|
||||
{
|
||||
defaultMessage: 'Avg. committed'
|
||||
}
|
||||
),
|
||||
color: theme.euiColorVis1
|
||||
},
|
||||
heapMemoryMax: {
|
||||
title: i18n.translate('xpack.apm.agentMetrics.java.heapMemorySeriesMax', {
|
||||
defaultMessage: 'Avg. limit'
|
||||
}),
|
||||
color: theme.euiColorVis2
|
||||
}
|
||||
};
|
||||
|
||||
const chartBase: ChartBase<HeapMemoryMetrics> = {
|
||||
const chartBase: ChartBase = {
|
||||
title: i18n.translate('xpack.apm.agentMetrics.java.heapMemoryChartTitle', {
|
||||
defaultMessage: 'Heap Memory'
|
||||
}),
|
||||
key: 'heap_memory_area_chart',
|
||||
type: 'area',
|
||||
yUnit: 'bytes',
|
||||
series: {
|
||||
heapMemoryUsed: {
|
||||
title: i18n.translate(
|
||||
'xpack.apm.agentMetrics.java.heapMemorySeriesUsed',
|
||||
{
|
||||
defaultMessage: 'Avg. used'
|
||||
}
|
||||
),
|
||||
color: theme.euiColorVis0
|
||||
},
|
||||
heapMemoryCommitted: {
|
||||
title: i18n.translate(
|
||||
'xpack.apm.agentMetrics.java.heapMemorySeriesCommitted',
|
||||
{
|
||||
defaultMessage: 'Avg. committed'
|
||||
}
|
||||
),
|
||||
color: theme.euiColorVis1
|
||||
},
|
||||
heapMemoryMax: {
|
||||
title: i18n.translate('xpack.apm.agentMetrics.java.heapMemorySeriesMax', {
|
||||
defaultMessage: 'Avg. limit'
|
||||
}),
|
||||
color: theme.euiColorVis2
|
||||
}
|
||||
}
|
||||
series
|
||||
};
|
||||
|
||||
export async function getHeapMemoryChart(setup: Setup, serviceName: string) {
|
||||
const result = await fetch(setup, serviceName);
|
||||
return transformDataToMetricsChart<HeapMemoryMetrics>(result, chartBase);
|
||||
return fetchAndTransformMetrics({
|
||||
setup,
|
||||
serviceName,
|
||||
chartBase,
|
||||
aggs: {
|
||||
heapMemoryMax: { avg: { field: METRIC_JAVA_HEAP_MEMORY_MAX } },
|
||||
heapMemoryCommitted: {
|
||||
avg: { field: METRIC_JAVA_HEAP_MEMORY_COMMITTED }
|
||||
},
|
||||
heapMemoryUsed: { avg: { field: METRIC_JAVA_HEAP_MEMORY_USED } }
|
||||
},
|
||||
additionalFilters: [{ term: { [SERVICE_AGENT_NAME]: 'java' } }]
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,65 +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 {
|
||||
SERVICE_AGENT_NAME,
|
||||
PROCESSOR_EVENT,
|
||||
SERVICE_NAME,
|
||||
METRIC_JAVA_NON_HEAP_MEMORY_MAX,
|
||||
METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED,
|
||||
METRIC_JAVA_NON_HEAP_MEMORY_USED
|
||||
} from '../../../../../../common/elasticsearch_fieldnames';
|
||||
import { Setup } from '../../../../helpers/setup_request';
|
||||
import { MetricsAggs, MetricSeriesKeys, AggValue } from '../../../types';
|
||||
import { getMetricsDateHistogramParams } from '../../../../helpers/metrics';
|
||||
import { rangeFilter } from '../../../../helpers/range_filter';
|
||||
|
||||
export interface NonHeapMemoryMetrics extends MetricSeriesKeys {
|
||||
nonHeapMemoryCommitted: AggValue;
|
||||
nonHeapMemoryUsed: AggValue;
|
||||
}
|
||||
|
||||
export async function fetch(setup: Setup, serviceName: string) {
|
||||
const { start, end, uiFiltersES, client, config } = setup;
|
||||
|
||||
const aggs = {
|
||||
nonHeapMemoryMax: { avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_MAX } },
|
||||
nonHeapMemoryCommitted: {
|
||||
avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED }
|
||||
},
|
||||
nonHeapMemoryUsed: {
|
||||
avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_USED }
|
||||
}
|
||||
};
|
||||
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.metricsIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [PROCESSOR_EVENT]: 'metric' } },
|
||||
{ term: { [SERVICE_AGENT_NAME]: 'java' } },
|
||||
{
|
||||
range: rangeFilter(start, end)
|
||||
},
|
||||
...uiFiltersES
|
||||
]
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
timeseriesData: {
|
||||
date_histogram: getMetricsDateHistogramParams(start, end),
|
||||
aggs
|
||||
},
|
||||
...aggs
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return client.search<void, MetricsAggs<NonHeapMemoryMetrics>>(params);
|
||||
}
|
|
@ -6,41 +6,61 @@
|
|||
|
||||
import theme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
METRIC_JAVA_NON_HEAP_MEMORY_MAX,
|
||||
METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED,
|
||||
METRIC_JAVA_NON_HEAP_MEMORY_USED,
|
||||
SERVICE_AGENT_NAME
|
||||
} from '../../../../../../common/elasticsearch_fieldnames';
|
||||
import { Setup } from '../../../../helpers/setup_request';
|
||||
import { fetch, NonHeapMemoryMetrics } from './fetcher';
|
||||
import { ChartBase } from '../../../types';
|
||||
import { transformDataToMetricsChart } from '../../../transform_metrics_chart';
|
||||
import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics';
|
||||
|
||||
const chartBase: ChartBase<NonHeapMemoryMetrics> = {
|
||||
const series = {
|
||||
nonHeapMemoryUsed: {
|
||||
title: i18n.translate(
|
||||
'xpack.apm.agentMetrics.java.nonHeapMemorySeriesUsed',
|
||||
{
|
||||
defaultMessage: 'Avg. used'
|
||||
}
|
||||
),
|
||||
color: theme.euiColorVis0
|
||||
},
|
||||
nonHeapMemoryCommitted: {
|
||||
title: i18n.translate(
|
||||
'xpack.apm.agentMetrics.java.nonHeapMemorySeriesCommitted',
|
||||
{
|
||||
defaultMessage: 'Avg. committed'
|
||||
}
|
||||
),
|
||||
color: theme.euiColorVis1
|
||||
}
|
||||
};
|
||||
|
||||
const chartBase: ChartBase = {
|
||||
title: i18n.translate('xpack.apm.agentMetrics.java.nonHeapMemoryChartTitle', {
|
||||
defaultMessage: 'Non-Heap Memory'
|
||||
}),
|
||||
key: 'non_heap_memory_area_chart',
|
||||
type: 'area',
|
||||
yUnit: 'bytes',
|
||||
series: {
|
||||
nonHeapMemoryUsed: {
|
||||
title: i18n.translate(
|
||||
'xpack.apm.agentMetrics.java.nonHeapMemorySeriesUsed',
|
||||
{
|
||||
defaultMessage: 'Avg. used'
|
||||
}
|
||||
),
|
||||
color: theme.euiColorVis0
|
||||
},
|
||||
nonHeapMemoryCommitted: {
|
||||
title: i18n.translate(
|
||||
'xpack.apm.agentMetrics.java.nonHeapMemorySeriesCommitted',
|
||||
{
|
||||
defaultMessage: 'Avg. committed'
|
||||
}
|
||||
),
|
||||
color: theme.euiColorVis1
|
||||
}
|
||||
}
|
||||
series
|
||||
};
|
||||
|
||||
export async function getNonHeapMemoryChart(setup: Setup, serviceName: string) {
|
||||
const result = await fetch(setup, serviceName);
|
||||
return transformDataToMetricsChart<NonHeapMemoryMetrics>(result, chartBase);
|
||||
return fetchAndTransformMetrics({
|
||||
setup,
|
||||
serviceName,
|
||||
chartBase,
|
||||
aggs: {
|
||||
nonHeapMemoryMax: { avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_MAX } },
|
||||
nonHeapMemoryCommitted: {
|
||||
avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED }
|
||||
},
|
||||
nonHeapMemoryUsed: {
|
||||
avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_USED }
|
||||
}
|
||||
},
|
||||
additionalFilters: [{ term: { [SERVICE_AGENT_NAME]: 'java' } }]
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,55 +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 {
|
||||
SERVICE_AGENT_NAME,
|
||||
PROCESSOR_EVENT,
|
||||
SERVICE_NAME,
|
||||
METRIC_JAVA_THREAD_COUNT
|
||||
} from '../../../../../../common/elasticsearch_fieldnames';
|
||||
import { Setup } from '../../../../helpers/setup_request';
|
||||
import { MetricsAggs, MetricSeriesKeys, AggValue } from '../../../types';
|
||||
import { getMetricsDateHistogramParams } from '../../../../helpers/metrics';
|
||||
import { rangeFilter } from '../../../../helpers/range_filter';
|
||||
|
||||
export interface ThreadCountMetrics extends MetricSeriesKeys {
|
||||
threadCount: AggValue;
|
||||
}
|
||||
|
||||
export async function fetch(setup: Setup, serviceName: string) {
|
||||
const { start, end, uiFiltersES, client, config } = setup;
|
||||
|
||||
const aggs = {
|
||||
threadCount: { avg: { field: METRIC_JAVA_THREAD_COUNT } },
|
||||
threadCountMax: { max: { field: METRIC_JAVA_THREAD_COUNT } }
|
||||
};
|
||||
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.metricsIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [PROCESSOR_EVENT]: 'metric' } },
|
||||
{ term: { [SERVICE_AGENT_NAME]: 'java' } },
|
||||
{ range: rangeFilter(start, end) },
|
||||
...uiFiltersES
|
||||
]
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
timeseriesData: {
|
||||
date_histogram: getMetricsDateHistogramParams(start, end),
|
||||
aggs
|
||||
},
|
||||
...aggs
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return client.search<void, MetricsAggs<ThreadCountMetrics>>(params);
|
||||
}
|
|
@ -6,35 +6,48 @@
|
|||
|
||||
import theme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
METRIC_JAVA_THREAD_COUNT,
|
||||
SERVICE_AGENT_NAME
|
||||
} from '../../../../../../common/elasticsearch_fieldnames';
|
||||
import { Setup } from '../../../../helpers/setup_request';
|
||||
import { fetch, ThreadCountMetrics } from './fetcher';
|
||||
import { ChartBase } from '../../../types';
|
||||
import { transformDataToMetricsChart } from '../../../transform_metrics_chart';
|
||||
import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics';
|
||||
|
||||
const chartBase: ChartBase<ThreadCountMetrics> = {
|
||||
const series = {
|
||||
threadCount: {
|
||||
title: i18n.translate('xpack.apm.agentMetrics.java.threadCount', {
|
||||
defaultMessage: 'Avg. count'
|
||||
}),
|
||||
color: theme.euiColorVis0
|
||||
},
|
||||
threadCountMax: {
|
||||
title: i18n.translate('xpack.apm.agentMetrics.java.threadCountMax', {
|
||||
defaultMessage: 'Max count'
|
||||
}),
|
||||
color: theme.euiColorVis1
|
||||
}
|
||||
};
|
||||
|
||||
const chartBase: ChartBase = {
|
||||
title: i18n.translate('xpack.apm.agentMetrics.java.threadCountChartTitle', {
|
||||
defaultMessage: 'Thread Count'
|
||||
}),
|
||||
key: 'thread_count_line_chart',
|
||||
type: 'linemark',
|
||||
yUnit: 'number',
|
||||
series: {
|
||||
threadCount: {
|
||||
title: i18n.translate('xpack.apm.agentMetrics.java.threadCount', {
|
||||
defaultMessage: 'Avg. count'
|
||||
}),
|
||||
color: theme.euiColorVis0
|
||||
},
|
||||
threadCountMax: {
|
||||
title: i18n.translate('xpack.apm.agentMetrics.java.threadCountMax', {
|
||||
defaultMessage: 'Max count'
|
||||
}),
|
||||
color: theme.euiColorVis1
|
||||
}
|
||||
}
|
||||
series
|
||||
};
|
||||
|
||||
export async function getThreadCountChart(setup: Setup, serviceName: string) {
|
||||
const result = await fetch(setup, serviceName);
|
||||
return transformDataToMetricsChart<ThreadCountMetrics>(result, chartBase);
|
||||
return fetchAndTransformMetrics({
|
||||
setup,
|
||||
serviceName,
|
||||
chartBase,
|
||||
aggs: {
|
||||
threadCount: { avg: { field: METRIC_JAVA_THREAD_COUNT } },
|
||||
threadCountMax: { max: { field: METRIC_JAVA_THREAD_COUNT } }
|
||||
},
|
||||
additionalFilters: [{ term: { [SERVICE_AGENT_NAME]: 'java' } }]
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,62 +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 {
|
||||
METRIC_PROCESS_CPU_PERCENT,
|
||||
METRIC_SYSTEM_CPU_PERCENT,
|
||||
PROCESSOR_EVENT,
|
||||
SERVICE_NAME
|
||||
} from '../../../../../../common/elasticsearch_fieldnames';
|
||||
import { Setup } from '../../../../helpers/setup_request';
|
||||
import { MetricsAggs, MetricSeriesKeys, AggValue } from '../../../types';
|
||||
import { getMetricsDateHistogramParams } from '../../../../helpers/metrics';
|
||||
import { rangeFilter } from '../../../../helpers/range_filter';
|
||||
|
||||
export interface CPUMetrics extends MetricSeriesKeys {
|
||||
systemCPUAverage: AggValue;
|
||||
systemCPUMax: AggValue;
|
||||
processCPUAverage: AggValue;
|
||||
processCPUMax: AggValue;
|
||||
}
|
||||
|
||||
export async function fetch(setup: Setup, serviceName: string) {
|
||||
const { start, end, uiFiltersES, client, config } = setup;
|
||||
|
||||
const aggs = {
|
||||
systemCPUAverage: { avg: { field: METRIC_SYSTEM_CPU_PERCENT } },
|
||||
systemCPUMax: { max: { field: METRIC_SYSTEM_CPU_PERCENT } },
|
||||
processCPUAverage: { avg: { field: METRIC_PROCESS_CPU_PERCENT } },
|
||||
processCPUMax: { max: { field: METRIC_PROCESS_CPU_PERCENT } }
|
||||
};
|
||||
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.metricsIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [PROCESSOR_EVENT]: 'metric' } },
|
||||
{
|
||||
range: rangeFilter(start, end)
|
||||
},
|
||||
...uiFiltersES
|
||||
]
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
timeseriesData: {
|
||||
date_histogram: getMetricsDateHistogramParams(start, end),
|
||||
aggs
|
||||
},
|
||||
...aggs
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return client.search<void, MetricsAggs<CPUMetrics>>(params);
|
||||
}
|
|
@ -6,47 +6,63 @@
|
|||
|
||||
import theme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
METRIC_SYSTEM_CPU_PERCENT,
|
||||
METRIC_PROCESS_CPU_PERCENT
|
||||
} from '../../../../../../common/elasticsearch_fieldnames';
|
||||
import { Setup } from '../../../../helpers/setup_request';
|
||||
import { fetch, CPUMetrics } from './fetcher';
|
||||
import { ChartBase } from '../../../types';
|
||||
import { transformDataToMetricsChart } from '../../../transform_metrics_chart';
|
||||
import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics';
|
||||
|
||||
const chartBase: ChartBase<CPUMetrics> = {
|
||||
const series = {
|
||||
systemCPUMax: {
|
||||
title: i18n.translate('xpack.apm.chart.cpuSeries.systemMaxLabel', {
|
||||
defaultMessage: 'System max'
|
||||
}),
|
||||
color: theme.euiColorVis1
|
||||
},
|
||||
systemCPUAverage: {
|
||||
title: i18n.translate('xpack.apm.chart.cpuSeries.systemAverageLabel', {
|
||||
defaultMessage: 'System average'
|
||||
}),
|
||||
color: theme.euiColorVis0
|
||||
},
|
||||
processCPUMax: {
|
||||
title: i18n.translate('xpack.apm.chart.cpuSeries.processMaxLabel', {
|
||||
defaultMessage: 'Process max'
|
||||
}),
|
||||
color: theme.euiColorVis7
|
||||
},
|
||||
processCPUAverage: {
|
||||
title: i18n.translate('xpack.apm.chart.cpuSeries.processAverageLabel', {
|
||||
defaultMessage: 'Process average'
|
||||
}),
|
||||
color: theme.euiColorVis5
|
||||
}
|
||||
};
|
||||
|
||||
const chartBase: ChartBase = {
|
||||
title: i18n.translate('xpack.apm.serviceDetails.metrics.cpuUsageChartTitle', {
|
||||
defaultMessage: 'CPU usage'
|
||||
}),
|
||||
key: 'cpu_usage_chart',
|
||||
type: 'linemark',
|
||||
yUnit: 'percent',
|
||||
series: {
|
||||
systemCPUMax: {
|
||||
title: i18n.translate('xpack.apm.chart.cpuSeries.systemMaxLabel', {
|
||||
defaultMessage: 'System max'
|
||||
}),
|
||||
color: theme.euiColorVis1
|
||||
},
|
||||
systemCPUAverage: {
|
||||
title: i18n.translate('xpack.apm.chart.cpuSeries.systemAverageLabel', {
|
||||
defaultMessage: 'System average'
|
||||
}),
|
||||
color: theme.euiColorVis0
|
||||
},
|
||||
processCPUMax: {
|
||||
title: i18n.translate('xpack.apm.chart.cpuSeries.processMaxLabel', {
|
||||
defaultMessage: 'Process max'
|
||||
}),
|
||||
color: theme.euiColorVis7
|
||||
},
|
||||
processCPUAverage: {
|
||||
title: i18n.translate('xpack.apm.chart.cpuSeries.processAverageLabel', {
|
||||
defaultMessage: 'Process average'
|
||||
}),
|
||||
color: theme.euiColorVis5
|
||||
}
|
||||
}
|
||||
series
|
||||
};
|
||||
|
||||
export async function getCPUChartData(setup: Setup, serviceName: string) {
|
||||
const result = await fetch(setup, serviceName);
|
||||
return transformDataToMetricsChart<CPUMetrics>(result, chartBase);
|
||||
const metricsChart = await fetchAndTransformMetrics({
|
||||
setup,
|
||||
serviceName,
|
||||
chartBase,
|
||||
aggs: {
|
||||
systemCPUAverage: { avg: { field: METRIC_SYSTEM_CPU_PERCENT } },
|
||||
systemCPUMax: { max: { field: METRIC_SYSTEM_CPU_PERCENT } },
|
||||
processCPUAverage: { avg: { field: METRIC_PROCESS_CPU_PERCENT } },
|
||||
processCPUMax: { max: { field: METRIC_PROCESS_CPU_PERCENT } }
|
||||
}
|
||||
});
|
||||
|
||||
return metricsChart;
|
||||
}
|
||||
|
|
|
@ -1,73 +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,
|
||||
METRIC_SYSTEM_FREE_MEMORY,
|
||||
METRIC_SYSTEM_TOTAL_MEMORY
|
||||
} from '../../../../../../common/elasticsearch_fieldnames';
|
||||
import { Setup } from '../../../../helpers/setup_request';
|
||||
import { MetricsAggs, MetricSeriesKeys, AggValue } from '../../../types';
|
||||
import { getMetricsDateHistogramParams } from '../../../../helpers/metrics';
|
||||
import { rangeFilter } from '../../../../helpers/range_filter';
|
||||
|
||||
export interface MemoryMetrics extends MetricSeriesKeys {
|
||||
memoryUsedAvg: AggValue;
|
||||
memoryUsedMax: AggValue;
|
||||
}
|
||||
|
||||
const percentUsedScript = {
|
||||
lang: 'expression',
|
||||
source: `1 - doc['${METRIC_SYSTEM_FREE_MEMORY}'] / doc['${METRIC_SYSTEM_TOTAL_MEMORY}']`
|
||||
};
|
||||
|
||||
export async function fetch(setup: Setup, serviceName: string) {
|
||||
const { start, end, uiFiltersES, client, config } = setup;
|
||||
|
||||
const aggs = {
|
||||
memoryUsedAvg: { avg: { script: percentUsedScript } },
|
||||
memoryUsedMax: { max: { script: percentUsedScript } }
|
||||
};
|
||||
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.metricsIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [PROCESSOR_EVENT]: 'metric' } },
|
||||
{
|
||||
range: rangeFilter(start, end)
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: METRIC_SYSTEM_FREE_MEMORY
|
||||
}
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: METRIC_SYSTEM_TOTAL_MEMORY
|
||||
}
|
||||
},
|
||||
...uiFiltersES
|
||||
]
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
timeseriesData: {
|
||||
date_histogram: getMetricsDateHistogramParams(start, end),
|
||||
aggs
|
||||
},
|
||||
...aggs
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return client.search<void, MetricsAggs<MemoryMetrics>>(params);
|
||||
}
|
|
@ -5,12 +5,28 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
METRIC_SYSTEM_FREE_MEMORY,
|
||||
METRIC_SYSTEM_TOTAL_MEMORY
|
||||
} from '../../../../../../common/elasticsearch_fieldnames';
|
||||
import { Setup } from '../../../../helpers/setup_request';
|
||||
import { fetch, MemoryMetrics } from './fetcher';
|
||||
import { ChartBase } from '../../../types';
|
||||
import { transformDataToMetricsChart } from '../../../transform_metrics_chart';
|
||||
import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics';
|
||||
|
||||
const chartBase: ChartBase<MemoryMetrics> = {
|
||||
const series = {
|
||||
memoryUsedMax: {
|
||||
title: i18n.translate('xpack.apm.chart.memorySeries.systemMaxLabel', {
|
||||
defaultMessage: 'Max'
|
||||
})
|
||||
},
|
||||
memoryUsedAvg: {
|
||||
title: i18n.translate('xpack.apm.chart.memorySeries.systemAverageLabel', {
|
||||
defaultMessage: 'Average'
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const chartBase: ChartBase = {
|
||||
title: i18n.translate(
|
||||
'xpack.apm.serviceDetails.metrics.memoryUsageChartTitle',
|
||||
{
|
||||
|
@ -20,21 +36,34 @@ const chartBase: ChartBase<MemoryMetrics> = {
|
|||
key: 'memory_usage_chart',
|
||||
type: 'linemark',
|
||||
yUnit: 'percent',
|
||||
series: {
|
||||
memoryUsedMax: {
|
||||
title: i18n.translate('xpack.apm.chart.memorySeries.systemMaxLabel', {
|
||||
defaultMessage: 'Max'
|
||||
})
|
||||
},
|
||||
memoryUsedAvg: {
|
||||
title: i18n.translate('xpack.apm.chart.memorySeries.systemAverageLabel', {
|
||||
defaultMessage: 'Average'
|
||||
})
|
||||
}
|
||||
}
|
||||
series
|
||||
};
|
||||
|
||||
const percentUsedScript = {
|
||||
lang: 'expression',
|
||||
source: `1 - doc['${METRIC_SYSTEM_FREE_MEMORY}'] / doc['${METRIC_SYSTEM_TOTAL_MEMORY}']`
|
||||
};
|
||||
|
||||
export async function getMemoryChartData(setup: Setup, serviceName: string) {
|
||||
const result = await fetch(setup, serviceName);
|
||||
return transformDataToMetricsChart<MemoryMetrics>(result, chartBase);
|
||||
return fetchAndTransformMetrics({
|
||||
setup,
|
||||
serviceName,
|
||||
chartBase,
|
||||
aggs: {
|
||||
memoryUsedAvg: { avg: { script: percentUsedScript } },
|
||||
memoryUsedMax: { max: { script: percentUsedScript } }
|
||||
},
|
||||
additionalFilters: [
|
||||
{
|
||||
exists: {
|
||||
field: METRIC_SYSTEM_FREE_MEMORY
|
||||
}
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: METRIC_SYSTEM_TOTAL_MEMORY
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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
|
||||
} from '../../../common/elasticsearch_fieldnames';
|
||||
import { Setup } from '../helpers/setup_request';
|
||||
import { getMetricsDateHistogramParams } from '../helpers/metrics';
|
||||
import { rangeFilter } from '../helpers/range_filter';
|
||||
import { ChartBase } from './types';
|
||||
import { transformDataToMetricsChart } from './transform_metrics_chart';
|
||||
|
||||
interface Aggs {
|
||||
[key: string]: {
|
||||
min?: any;
|
||||
max?: any;
|
||||
sum?: any;
|
||||
avg?: any;
|
||||
};
|
||||
}
|
||||
|
||||
interface Filter {
|
||||
exists?: {
|
||||
field: string;
|
||||
};
|
||||
term?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchAndTransformMetrics<T extends Aggs>({
|
||||
setup,
|
||||
serviceName,
|
||||
chartBase,
|
||||
aggs,
|
||||
additionalFilters = []
|
||||
}: {
|
||||
setup: Setup;
|
||||
serviceName: string;
|
||||
chartBase: ChartBase;
|
||||
aggs: T;
|
||||
additionalFilters?: Filter[];
|
||||
}) {
|
||||
const { start, end, uiFiltersES, client, config } = setup;
|
||||
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.metricsIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [PROCESSOR_EVENT]: 'metric' } },
|
||||
{
|
||||
range: rangeFilter(start, end)
|
||||
},
|
||||
...additionalFilters,
|
||||
...uiFiltersES
|
||||
]
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
timeseriesData: {
|
||||
date_histogram: getMetricsDateHistogramParams(start, end),
|
||||
aggs
|
||||
},
|
||||
...aggs
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const response = await client.search(params);
|
||||
|
||||
return transformDataToMetricsChart(response, chartBase);
|
||||
}
|
|
@ -3,22 +3,12 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { AggregationSearchResponse } from 'elasticsearch';
|
||||
import { MetricsAggs, MetricSeriesKeys, AggValue } from './types';
|
||||
import { transformDataToMetricsChart } from './transform_metrics_chart';
|
||||
import { ChartType, YUnit } from '../../../typings/timeseries';
|
||||
|
||||
test('transformDataToMetricsChart should transform an ES result into a chart object', () => {
|
||||
interface TestKeys extends MetricSeriesKeys {
|
||||
a: AggValue;
|
||||
b: AggValue;
|
||||
c: AggValue;
|
||||
}
|
||||
|
||||
type R = AggregationSearchResponse<void, MetricsAggs<TestKeys>>;
|
||||
|
||||
const response = {
|
||||
hits: { total: 5000 } as R['hits'],
|
||||
hits: { total: 5000 },
|
||||
aggregations: {
|
||||
a: { value: 1000 },
|
||||
b: { value: 1000 },
|
||||
|
@ -29,24 +19,27 @@ test('transformDataToMetricsChart should transform an ES result into a chart obj
|
|||
a: { value: 10 },
|
||||
b: { value: 10 },
|
||||
c: { value: 10 },
|
||||
key: 1
|
||||
} as R['aggregations']['timeseriesData']['buckets'][0],
|
||||
key: 1,
|
||||
doc_count: 0
|
||||
},
|
||||
{
|
||||
a: { value: 20 },
|
||||
b: { value: 20 },
|
||||
c: { value: 20 },
|
||||
key: 2
|
||||
} as R['aggregations']['timeseriesData']['buckets'][0],
|
||||
key: 2,
|
||||
doc_count: 0
|
||||
},
|
||||
{
|
||||
a: { value: 30 },
|
||||
b: { value: 30 },
|
||||
c: { value: 30 },
|
||||
key: 3
|
||||
} as R['aggregations']['timeseriesData']['buckets'][0]
|
||||
key: 3,
|
||||
doc_count: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
} as R['aggregations']
|
||||
} as R;
|
||||
}
|
||||
} as any;
|
||||
|
||||
const chartBase = {
|
||||
title: 'Test Chart Title',
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { AggregationSearchResponse } from 'elasticsearch';
|
||||
import theme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { ChartBase, MetricsAggs, MetricSeriesKeys } from './types';
|
||||
import { AggregationSearchResponse, AggregatedValue } from 'elasticsearch';
|
||||
import { ChartBase } from './types';
|
||||
|
||||
const colors = [
|
||||
theme.euiColorVis0,
|
||||
|
@ -20,9 +20,33 @@ const colors = [
|
|||
export type GenericMetricsChart = ReturnType<
|
||||
typeof transformDataToMetricsChart
|
||||
>;
|
||||
export function transformDataToMetricsChart<T extends MetricSeriesKeys>(
|
||||
result: AggregationSearchResponse<void, MetricsAggs<T>>,
|
||||
chartBase: ChartBase<T>
|
||||
|
||||
interface AggregatedParams {
|
||||
body: {
|
||||
aggs: {
|
||||
timeseriesData: {
|
||||
date_histogram: any;
|
||||
aggs: {
|
||||
min?: any;
|
||||
max?: any;
|
||||
sum?: any;
|
||||
avg?: any;
|
||||
};
|
||||
};
|
||||
} & {
|
||||
[key: string]: {
|
||||
min?: any;
|
||||
max?: any;
|
||||
sum?: any;
|
||||
avg?: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function transformDataToMetricsChart<Params extends AggregatedParams>(
|
||||
result: AggregationSearchResponse<unknown, Params>,
|
||||
chartBase: ChartBase
|
||||
) {
|
||||
const { aggregations, hits } = result;
|
||||
const { timeseriesData } = aggregations;
|
||||
|
@ -32,20 +56,24 @@ export function transformDataToMetricsChart<T extends MetricSeriesKeys>(
|
|||
key: chartBase.key,
|
||||
yUnit: chartBase.yUnit,
|
||||
totalHits: hits.total,
|
||||
series: Object.keys(chartBase.series).map((seriesKey, i) => ({
|
||||
title: chartBase.series[seriesKey].title,
|
||||
key: seriesKey,
|
||||
type: chartBase.type,
|
||||
color: chartBase.series[seriesKey].color || colors[i],
|
||||
overallValue: aggregations[seriesKey].value,
|
||||
data: timeseriesData.buckets.map(bucket => {
|
||||
const { value } = bucket[seriesKey];
|
||||
const y = value === null || isNaN(value) ? null : value;
|
||||
return {
|
||||
x: bucket.key,
|
||||
y
|
||||
};
|
||||
})
|
||||
}))
|
||||
series: Object.keys(chartBase.series).map((seriesKey, i) => {
|
||||
const agg = aggregations[seriesKey];
|
||||
|
||||
return {
|
||||
title: chartBase.series[seriesKey].title,
|
||||
key: seriesKey,
|
||||
type: chartBase.type,
|
||||
color: chartBase.series[seriesKey].color || colors[i],
|
||||
overallValue: agg.value,
|
||||
data: timeseriesData.buckets.map(bucket => {
|
||||
const { value } = bucket[seriesKey] as AggregatedValue;
|
||||
const y = value === null || isNaN(value) ? null : value;
|
||||
return {
|
||||
x: bucket.key,
|
||||
y
|
||||
};
|
||||
})
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,38 +3,17 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ChartType, YUnit } from '../../../typings/timeseries';
|
||||
|
||||
export interface AggValue {
|
||||
value: number | null;
|
||||
}
|
||||
|
||||
export interface MetricSeriesKeys {
|
||||
[key: string]: AggValue;
|
||||
}
|
||||
|
||||
export interface ChartBase<T extends MetricSeriesKeys> {
|
||||
export interface ChartBase {
|
||||
title: string;
|
||||
key: string;
|
||||
type: ChartType;
|
||||
yUnit: YUnit;
|
||||
series: {
|
||||
[key in keyof T]: {
|
||||
[key: string]: {
|
||||
title: string;
|
||||
color?: string;
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type MetricsAggs<T extends MetricSeriesKeys> = {
|
||||
timeseriesData: {
|
||||
buckets: Array<
|
||||
{
|
||||
key_as_string: string; // timestamp as string
|
||||
key: number; // timestamp as epoch milliseconds
|
||||
doc_count: number;
|
||||
} & T
|
||||
>;
|
||||
};
|
||||
} & T;
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { BucketAgg } from 'elasticsearch';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import {
|
||||
PROCESSOR_EVENT,
|
||||
|
@ -48,19 +46,11 @@ export async function getService(serviceName: string, setup: Setup) {
|
|||
}
|
||||
};
|
||||
|
||||
interface Aggs {
|
||||
types: {
|
||||
buckets: BucketAgg[];
|
||||
};
|
||||
agents: {
|
||||
buckets: BucketAgg[];
|
||||
};
|
||||
}
|
||||
|
||||
const { aggregations } = await client.search<void, Aggs>(params);
|
||||
const { aggregations } = await client.search(params);
|
||||
const buckets = idx(aggregations, _ => _.types.buckets) || [];
|
||||
const types = buckets.map(bucket => bucket.key);
|
||||
const agentName = idx(aggregations, _ => _.agents.buckets[0].key);
|
||||
const agentName = idx(aggregations, _ => _.agents.buckets[0].key) || '';
|
||||
|
||||
return {
|
||||
serviceName,
|
||||
types,
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { BucketAgg } from 'elasticsearch';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import {
|
||||
PROCESSOR_EVENT,
|
||||
|
@ -65,33 +64,14 @@ export async function getServicesItems(setup: Setup) {
|
|||
}
|
||||
};
|
||||
|
||||
interface ServiceBucket extends BucketAgg {
|
||||
avg: {
|
||||
value: number;
|
||||
};
|
||||
agents: {
|
||||
buckets: BucketAgg[];
|
||||
};
|
||||
events: {
|
||||
buckets: BucketAgg[];
|
||||
};
|
||||
environments: {
|
||||
buckets: BucketAgg[];
|
||||
};
|
||||
}
|
||||
|
||||
interface Aggs extends BucketAgg {
|
||||
services: {
|
||||
buckets: ServiceBucket[];
|
||||
};
|
||||
}
|
||||
|
||||
const resp = await client.search<void, Aggs>(params);
|
||||
const resp = await client.search(params);
|
||||
const aggs = resp.aggregations;
|
||||
|
||||
const serviceBuckets = idx(aggs, _ => _.services.buckets) || [];
|
||||
|
||||
const items = serviceBuckets.map(bucket => {
|
||||
const eventTypes = bucket.events.buckets;
|
||||
|
||||
const transactions = eventTypes.find(e => e.key === 'transaction');
|
||||
const totalTransactions = idx(transactions, _ => _.doc_count) || 0;
|
||||
|
||||
|
|
|
@ -4,42 +4,17 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SearchParams } from 'elasticsearch';
|
||||
import {
|
||||
TRANSACTION_DURATION,
|
||||
TRANSACTION_NAME
|
||||
} from '../../../common/elasticsearch_fieldnames';
|
||||
import { PromiseReturnType, StringMap } from '../../../typings/common';
|
||||
import { Transaction } from '../../../typings/es_schemas/ui/Transaction';
|
||||
import { Setup } from '../helpers/setup_request';
|
||||
|
||||
interface Bucket {
|
||||
key: string;
|
||||
doc_count: number;
|
||||
avg: { value: number };
|
||||
p95: { values: { '95.0': number } };
|
||||
sum: { value: number };
|
||||
sample: {
|
||||
hits: {
|
||||
total: number;
|
||||
max_score: number | null;
|
||||
hits: Array<{
|
||||
_source: Transaction;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface Aggs {
|
||||
transactions: {
|
||||
buckets: Bucket[];
|
||||
};
|
||||
}
|
||||
|
||||
export type ESResponse = PromiseReturnType<typeof transactionGroupsFetcher>;
|
||||
export function transactionGroupsFetcher(setup: Setup, bodyQuery: StringMap) {
|
||||
const { client, config } = setup;
|
||||
const params: SearchParams = {
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.transactionIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
|
@ -72,5 +47,5 @@ export function transactionGroupsFetcher(setup: Setup, bodyQuery: StringMap) {
|
|||
}
|
||||
};
|
||||
|
||||
return client.search<void, Aggs>(params);
|
||||
return client.search(params);
|
||||
}
|
||||
|
|
|
@ -6,16 +6,23 @@
|
|||
|
||||
import moment from 'moment';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { Transaction } from '../../../typings/es_schemas/ui/Transaction';
|
||||
import { ESResponse } from './fetcher';
|
||||
|
||||
function calculateRelativeImpacts(transactionGroups: ITransactionGroup[]) {
|
||||
const values = transactionGroups.map(({ impact }) => impact);
|
||||
const values = transactionGroups
|
||||
.map(({ impact }) => impact)
|
||||
.filter(value => value !== null) as number[];
|
||||
|
||||
const max = Math.max(...values);
|
||||
const min = Math.min(...values);
|
||||
|
||||
return transactionGroups.map(bucket => ({
|
||||
...bucket,
|
||||
impact: ((bucket.impact - min) / (max - min)) * 100 || 0
|
||||
impact:
|
||||
bucket.impact !== null
|
||||
? ((bucket.impact - min) / (max - min)) * 100 || 0
|
||||
: 0
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -27,7 +34,7 @@ function getTransactionGroup(
|
|||
const averageResponseTime = bucket.avg.value;
|
||||
const transactionsPerMinute = bucket.doc_count / minutes;
|
||||
const impact = bucket.sum.value;
|
||||
const sample = bucket.sample.hits.hits[0]._source;
|
||||
const sample = bucket.sample.hits.hits[0]._source as Transaction;
|
||||
|
||||
return {
|
||||
name: bucket.key,
|
||||
|
|
|
@ -8,28 +8,11 @@ import { getMlIndex } from '../../../../../common/ml_job_constants';
|
|||
import { PromiseReturnType } from '../../../../../typings/common';
|
||||
import { Setup } from '../../../helpers/setup_request';
|
||||
|
||||
export interface ESBucket {
|
||||
key_as_string: string; // timestamp as string
|
||||
key: number; // timestamp
|
||||
doc_count: number;
|
||||
anomaly_score: {
|
||||
value: number | null;
|
||||
};
|
||||
lower: {
|
||||
value: number | null;
|
||||
};
|
||||
upper: {
|
||||
value: number | null;
|
||||
};
|
||||
}
|
||||
export type ESResponse = Exclude<
|
||||
PromiseReturnType<typeof anomalySeriesFetcher>,
|
||||
undefined
|
||||
>;
|
||||
|
||||
interface Aggs {
|
||||
ml_avg_response_times: {
|
||||
buckets: ESBucket[];
|
||||
};
|
||||
}
|
||||
|
||||
export type ESResponse = PromiseReturnType<typeof anomalySeriesFetcher>;
|
||||
export async function anomalySeriesFetcher({
|
||||
serviceName,
|
||||
transactionType,
|
||||
|
@ -91,7 +74,8 @@ export async function anomalySeriesFetcher({
|
|||
};
|
||||
|
||||
try {
|
||||
return await client.search<void, Aggs>(params);
|
||||
const response = await client.search(params);
|
||||
return response;
|
||||
} catch (err) {
|
||||
const isHttpError = 'statusCode' in err;
|
||||
if (isHttpError) {
|
||||
|
|
|
@ -55,10 +55,12 @@ export async function getAnomalySeries({
|
|||
setup
|
||||
});
|
||||
|
||||
return anomalySeriesTransform(
|
||||
esResponse,
|
||||
mlBucketSize,
|
||||
bucketSize,
|
||||
timeSeriesDates
|
||||
);
|
||||
return esResponse
|
||||
? anomalySeriesTransform(
|
||||
esResponse,
|
||||
mlBucketSize,
|
||||
bucketSize,
|
||||
timeSeriesDates
|
||||
)
|
||||
: undefined;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { ESResponse } from '../fetcher';
|
||||
|
||||
export const mlAnomalyResponse: ESResponse = {
|
||||
export const mlAnomalyResponse: ESResponse = ({
|
||||
took: 3,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
|
@ -124,4 +124,4 @@ export const mlAnomalyResponse: ESResponse = {
|
|||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
} as unknown) as ESResponse;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { ESBucket, ESResponse } from './fetcher';
|
||||
import { ESResponse } from './fetcher';
|
||||
import { mlAnomalyResponse } from './mock-responses/mlAnomalyResponse';
|
||||
import { anomalySeriesTransform, replaceFirstAndLastBucket } from './transform';
|
||||
|
||||
|
@ -46,7 +46,7 @@ describe('anomalySeriesTransform', () => {
|
|||
key: 20000,
|
||||
anomaly_score: { value: 90 }
|
||||
}
|
||||
] as ESBucket[]);
|
||||
]);
|
||||
|
||||
const getMlBucketSize = 5;
|
||||
const bucketSize = 5;
|
||||
|
@ -72,7 +72,7 @@ describe('anomalySeriesTransform', () => {
|
|||
key: 5000,
|
||||
anomaly_score: { value: 90 }
|
||||
}
|
||||
] as ESBucket[]);
|
||||
]);
|
||||
|
||||
const getMlBucketSize = 10;
|
||||
const bucketSize = 5;
|
||||
|
@ -112,7 +112,7 @@ describe('anomalySeriesTransform', () => {
|
|||
upper: { value: 45 },
|
||||
lower: { value: 40 }
|
||||
}
|
||||
] as ESBucket[]);
|
||||
]);
|
||||
|
||||
const mlBucketSize = 10;
|
||||
const bucketSize = 5;
|
||||
|
@ -151,7 +151,7 @@ describe('anomalySeriesTransform', () => {
|
|||
upper: { value: 25 },
|
||||
lower: { value: 20 }
|
||||
}
|
||||
] as ESBucket[]);
|
||||
]);
|
||||
|
||||
const getMlBucketSize = 10;
|
||||
const bucketSize = 5;
|
||||
|
@ -190,7 +190,7 @@ describe('anomalySeriesTransform', () => {
|
|||
upper: { value: null },
|
||||
lower: { value: null }
|
||||
}
|
||||
] as ESBucket[]);
|
||||
]);
|
||||
|
||||
const getMlBucketSize = 10;
|
||||
const bucketSize = 5;
|
||||
|
@ -234,10 +234,10 @@ describe('replaceFirstAndLastBucket', () => {
|
|||
lower: 30,
|
||||
upper: 40
|
||||
}
|
||||
] as any;
|
||||
];
|
||||
|
||||
const timeSeriesDates = [10, 15];
|
||||
expect(replaceFirstAndLastBucket(buckets, timeSeriesDates)).toEqual([
|
||||
expect(replaceFirstAndLastBucket(buckets as any, timeSeriesDates)).toEqual([
|
||||
{ x: 10, lower: 10, upper: 20 },
|
||||
{ x: 15, lower: 30, upper: 40 }
|
||||
]);
|
||||
|
@ -271,8 +271,8 @@ describe('replaceFirstAndLastBucket', () => {
|
|||
});
|
||||
});
|
||||
|
||||
function getESResponse(buckets: ESBucket[]): ESResponse {
|
||||
return {
|
||||
function getESResponse(buckets: any): ESResponse {
|
||||
return ({
|
||||
took: 3,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
|
@ -288,7 +288,7 @@ function getESResponse(buckets: ESBucket[]): ESResponse {
|
|||
},
|
||||
aggregations: {
|
||||
ml_avg_response_times: {
|
||||
buckets: buckets.map(bucket => {
|
||||
buckets: buckets.map((bucket: any) => {
|
||||
return {
|
||||
...bucket,
|
||||
lower: { value: idx(bucket, _ => _.lower.value) || null },
|
||||
|
@ -300,5 +300,5 @@ function getESResponse(buckets: ESBucket[]): ESResponse {
|
|||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
} as unknown) as ESResponse;
|
||||
}
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
import { first, last } from 'lodash';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { Coordinate, RectCoordinate } from '../../../../../typings/timeseries';
|
||||
import { ESBucket, ESResponse } from './fetcher';
|
||||
import { ESResponse } from './fetcher';
|
||||
|
||||
type IBucket = ReturnType<typeof getBucket>;
|
||||
function getBucket(bucket: ESBucket) {
|
||||
function getBucket(
|
||||
bucket: ESResponse['aggregations']['ml_avg_response_times']['buckets'][0]
|
||||
) {
|
||||
return {
|
||||
x: bucket.key,
|
||||
anomalyScore: bucket.anomaly_score.value,
|
||||
|
@ -28,10 +30,6 @@ export function anomalySeriesTransform(
|
|||
bucketSize: number,
|
||||
timeSeriesDates: number[]
|
||||
) {
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buckets = (
|
||||
idx(response, _ => _.aggregations.ml_avg_response_times.buckets) || []
|
||||
).map(getBucket);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ESFilter, SearchParams } from 'elasticsearch';
|
||||
import { ESFilter } from 'elasticsearch';
|
||||
import {
|
||||
PROCESSOR_EVENT,
|
||||
SERVICE_NAME,
|
||||
|
@ -18,53 +18,6 @@ import { getBucketSize } from '../../../helpers/get_bucket_size';
|
|||
import { rangeFilter } from '../../../helpers/range_filter';
|
||||
import { Setup } from '../../../helpers/setup_request';
|
||||
|
||||
interface ResponseTimeBucket {
|
||||
key_as_string: string;
|
||||
key: number;
|
||||
doc_count: number;
|
||||
avg: {
|
||||
value: number | null;
|
||||
};
|
||||
pct: {
|
||||
values: {
|
||||
'95.0': number | 'NaN';
|
||||
'99.0': number | 'NaN';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface TransactionResultBucket {
|
||||
/**
|
||||
* transaction result eg. 2xx
|
||||
*/
|
||||
key: string;
|
||||
doc_count: number;
|
||||
timeseries: {
|
||||
buckets: Array<{
|
||||
key_as_string: string;
|
||||
/**
|
||||
* timestamp in ms
|
||||
*/
|
||||
key: number;
|
||||
doc_count: number;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
interface Aggs {
|
||||
response_times: {
|
||||
buckets: ResponseTimeBucket[];
|
||||
};
|
||||
transaction_results: {
|
||||
doc_count_error_upper_bound: number;
|
||||
sum_other_doc_count: number;
|
||||
buckets: TransactionResultBucket[];
|
||||
};
|
||||
overall_avg_duration: {
|
||||
value: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type ESResponse = PromiseReturnType<typeof timeseriesFetcher>;
|
||||
export function timeseriesFetcher({
|
||||
serviceName,
|
||||
|
@ -96,8 +49,8 @@ export function timeseriesFetcher({
|
|||
filter.push({ term: { [TRANSACTION_TYPE]: transactionType } });
|
||||
}
|
||||
|
||||
const params: SearchParams = {
|
||||
index: config.get('apm_oss.transactionIndices'),
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.transactionIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
query: { bool: { filter } },
|
||||
|
@ -134,5 +87,5 @@ export function timeseriesFetcher({
|
|||
}
|
||||
};
|
||||
|
||||
return client.search<void, Aggs>(params);
|
||||
return client.search(params);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { ESResponse } from '../fetcher';
|
||||
|
||||
export const timeseriesResponse: ESResponse = {
|
||||
export const timeseriesResponse = ({
|
||||
took: 368,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
|
@ -2826,4 +2826,4 @@ export const timeseriesResponse: ESResponse = {
|
|||
value: 32861.15660262639
|
||||
}
|
||||
}
|
||||
};
|
||||
} as unknown) as ESResponse;
|
||||
|
|
|
@ -96,7 +96,7 @@ describe('getTpmBuckets', () => {
|
|||
}
|
||||
];
|
||||
const bucketSize = 10;
|
||||
expect(getTpmBuckets(buckets, bucketSize)).toEqual([
|
||||
expect(getTpmBuckets(buckets as any, bucketSize)).toEqual([
|
||||
{
|
||||
dataPoints: [
|
||||
{ x: 0, y: 0 },
|
||||
|
|
|
@ -10,19 +10,7 @@ import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
|
|||
import { Coordinate } from '../../../../../typings/timeseries';
|
||||
import { ESResponse } from './fetcher';
|
||||
|
||||
export interface ApmTimeSeriesResponse {
|
||||
totalHits: number;
|
||||
responseTimes: {
|
||||
avg: Coordinate[];
|
||||
p95: Coordinate[];
|
||||
p99: Coordinate[];
|
||||
};
|
||||
tpmBuckets: Array<{
|
||||
key: string;
|
||||
dataPoints: Coordinate[];
|
||||
}>;
|
||||
overallAvgDuration?: number;
|
||||
}
|
||||
export type ApmTimeSeriesResponse = ReturnType<typeof timeseriesTransformer>;
|
||||
|
||||
export function timeseriesTransformer({
|
||||
timeseriesResponse,
|
||||
|
@ -30,7 +18,7 @@ export function timeseriesTransformer({
|
|||
}: {
|
||||
timeseriesResponse: ESResponse;
|
||||
bucketSize: number;
|
||||
}): ApmTimeSeriesResponse {
|
||||
}) {
|
||||
const aggs = timeseriesResponse.aggregations;
|
||||
const overallAvgDuration = idx(aggs, _ => _.overall_avg_duration.value);
|
||||
const responseTimeBuckets = idx(aggs, _ => _.response_times.buckets);
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SearchParams } from 'elasticsearch';
|
||||
import {
|
||||
PROCESSOR_EVENT,
|
||||
SERVICE_NAME,
|
||||
|
@ -22,8 +21,8 @@ export async function calculateBucketSize(
|
|||
) {
|
||||
const { start, end, uiFiltersES, client, config } = setup;
|
||||
|
||||
const params: SearchParams = {
|
||||
index: config.get('apm_oss.transactionIndices'),
|
||||
const params = {
|
||||
index: config.get<string>('apm_oss.transactionIndices'),
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
|
@ -56,13 +55,8 @@ export async function calculateBucketSize(
|
|||
}
|
||||
};
|
||||
|
||||
interface Aggs {
|
||||
stats: {
|
||||
max: number;
|
||||
};
|
||||
}
|
||||
const resp = await client.search(params);
|
||||
|
||||
const resp = await client.search<void, Aggs>(params);
|
||||
const minBucketSize: number = config.get('xpack.apm.minimumBucketSize');
|
||||
const bucketTargetCount: number = config.get('xpack.apm.bucketTargetCount');
|
||||
const max = resp.aggregations.stats.max;
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import {
|
||||
PROCESSOR_EVENT,
|
||||
SERVICE_NAME,
|
||||
|
@ -15,29 +14,9 @@ import {
|
|||
TRANSACTION_SAMPLED,
|
||||
TRANSACTION_TYPE
|
||||
} from '../../../../../common/elasticsearch_fieldnames';
|
||||
import { PromiseReturnType } from '../../../../../typings/common';
|
||||
import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction';
|
||||
import { rangeFilter } from '../../../helpers/range_filter';
|
||||
import { Setup } from '../../../helpers/setup_request';
|
||||
|
||||
interface Bucket {
|
||||
key: number;
|
||||
doc_count: number;
|
||||
sample: SearchResponse<{
|
||||
transaction: Pick<Transaction['transaction'], 'id' | 'sampled'>;
|
||||
trace: {
|
||||
id: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
interface Aggs {
|
||||
distribution: {
|
||||
buckets: Bucket[];
|
||||
};
|
||||
}
|
||||
|
||||
export type ESResponse = PromiseReturnType<typeof bucketFetcher>;
|
||||
export function bucketFetcher(
|
||||
serviceName: string,
|
||||
transactionName: string,
|
||||
|
@ -95,5 +74,5 @@ export function bucketFetcher(
|
|||
}
|
||||
};
|
||||
|
||||
return client.search<void, Aggs>(params);
|
||||
return client.search(params);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
|
||||
import { isEmpty } from 'lodash';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { ESResponse } from './fetcher';
|
||||
import { PromiseReturnType } from '../../../../../typings/common';
|
||||
import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction';
|
||||
import { bucketFetcher } from './fetcher';
|
||||
|
||||
type DistributionBucketResponse = PromiseReturnType<typeof bucketFetcher>;
|
||||
|
||||
function getDefaultSample(buckets: IBucket[]) {
|
||||
const samples = buckets
|
||||
|
@ -23,9 +27,12 @@ function getDefaultSample(buckets: IBucket[]) {
|
|||
|
||||
export type IBucket = ReturnType<typeof getBucket>;
|
||||
function getBucket(
|
||||
bucket: ESResponse['aggregations']['distribution']['buckets'][0]
|
||||
bucket: DistributionBucketResponse['aggregations']['distribution']['buckets'][0]
|
||||
) {
|
||||
const sampleSource = idx(bucket, _ => _.sample.hits.hits[0]._source);
|
||||
const sampleSource = idx(bucket, _ => _.sample.hits.hits[0]._source) as
|
||||
| Transaction
|
||||
| undefined;
|
||||
|
||||
const isSampled = idx(sampleSource, _ => _.transaction.sampled);
|
||||
const sample = {
|
||||
traceId: idx(sampleSource, _ => _.trace.id),
|
||||
|
@ -39,7 +46,7 @@ function getBucket(
|
|||
};
|
||||
}
|
||||
|
||||
export function bucketTransformer(response: ESResponse) {
|
||||
export function bucketTransformer(response: DistributionBucketResponse) {
|
||||
const buckets = response.aggregations.distribution.buckets.map(getBucket);
|
||||
|
||||
return {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { BucketAgg, ESFilter } from 'elasticsearch';
|
||||
import { ESFilter } from 'elasticsearch';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import {
|
||||
PROCESSOR_EVENT,
|
||||
|
@ -57,13 +57,7 @@ export async function getEnvironments(setup: Setup, serviceName?: string) {
|
|||
}
|
||||
};
|
||||
|
||||
interface Aggs extends BucketAgg {
|
||||
environments: {
|
||||
buckets: BucketAgg[];
|
||||
};
|
||||
}
|
||||
|
||||
const resp = await client.search<void, Aggs>(params);
|
||||
const resp = await client.search(params);
|
||||
const aggs = resp.aggregations;
|
||||
const environmentsBuckets = idx(aggs, _ => _.environments.buckets) || [];
|
||||
|
||||
|
|
|
@ -20,3 +20,9 @@ export type PromiseReturnType<Func> = Func extends (
|
|||
) => Promise<infer Value>
|
||||
? Value
|
||||
: Func;
|
||||
|
||||
export type IndexAsString<Map> = {
|
||||
[k: string]: Map[keyof Map];
|
||||
} & Map;
|
||||
|
||||
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
|
|
@ -4,24 +4,114 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { StringMap } from './common';
|
||||
import { StringMap, IndexAsString } from './common';
|
||||
|
||||
declare module 'elasticsearch' {
|
||||
// extending SearchResponse to be able to have typed aggregations
|
||||
export interface AggregationSearchResponse<Hits = unknown, Aggs = unknown>
|
||||
extends SearchResponse<Hits> {
|
||||
aggregations: Aggs;
|
||||
|
||||
type AggregationType =
|
||||
| 'date_histogram'
|
||||
| 'histogram'
|
||||
| 'terms'
|
||||
| 'avg'
|
||||
| 'top_hits'
|
||||
| 'max'
|
||||
| 'min'
|
||||
| 'percentiles'
|
||||
| 'sum'
|
||||
| 'extended_stats';
|
||||
|
||||
type AggOptions = AggregationOptionMap & {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-interface
|
||||
export type AggregationOptionMap = {
|
||||
aggs?: {
|
||||
[aggregationName: string]: {
|
||||
[T in AggregationType]?: AggOptions & AggregationOptionMap
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-interface
|
||||
type BucketAggregation<SubAggregationMap, KeyType = string> = {
|
||||
buckets: Array<
|
||||
{
|
||||
key: KeyType;
|
||||
key_as_string: string;
|
||||
doc_count: number;
|
||||
} & (SubAggregationMap extends { aggs: any }
|
||||
? AggregationResultMap<SubAggregationMap['aggs']>
|
||||
: {})
|
||||
>;
|
||||
};
|
||||
|
||||
interface AggregatedValue {
|
||||
value: number | null;
|
||||
}
|
||||
|
||||
export interface BucketAgg<T = string> {
|
||||
key: T;
|
||||
doc_count: number;
|
||||
}
|
||||
type AggregationResultMap<AggregationOption> = IndexAsString<
|
||||
{
|
||||
[AggregationName in keyof AggregationOption]: {
|
||||
avg: AggregatedValue;
|
||||
max: AggregatedValue;
|
||||
min: AggregatedValue;
|
||||
sum: AggregatedValue;
|
||||
terms: BucketAggregation<AggregationOption[AggregationName]>;
|
||||
date_histogram: BucketAggregation<
|
||||
AggregationOption[AggregationName],
|
||||
number
|
||||
>;
|
||||
histogram: BucketAggregation<
|
||||
AggregationOption[AggregationName],
|
||||
number
|
||||
>;
|
||||
top_hits: {
|
||||
hits: {
|
||||
total: number;
|
||||
max_score: number | null;
|
||||
hits: Array<{
|
||||
_source: AggregationOption[AggregationName] extends {
|
||||
Mapping: any;
|
||||
}
|
||||
? AggregationOption[AggregationName]['Mapping']
|
||||
: never;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
percentiles: {
|
||||
values: {
|
||||
[key: string]: number;
|
||||
};
|
||||
};
|
||||
extended_stats: {
|
||||
count: number;
|
||||
min: number;
|
||||
max: number;
|
||||
avg: number;
|
||||
sum: number;
|
||||
sum_of_squares: number;
|
||||
variance: number;
|
||||
std_deviation: number;
|
||||
std_deviation_bounds: {
|
||||
upper: number;
|
||||
lower: number;
|
||||
};
|
||||
};
|
||||
}[AggregationType & keyof AggregationOption[AggregationName]]
|
||||
}
|
||||
>;
|
||||
|
||||
export interface TermsAggsBucket {
|
||||
key: string;
|
||||
doc_count: number;
|
||||
}
|
||||
export type AggregationSearchResponse<HitType, SearchParams> = Pick<
|
||||
SearchResponse<HitType>,
|
||||
Exclude<keyof SearchResponse<HitType>, 'aggregations'>
|
||||
> &
|
||||
(SearchParams extends { body: Required<AggregationOptionMap> }
|
||||
? {
|
||||
aggregations: AggregationResultMap<SearchParams['body']['aggs']>;
|
||||
}
|
||||
: {});
|
||||
|
||||
export interface ESFilter {
|
||||
[key: string]: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue