Skip to content

Commit

Permalink
[APM] Fallback to terms agg search if terms enum doesn’t return resul…
Browse files Browse the repository at this point in the history
…ts (#143619)

* [APM] Fallback to terms agg search if terms enum doesn’t return results

* Add api test for suggestions
  • Loading branch information
sorenlouv authored Oct 19, 2022
1 parent 3d33abe commit 1def8b5
Show file tree
Hide file tree
Showing 5 changed files with 386 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function getSuggestionsWithTermsAggregation({
fieldName: string;
fieldValue: string;
searchAggregatedTransactions: boolean;
serviceName: string;
serviceName?: string;
setup: Setup;
size: number;
start: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ProcessorEvent } from '@kbn/observability-plugin/common';
import { getProcessorEventForTransactions } from '../../lib/helpers/transactions';
import { Setup } from '../../lib/helpers/setup_request';

export async function getSuggestions({
export async function getSuggestionsWithTermsEnum({
fieldName,
fieldValue,
searchAggregatedTransactions,
Expand All @@ -27,30 +27,33 @@ export async function getSuggestions({
}) {
const { apmEventClient } = setup;

const response = await apmEventClient.termsEnum('get_suggestions', {
apm: {
events: [
getProcessorEventForTransactions(searchAggregatedTransactions),
ProcessorEvent.error,
ProcessorEvent.metric,
],
},
body: {
case_insensitive: true,
field: fieldName,
size,
string: fieldValue,
index_filter: {
range: {
['@timestamp']: {
gte: start,
lte: end,
format: 'epoch_millis',
const response = await apmEventClient.termsEnum(
'get_suggestions_with_terms_enum',
{
apm: {
events: [
getProcessorEventForTransactions(searchAggregatedTransactions),
ProcessorEvent.error,
ProcessorEvent.metric,
],
},
body: {
case_insensitive: true,
field: fieldName,
size,
string: fieldValue,
index_filter: {
range: {
['@timestamp']: {
gte: start,
lte: end,
format: 'epoch_millis',
},
},
},
},
},
});
}
);

return { terms: response.terms };
}
51 changes: 29 additions & 22 deletions x-pack/plugins/apm/server/routes/suggestions/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import * as t from 'io-ts';
import { maxSuggestions } from '@kbn/observability-plugin/common';
import { getSuggestions } from './get_suggestions';
import { getSuggestionsWithTermsEnum } from './get_suggestions_with_terms_enum';
import { getSuggestionsWithTermsAggregation } from './get_suggestions_with_terms_aggregation';
import { getSearchTransactionsEvents } from '../../lib/helpers/transactions';
import { setupRequest } from '../../lib/helpers/setup_request';
Expand Down Expand Up @@ -41,28 +41,35 @@ const suggestionsRoute = createApmServerRoute({
maxSuggestions
);

const suggestions = serviceName
? await getSuggestionsWithTermsAggregation({
fieldName,
fieldValue,
searchAggregatedTransactions,
serviceName,
setup,
size,
start,
end,
})
: await getSuggestions({
fieldName,
fieldValue,
searchAggregatedTransactions,
setup,
size,
start,
end,
});
if (!serviceName) {
const suggestions = await getSuggestionsWithTermsEnum({
fieldName,
fieldValue,
searchAggregatedTransactions,
setup,
size,
start,
end,
});

return suggestions;
// if no terms are found using terms enum it will fall back to using ordinary terms agg search
// This is useful because terms enum can only find terms that start with the search query
// whereas terms agg approach can find terms that contain the search query
if (suggestions.terms.length > 0) {
return suggestions;
}
}

return getSuggestionsWithTermsAggregation({
fieldName,
fieldValue,
searchAggregatedTransactions,
serviceName,
setup,
size,
start,
end,
});
},
});

Expand Down
77 changes: 77 additions & 0 deletions x-pack/test/apm_api_integration/tests/suggestions/generate_data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { apm, timerange } from '@kbn/apm-synthtrace';
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
import { times } from 'lodash';

export async function generateData({
synthtraceEsClient,
start,
end,
}: {
synthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
const services = times(5).flatMap((serviceId) => {
return ['go', 'java'].flatMap((agentName) => {
return ['production', 'development', 'staging'].flatMap((environment) => {
return times(5).flatMap((envId) => {
const service = apm
.service({
name: `${agentName}-${serviceId}`,
environment: `${environment}-${envId}`,
agentName,
})
.instance('instance-a');

return service;
});
});
});
});

const transactionNames = [
'GET /api/product/:id',
'PUT /api/product/:id',
'GET /api/user/:id',
'PUT /api/user/:id',
];

const phpService = apm
.service({
name: `custom-php-service`,
environment: `custom-php-environment`,
agentName: 'php',
})
.instance('instance-a');

const docs = timerange(start, end)
.ratePerMinute(1)
.generator((timestamp) => {
const autoGeneratedDocs = services.flatMap((service) => {
return transactionNames.flatMap((transactionName) => {
return service
.transaction({ transactionName, transactionType: 'my-custom-type' })
.timestamp(timestamp)
.duration(1000);
});
});

const customDoc = phpService
.transaction({
transactionName: 'GET /api/php/memory',
transactionType: 'custom-php-type',
})
.timestamp(timestamp)
.duration(1000);

return [...autoGeneratedDocs, customDoc];
});

return await synthtraceEsClient.index(docs);
}
Loading

0 comments on commit 1def8b5

Please sign in to comment.