Skip to content

Commit

Permalink
[APM] Migrate /correlations to deployment agnostic test (elastic#19…
Browse files Browse the repository at this point in the history
…9276)

## Summary

Closes elastic#198962
Part of elastic#193245

This PR contains the changes to migrate `correlations` test folder to
Deployment-agnostic testing strategy.

### How to test

- Serverless

```
node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts
node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep="APM"
```

It's recommended to be run against
[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)

- Stateful
```
node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts
node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep="APM"
```

## Checks

- [ ] (OPTIONAL, only if a test has been unskipped) Run flaky test suite
- [x] local run for serverless
- [x] local run for stateful
- [x] MKI run for serverless
  • Loading branch information
rmyz authored Nov 11, 2024
1 parent e429849 commit c97b85d
Show file tree
Hide file tree
Showing 12 changed files with 449 additions and 385 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.
*/

type ArchiveName =
| '8.0.0'
| 'apm_8.0.0'
| 'apm_mappings_only_8.0.0'
| 'infra_metrics_and_apm'
| 'metrics_8.0.0'
| 'ml_8.0.0'
| 'observability_overview'
| 'rum_8.0.0'
| 'rum_test_data';

export const ARCHIVER_ROUTES: { [key in ArchiveName]: string } = {
'8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/8.0.0',
'apm_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0',
'apm_mappings_only_8.0.0':
'x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_mappings_only_8.0.0',
infra_metrics_and_apm:
'x-pack/test/apm_api_integration/common/fixtures/es_archiver/infra_metrics_and_apm',
'metrics_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/metrics_8.0.0',
'ml_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/ml_8.0.0',
observability_overview:
'x-pack/test/apm_api_integration/common/fixtures/es_archiver/observability_overview',
'rum_8.0.0': 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_8.0.0',
rum_test_data: 'x-pack/test/apm_api_integration/common/fixtures/es_archiver/rum_test_data',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/*
* 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 { orderBy } from 'lodash';
import expect from '@kbn/expect';
import type { FailedTransactionsCorrelationsResponse } from '@kbn/apm-plugin/common/correlations/failed_transactions_correlations/types';
import { EVENT_OUTCOME } from '@kbn/apm-plugin/common/es_fields/apm';
import { EventOutcome } from '@kbn/apm-plugin/common/event_outcome';
import { LatencyDistributionChartType } from '@kbn/apm-plugin/common/latency_distribution_chart_types';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import { ARCHIVER_ROUTES } from '../constants/archiver';

// These tests go through the full sequence of queries required
// to get the final results for a failed transactions correlation analysis.
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const apmApiClient = getService('apmApi');
const esArchiver = getService('esArchiver');
// This matches the parameters used for the other tab's queries in `../correlations/*`.
const getOptions = () => ({
environment: 'ENVIRONMENT_ALL',
start: '2020',
end: '2021',
kuery: '',
});

describe('failed transactions', () => {
describe('without data', () => {
it('handles the empty state', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});

expect(overallDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${overallDistributionResponse.status}'`
);

const errorDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});

expect(errorDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${errorDistributionResponse.status}'`
);

const fieldCandidatesResponse = await apmApiClient.readUser({
endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
params: {
query: getOptions(),
},
});

expect(fieldCandidatesResponse.status).to.eql(
200,
`Expected status to be '200', got '${fieldCandidatesResponse.status}'`
);

const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/correlations/p_values/transactions',
params: {
body: {
...getOptions(),
fieldCandidates: fieldCandidatesResponse.body?.fieldCandidates,
},
},
});

expect(failedTransactionsCorrelationsResponse.status).to.eql(
200,
`Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
);

const finalRawResponse: FailedTransactionsCorrelationsResponse = {
ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
overallHistogram: overallDistributionResponse.body?.overallHistogram,
failedTransactionsCorrelations:
failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
};

expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
0,
`Expected 0 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
);
});
});

describe('with data', () => {
before(async () => {
await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
});
after(async () => {
await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
});

it('runs queries and returns results', async () => {
const overallDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});

expect(overallDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${overallDistributionResponse.status}'`
);

const errorDistributionResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/latency/overall_distribution/transactions',
params: {
body: {
...getOptions(),
percentileThreshold: 95,
termFilters: [{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }],
chartType: LatencyDistributionChartType.failedTransactionsCorrelations,
},
},
});

expect(errorDistributionResponse.status).to.eql(
200,
`Expected status to be '200', got '${errorDistributionResponse.status}'`
);

const fieldCandidatesResponse = await apmApiClient.readUser({
endpoint: 'GET /internal/apm/correlations/field_candidates/transactions',
params: {
query: getOptions(),
},
});

expect(fieldCandidatesResponse.status).to.eql(
200,
`Expected status to be '200', got '${fieldCandidatesResponse.status}'`
);

const fieldCandidates = fieldCandidatesResponse.body?.fieldCandidates.filter(
(t) => !(t === EVENT_OUTCOME)
);

// Identified 80 fieldCandidates.
expect(fieldCandidates.length).to.eql(
80,
`Expected field candidates length to be '80', got '${fieldCandidates.length}'`
);

const failedTransactionsCorrelationsResponse = await apmApiClient.readUser({
endpoint: 'POST /internal/apm/correlations/p_values/transactions',
params: {
body: {
...getOptions(),
fieldCandidates,
},
},
});

expect(failedTransactionsCorrelationsResponse.status).to.eql(
200,
`Expected status to be '200', got '${failedTransactionsCorrelationsResponse.status}'`
);

const fieldsToSample = new Set<string>();
if (
failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.length > 0
) {
failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations.forEach(
(d) => {
fieldsToSample.add(d.fieldName);
}
);
}

const finalRawResponse: FailedTransactionsCorrelationsResponse = {
ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning,
percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue,
overallHistogram: overallDistributionResponse.body?.overallHistogram,
errorHistogram: errorDistributionResponse.body?.overallHistogram,
failedTransactionsCorrelations:
failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations,
};

expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875);
expect(finalRawResponse?.errorHistogram?.length).to.be(101);
expect(finalRawResponse?.overallHistogram?.length).to.be(101);

expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql(
29,
`Expected 29 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations?.length}.`
);

const sortedCorrelations = orderBy(
finalRawResponse?.failedTransactionsCorrelations,
['score', 'fieldName', 'fieldValue'],
['desc', 'asc', 'asc']
);
const correlation = sortedCorrelations?.[0];

expect(typeof correlation).to.be('object');
expect(correlation?.doc_count).to.be(31);
expect(correlation?.score).to.be(83.70467673605746);
expect(correlation?.bg_count).to.be(31);
expect(correlation?.fieldName).to.be('transaction.result');
expect(correlation?.fieldValue).to.be('HTTP 5xx');
expect(typeof correlation?.pValue).to.be('number');
expect(typeof correlation?.normalizedScore).to.be('number');
expect(typeof correlation?.failurePercentage).to.be('number');
expect(typeof correlation?.successPercentage).to.be('number');
});
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 expect from '@kbn/expect';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import { ARCHIVER_ROUTES } from '../constants/archiver';

export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const apmApiClient = getService('apmApi');
const esArchiver = getService('esArchiver');

const endpoint = 'GET /internal/apm/correlations/field_candidates/transactions';

const getOptions = () => ({
params: {
query: {
environment: 'ENVIRONMENT_ALL',
start: '2020',
end: '2021',
kuery: '',
},
},
});

describe('field candidates', () => {
describe('without data', () => {
it('handles the empty state', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
});

expect(response.status).to.be(200);
// If the source indices are empty, there will be no field candidates
// because of the `include_empty_fields: false` option in the query.
expect(response.body?.fieldCandidates.length).to.be(0);
});
});

describe('with data and default args', () => {
before(async () => {
await esArchiver.load(ARCHIVER_ROUTES['8.0.0']);
});
after(async () => {
await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']);
});

it('returns field candidates', async () => {
const response = await apmApiClient.readUser({
endpoint,
...getOptions(),
});

expect(response.status).to.eql(200);
expect(response.body?.fieldCandidates.length).to.be(81);
});
});
});
}
Loading

0 comments on commit c97b85d

Please sign in to comment.