[8.12] [APM] Add filter to /has_data api (#173382) (#173559)

# Backport

This will backport the following commits from `main` to `8.12`:
- [[APM] Add filter to `/has_data` api
(#173382)](https://github.com/elastic/kibana/pull/173382)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Søren
Louv-Jansen","email":"soren.louv@elastic.co"},"sourceCommit":{"committedDate":"2023-12-18T18:18:03Z","message":"[APM]
Add filter to `/has_data` api (#173382)\n\nCloses
https://github.com/elastic/kibana/issues/154997\r\n\r\nThis PR adds a
data tier filter to the `/has_data` api, thus limitting\r\nthe number of
shards being hit by the
request.","sha":"e7593c0e46f1ce707c1b951f8d013e722ac79353","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","v8.12.0","v8.10.5","Team:obs-ux-infra_services","v8.13.0","v8.11.4"],"number":173382,"url":"https://github.com/elastic/kibana/pull/173382","mergeCommit":{"message":"[APM]
Add filter to `/has_data` api (#173382)\n\nCloses
https://github.com/elastic/kibana/issues/154997\r\n\r\nThis PR adds a
data tier filter to the `/has_data` api, thus limitting\r\nthe number of
shards being hit by the
request.","sha":"e7593c0e46f1ce707c1b951f8d013e722ac79353"}},"sourceBranch":"main","suggestedTargetBranches":["8.12","8.10","8.11"],"targetPullRequestStates":[{"branch":"8.12","label":"v8.12.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.10","label":"v8.10.5","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.13.0","labelRegex":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/173382","number":173382,"mergeCommit":{"message":"[APM]
Add filter to `/has_data` api (#173382)\n\nCloses
https://github.com/elastic/kibana/issues/154997\r\n\r\nThis PR adds a
data tier filter to the `/has_data` api, thus limitting\r\nthe number of
shards being hit by the
request.","sha":"e7593c0e46f1ce707c1b951f8d013e722ac79353"}},{"branch":"8.11","label":"v8.11.4","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Søren Louv-Jansen <soren.louv@elastic.co>
This commit is contained in:
Kibana Machine 2023-12-18 16:15:39 -05:00 committed by GitHub
parent 34f6840f54
commit 23406d2a08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 420 deletions

View file

@ -8,10 +8,29 @@
import { ProcessorEvent } from '@kbn/observability-plugin/common';
import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
// Note: this logic is duplicated in tutorials/apm/envs/on_prem
export async function hasHistoricalAgentData(apmEventClient: APMEventClient) {
const hasDataInWarmOrHotDataTiers = await hasDataRequest(apmEventClient, [
'data_hot',
'data_warm',
]);
if (hasDataInWarmOrHotDataTiers) {
return true;
}
const hasDataUnbounded = await hasDataRequest(apmEventClient);
return hasDataUnbounded;
}
type DataTier = 'data_hot' | 'data_warm' | 'data_cold' | 'data_frozen';
async function hasDataRequest(
apmEventClient: APMEventClient,
dataTiers?: DataTier[]
) {
const query = dataTiers ? { terms: { _tier: dataTiers } } : undefined;
const params = {
terminate_after: 1,
apm: {
events: [
ProcessorEvent.error,
@ -20,8 +39,10 @@ export async function hasHistoricalAgentData(apmEventClient: APMEventClient) {
],
},
body: {
terminate_after: 1,
track_total_hits: 1,
size: 0,
query,
},
};

View file

@ -1,313 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`services queries fetches the agent status 1`] = `
Object {
"apm": Object {
"events": Array [
"error",
"metric",
"transaction",
],
},
"body": Object {
"size": 0,
"track_total_hits": 1,
},
"terminate_after": 1,
}
`;
exports[`services queries fetches the service agent name 1`] = `
Object {
"apm": Object {
"events": Array [
"error",
"transaction",
"metric",
],
},
"body": Object {
"_source": Array [
"agent.name",
"service.runtime.name",
"cloud.provider",
"cloud.service.name",
],
"query": Object {
"bool": Object {
"filter": Array [
Object {
"term": Object {
"service.name": "foo",
},
},
Object {
"range": Object {
"@timestamp": Object {
"format": "epoch_millis",
"gte": 0,
"lte": 50000,
},
},
},
Object {
"exists": Object {
"field": "agent.name",
},
},
],
"should": Array [
Object {
"exists": Object {
"field": "service.runtime.name",
},
},
Object {
"exists": Object {
"field": "cloud.provider",
},
},
Object {
"exists": Object {
"field": "cloud.service.name",
},
},
],
},
},
"size": 1,
"sort": Object {
"_score": Object {
"order": "desc",
},
},
"track_total_hits": 1,
},
"terminate_after": 1,
}
`;
exports[`services queries fetches the service items 1`] = `
Array [
Object {
"apm": Object {
"sources": Array [
Object {
"documentType": "transactionEvent",
"rollupInterval": "none",
},
],
},
"body": Object {
"aggs": Object {
"sample": Object {
"aggs": Object {
"overflowCount": Object {
"sum": Object {
"field": "service_transaction.aggregation.overflow_count",
},
},
"services": Object {
"aggs": Object {
"transactionType": Object {
"aggs": Object {
"avg_duration": Object {
"avg": Object {
"field": "transaction.duration.us",
},
},
"environments": Object {
"terms": Object {
"field": "service.environment",
},
},
"sample": Object {
"top_metrics": Object {
"metrics": Array [
Object {
"field": "agent.name",
},
],
"sort": Object {
"@timestamp": "desc",
},
},
},
"successful": Object {
"filter": Object {
"bool": Object {
"filter": Array [
Object {
"terms": Object {
"event.outcome": Array [
"success",
],
},
},
],
},
},
},
"successful_or_failed": Object {
"filter": Object {
"bool": Object {
"filter": Array [
Object {
"terms": Object {
"event.outcome": Array [
"failure",
"success",
],
},
},
],
},
},
},
},
"terms": Object {
"field": "transaction.type",
},
},
},
"terms": Object {
"field": "service.name",
"size": 1000,
},
},
},
"random_sampler": Object {
"probability": 1,
"seed": 0,
},
},
},
"query": Object {
"bool": Object {
"filter": Array [
Object {
"range": Object {
"@timestamp": Object {
"format": "epoch_millis",
"gte": 0,
"lte": 50000,
},
},
},
],
},
},
"size": 0,
"track_total_hits": false,
},
},
Object {
"apm": Object {
"events": Array [
"metric",
"error",
],
},
"body": Object {
"aggs": Object {
"sample": Object {
"aggs": Object {
"services": Object {
"aggs": Object {
"environments": Object {
"terms": Object {
"field": "service.environment",
},
},
"latest": Object {
"top_metrics": Object {
"metrics": Array [
Object {
"field": "agent.name",
},
],
"sort": Object {
"@timestamp": "desc",
},
},
},
},
"terms": Object {
"field": "service.name",
"size": 1000,
},
},
},
"random_sampler": Object {
"probability": 1,
"seed": 0,
},
},
},
"query": Object {
"bool": Object {
"filter": Array [
Object {
"range": Object {
"@timestamp": Object {
"format": "epoch_millis",
"gte": 0,
"lte": 50000,
},
},
},
],
},
},
"size": 0,
"track_total_hits": false,
},
},
undefined,
]
`;
exports[`services queries fetches the service transaction types 1`] = `
Object {
"apm": Object {
"sources": Array [
Object {
"documentType": "transactionMetric",
"rollupInterval": "1m",
},
],
},
"body": Object {
"aggs": Object {
"types": Object {
"terms": Object {
"field": "transaction.type",
"size": 100,
},
},
},
"query": Object {
"bool": Object {
"filter": Array [
Object {
"term": Object {
"service.name": "foo",
},
},
Object {
"range": Object {
"@timestamp": Object {
"format": "epoch_millis",
"gte": 0,
"lte": 50000,
},
},
},
],
},
},
"size": 0,
"track_total_hits": false,
},
}
`;

View file

@ -1,90 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ApmDocumentType } from '../../../common/document_type';
import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
import { RollupInterval } from '../../../common/rollup';
import {
inspectSearchParams,
SearchParamsMock,
} from '../../utils/test_helpers';
import { hasHistoricalAgentData } from '../historical_data/has_historical_agent_data';
import { getServicesItems } from './get_services/get_services_items';
import { getServiceAgent } from './get_service_agent';
import { getServiceTransactionTypes } from './get_service_transaction_types';
describe('services queries', () => {
let mock: SearchParamsMock;
afterEach(() => {
mock.teardown();
});
it('fetches the service agent name', async () => {
mock = await inspectSearchParams(({ mockApmEventClient }) =>
getServiceAgent({
serviceName: 'foo',
apmEventClient: mockApmEventClient,
start: 0,
end: 50000,
})
);
expect(mock.params).toMatchSnapshot();
});
it('fetches the service transaction types', async () => {
mock = await inspectSearchParams(({ mockApmEventClient }) =>
getServiceTransactionTypes({
serviceName: 'foo',
apmEventClient: mockApmEventClient,
start: 0,
end: 50000,
documentType: ApmDocumentType.TransactionMetric,
rollupInterval: RollupInterval.OneMinute,
})
);
expect(mock.params).toMatchSnapshot();
});
it('fetches the service items', async () => {
mock = await inspectSearchParams(
({ mockApmEventClient, mockApmAlertsClient }) =>
getServicesItems({
mlClient: undefined,
apmEventClient: mockApmEventClient,
documentType: ApmDocumentType.TransactionEvent,
rollupInterval: RollupInterval.None,
logger: {} as any,
environment: ENVIRONMENT_ALL.value,
kuery: '',
start: 0,
end: 50000,
serviceGroup: null,
randomSampler: {
probability: 1,
seed: 0,
},
apmAlertsClient: mockApmAlertsClient,
useDurationSummary: false,
})
);
const allParams = mock.spy.mock.calls.map((call) => call[1]);
expect(allParams).toMatchSnapshot();
});
it('fetches the agent status', async () => {
mock = await inspectSearchParams(({ mockApmEventClient }) =>
hasHistoricalAgentData(mockApmEventClient)
);
expect(mock.params).toMatchSnapshot();
});
});

View file

@ -6,34 +6,54 @@
*/
import expect from '@kbn/expect';
import { apm, timerange } from '@kbn/apm-synthtrace-client';
import moment from 'moment';
import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const archiveName = 'apm_8.0.0';
const synthtraceEsClient = getService('synthtraceEsClient');
registry.when(
'Historical data when data is not loaded',
{ config: 'basic', archives: [] },
() => {
it('handles the empty state', async () => {
registry.when('Historical data ', { config: 'basic', archives: [] }, () => {
describe('when there is not data', () => {
it('returns hasData=false', async () => {
const response = await apmApiClient.readUser({ endpoint: `GET /internal/apm/has_data` });
expect(response.status).to.be(200);
expect(response.body.hasData).to.be(false);
});
}
);
});
registry.when(
'Historical data when data is loaded',
{ config: 'basic', archives: [archiveName] },
() => {
it('returns hasData: true', async () => {
describe('when there is data', () => {
before(async () => {
const start = moment().subtract(30, 'minutes').valueOf();
const end = moment().valueOf();
const serviceInstance = apm
.service({ name: 'my-go-service', environment: 'production', agentName: 'go' })
.instance('instance-a');
const documents = [
timerange(start, end)
.interval('1m')
.generator((timestamp) =>
serviceInstance
.transaction({ transactionName: 'GET /users' })
.timestamp(timestamp)
.duration(10)
),
];
await synthtraceEsClient.index(documents);
});
after(() => synthtraceEsClient.clean());
it('returns hasData=true', async () => {
const response = await apmApiClient.readUser({ endpoint: `GET /internal/apm/has_data` });
expect(response.status).to.be(200);
expect(response.body.hasData).to.be(true);
});
}
);
});
});
}