diff --git a/src/plugins/profiling/public/app.tsx b/src/plugins/profiling/public/app.tsx index 1b03fa4ef9d87..7aace0a4cd7f9 100644 --- a/src/plugins/profiling/public/app.tsx +++ b/src/plugins/profiling/public/app.tsx @@ -78,7 +78,7 @@ function App({ fetchTopN, fetchElasticFlamechart, fetchPixiFlamechart }: Props) - + ), diff --git a/src/plugins/profiling/public/services.ts b/src/plugins/profiling/public/services.ts index 5b2033b815083..31911d6b6451b 100644 --- a/src/plugins/profiling/public/services.ts +++ b/src/plugins/profiling/public/services.ts @@ -20,8 +20,10 @@ function getFetchQuery(seconds: string): HttpFetchQuery { return { index: 'profiling-events', projectID: 5, - timeFrom: unixTime - parseInt(seconds), + timeFrom: unixTime - parseInt(seconds, 10), timeTo: unixTime, + // TODO remove hard-coded value for topN items length and expose it through the UI + n: 100, } as HttpFetchQuery; } @@ -33,11 +35,7 @@ export function getServices(core: CoreStart): Services { fetchTopN: async (type: string, seconds: string) => { try { const query = getFetchQuery(seconds); - const response = await core.http.get( - `${paths.TopN}/${type}`, - { query } - ); - return response; + return await core.http.get(`${paths.TopN}/${type}`, { query }); } catch (e) { return e; } @@ -46,11 +44,7 @@ export function getServices(core: CoreStart): Services { fetchElasticFlamechart: async (seconds: string) => { try { const query = getFetchQuery(seconds); - const response = await core.http.get( - paths.FlamechartElastic, - { query } - ); - return response; + return await core.http.get(paths.FlamechartElastic, { query }); } catch (e) { return e; } @@ -59,11 +53,7 @@ export function getServices(core: CoreStart): Services { fetchPixiFlamechart: async (seconds: string) => { try { const query = getFetchQuery(seconds); - const response = await core.http.get( - paths.FlamechartPixi, - { query } - ); - return response; + return await core.http.get(paths.FlamechartPixi, { query }); } catch (e) { return e; } diff --git a/src/plugins/profiling/server/routes/mappings.ts b/src/plugins/profiling/server/routes/mappings.ts index 5c10bd4f8fed0..fb67a2a747c60 100644 --- a/src/plugins/profiling/server/routes/mappings.ts +++ b/src/plugins/profiling/server/routes/mappings.ts @@ -10,13 +10,10 @@ import { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api interface ProjectTimeQuery { bool: { - must: Array< + filter: Array< | { term: { - ProjectID: { - value: string; - boost: number; - }; + ProjectID: string; }; } | { @@ -40,13 +37,10 @@ export function newProjectTimeQuery( ): ProjectTimeQuery { return { bool: { - must: [ + filter: [ { term: { - ProjectID: { - value: projectID, - boost: 1.0, - }, + ProjectID: projectID, }, }, { @@ -65,21 +59,23 @@ export function newProjectTimeQuery( } export function autoHistogramSumCountOnGroupByField( - searchField: string + searchField: string, + topNItems: number ): AggregationsAggregationContainer { return { auto_date_histogram: { field: '@timestamp', - buckets: 100, + buckets: 50, }, aggs: { group_by: { terms: { field: searchField, - order: { - Count: 'desc', - }, - size: 100, + // We remove the ordering since we will rely directly on the natural + // ordering of Elasticsearch: by default this will be the descending count + // of matched documents. This is not equal to the ordering by sum of Count field, + // but it's a good-enough approximation given the distribution of Count. + size: topNItems, }, aggs: { Count: { diff --git a/src/plugins/profiling/server/routes/search_topn.test.ts b/src/plugins/profiling/server/routes/search_topn.test.ts index fee6fdeff2735..80c412a4c4cde 100644 --- a/src/plugins/profiling/server/routes/search_topn.test.ts +++ b/src/plugins/profiling/server/routes/search_topn.test.ts @@ -9,7 +9,13 @@ import { topNElasticSearchQuery } from './search_topn'; import { DataRequestHandlerContext } from '../../../data/server'; import { kibanaResponseFactory } from '../../../../core/server'; -import { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/types'; +import { + AggregationsAggregationContainer, + AggregationsHistogramAggregate, + AggregationsHistogramBucket, + AggregationsMultiBucketAggregateBase, + AggregationsStringTermsBucket, +} from '@elastic/elasticsearch/lib/api/types'; const anyQuery = 'any::query'; const index = 'test'; @@ -36,23 +42,23 @@ function mockTopNData() { histogram: { buckets: [ { - key_as_string: '1644506880', - key: 1644506880000, - doc_count: 700, + key_as_string: '123', + key: 123000, + doc_count: 10, group_by: { buckets: [ { - key: 'vyHke_Kdp2c05tXV7a_Rkg==', + key: '::any::key::', doc_count: 10, Count: { value: 100.0, }, - }, + } as AggregationsStringTermsBucket, ], - }, - }, + } as AggregationsMultiBucketAggregateBase, + } as AggregationsHistogramBucket, ], - }, + } as AggregationsHistogramAggregate, }, }, }), diff --git a/src/plugins/profiling/server/routes/search_topn.ts b/src/plugins/profiling/server/routes/search_topn.ts index 62b1d65df6e6b..dc8e0b5f10faa 100644 --- a/src/plugins/profiling/server/routes/search_topn.ts +++ b/src/plugins/profiling/server/routes/search_topn.ts @@ -8,12 +8,13 @@ import { schema } from '@kbn/config-schema'; import type { IRouter, KibanaResponseFactory } from 'kibana/server'; import { + AggregationsHistogramAggregate, AggregationsHistogramBucket, - AggregationsMultiBucketAggregateBase, + AggregationsStringTermsBucket, } from '@elastic/elasticsearch/lib/api/types'; import type { DataRequestHandlerContext } from '../../../data/server'; import { getRemoteRoutePaths } from '../../common'; -import { newProjectTimeQuery, autoHistogramSumCountOnGroupByField } from './mappings'; +import { autoHistogramSumCountOnGroupByField, newProjectTimeQuery } from './mappings'; export async function topNElasticSearchQuery( context: DataRequestHandlerContext, @@ -21,6 +22,7 @@ export async function topNElasticSearchQuery( projectID: string, timeFrom: string, timeTo: string, + topNItems: number, searchField: string, response: KibanaResponseFactory ) { @@ -30,23 +32,22 @@ export async function topNElasticSearchQuery( body: { query: newProjectTimeQuery(projectID, timeFrom, timeTo), aggs: { - histogram: autoHistogramSumCountOnGroupByField(searchField), + histogram: autoHistogramSumCountOnGroupByField(searchField, topNItems), }, }, }); if (searchField === 'StackTraceID') { - const autoDateHistogram = resTopNStackTraces.body.aggregations - ?.histogram as AggregationsMultiBucketAggregateBase; - const docIDs: string[] = []; - autoDateHistogram.buckets?.forEach((timeInterval: any) => { - timeInterval.group_by.buckets.forEach((stackTraceItem: any) => { + ( + resTopNStackTraces.body.aggregations?.histogram as AggregationsHistogramAggregate + ).buckets.forEach((timeInterval: AggregationsHistogramBucket) => { + timeInterval.group_by.buckets.forEach((stackTraceItem: AggregationsStringTermsBucket) => { docIDs.push(stackTraceItem.key); }); }); - const resTraceMetadata = await esClient.mget({ + const resTraceMetadata = await esClient.mget({ index: 'profiling-stacktraces', body: { ids: docIDs }, }); @@ -76,23 +77,25 @@ export function queryTopNCommon( path: pathName, validate: { query: schema.object({ - index: schema.maybe(schema.string()), - projectID: schema.maybe(schema.string()), - timeFrom: schema.maybe(schema.string()), - timeTo: schema.maybe(schema.string()), + index: schema.string(), + projectID: schema.string(), + timeFrom: schema.string(), + timeTo: schema.string(), + n: schema.number(), }), }, }, async (context, request, response) => { - const { index, projectID, timeFrom, timeTo } = request.query; + const { index, projectID, timeFrom, timeTo, n } = request.query; try { return await topNElasticSearchQuery( context, - index!, - projectID!, - timeFrom!, - timeTo!, + index, + projectID, + timeFrom, + timeTo, + n, searchField, response );