diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0_empty/mappings.json b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_mappings_only_8.0.0/mappings.json similarity index 100% rename from x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0_empty/mappings.json rename to x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_mappings_only_8.0.0/mappings.json diff --git a/x-pack/test/apm_api_integration/common/registry.ts b/x-pack/test/apm_api_integration/common/registry.ts index 78c5bcb383c9..55b5863e6d44 100644 --- a/x-pack/test/apm_api_integration/common/registry.ts +++ b/x-pack/test/apm_api_integration/common/registry.ts @@ -15,7 +15,7 @@ import { FtrProviderContext } from './ftr_provider_context'; type ArchiveName = | 'apm_8.0.0' - | 'apm_8.0.0_empty' + | 'apm_mappings_only_8.0.0' | '8.0.0' | 'metrics_8.0.0' | 'ml_8.0.0' diff --git a/x-pack/test/apm_api_integration/common/synthtrace_es_client.ts b/x-pack/test/apm_api_integration/common/synthtrace_es_client.ts index 6a42ae16f0b2..aebe4e71178f 100644 --- a/x-pack/test/apm_api_integration/common/synthtrace_es_client.ts +++ b/x-pack/test/apm_api_integration/common/synthtrace_es_client.ts @@ -14,6 +14,7 @@ import { import { chunk } from 'lodash'; import pLimit from 'p-limit'; import { inspect } from 'util'; +import { PromiseReturnType } from '../../../plugins/observability/typings/common'; import { InheritedFtrProviderContext } from './ftr_provider_context'; export async function synthtraceEsClient(context: InheritedFtrProviderContext) { @@ -74,3 +75,5 @@ export async function synthtraceEsClient(context: InheritedFtrProviderContext) { }, }; } + +export type SynthtraceEsClient = PromiseReturnType; diff --git a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts index 76f8d20c8ada..f0f917e865fa 100644 --- a/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts +++ b/x-pack/test/apm_api_integration/tests/error_rate/service_apis.ts @@ -114,7 +114,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let errorRateMetricValues: PromiseReturnType; let errorTransactionValues: PromiseReturnType; - registry.when('Services APIs', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { + registry.when('Services APIs', { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { describe('when data is loaded ', () => { const GO_PROD_LIST_RATE = 75; const GO_PROD_LIST_ERROR_RATE = 25; diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index f68a49658f2e..7c709901d53e 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -113,11 +113,11 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte }); describe('services/error_groups_main_statistics', function () { - loadTestFile(require.resolve('./services/error_groups_main_statistics')); + loadTestFile(require.resolve('./services/error_groups/error_groups_main_statistics')); }); describe('services/error_groups_detailed_statistics', function () { - loadTestFile(require.resolve('./services/error_groups_detailed_statistics')); + loadTestFile(require.resolve('./services/error_groups/error_groups_detailed_statistics')); }); describe('services/detailed_statistics', function () { diff --git a/x-pack/test/apm_api_integration/tests/latency/service_apis.ts b/x-pack/test/apm_api_integration/tests/latency/service_apis.ts index d3ec68c51782..aa8282ccb0cc 100644 --- a/x-pack/test/apm_api_integration/tests/latency/service_apis.ts +++ b/x-pack/test/apm_api_integration/tests/latency/service_apis.ts @@ -116,7 +116,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let latencyMetricValues: PromiseReturnType; let latencyTransactionValues: PromiseReturnType; - registry.when('Services APIs', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { + registry.when('Services APIs', { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { describe('when data is loaded ', () => { const GO_PROD_RATE = 80; const GO_DEV_RATE = 20; diff --git a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts index e4aad4f3f697..9082c5dec3b7 100644 --- a/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts @@ -83,89 +83,95 @@ export default function ApiTest({ getService }: FtrProviderContext) { } ); - registry.when('data is loaded', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { - describe('Observability overview api ', () => { - const GO_PROD_RATE = 50; - const GO_DEV_RATE = 5; - const JAVA_PROD_RATE = 45; - before(async () => { - const serviceGoProdInstance = service('synth-go', 'production', 'go').instance( - 'instance-a' - ); - const serviceGoDevInstance = service('synth-go', 'development', 'go').instance( - 'instance-b' - ); - const serviceJavaInstance = service('synth-java', 'production', 'java').instance( - 'instance-c' - ); - - await synthtraceEsClient.index([ - ...timerange(start, end) - .interval('1m') - .rate(GO_PROD_RATE) - .flatMap((timestamp) => - serviceGoProdInstance - .transaction('GET /api/product/list') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ...timerange(start, end) - .interval('1m') - .rate(GO_DEV_RATE) - .flatMap((timestamp) => - serviceGoDevInstance - .transaction('GET /api/product/:id') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ...timerange(start, end) - .interval('1m') - .rate(JAVA_PROD_RATE) - .flatMap((timestamp) => - serviceJavaInstance - .transaction('POST /api/product/buy') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ]); - }); - - after(() => synthtraceEsClient.clean()); - - describe('compare throughput values', () => { - let throughputValues: PromiseReturnType; + registry.when( + 'data is loaded', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('Observability overview api ', () => { + const GO_PROD_RATE = 50; + const GO_DEV_RATE = 5; + const JAVA_PROD_RATE = 45; before(async () => { - throughputValues = await getThroughputValues(); + const serviceGoProdInstance = service('synth-go', 'production', 'go').instance( + 'instance-a' + ); + const serviceGoDevInstance = service('synth-go', 'development', 'go').instance( + 'instance-b' + ); + const serviceJavaInstance = service('synth-java', 'production', 'java').instance( + 'instance-c' + ); + + await synthtraceEsClient.index([ + ...timerange(start, end) + .interval('1m') + .rate(GO_PROD_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction('GET /api/product/list') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(GO_DEV_RATE) + .flatMap((timestamp) => + serviceGoDevInstance + .transaction('GET /api/product/:id') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(JAVA_PROD_RATE) + .flatMap((timestamp) => + serviceJavaInstance + .transaction('POST /api/product/buy') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), + ]); }); - it('returns same number of service as shown on service inventory API', () => { - const { serviceInventoryCount, observabilityOverview } = throughputValues; - [serviceInventoryCount, observabilityOverview.serviceCount].forEach((value) => - expect(value).to.be.equal(2) - ); - }); + after(() => synthtraceEsClient.clean()); - it('returns same throughput value on service inventory and obs throughput count', () => { - const { serviceInventoryThroughputSum, observabilityOverview } = throughputValues; - const obsThroughputCount = roundNumber(observabilityOverview.transactionPerMinute.value); - [serviceInventoryThroughputSum, obsThroughputCount].forEach((value) => - expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE + JAVA_PROD_RATE)) - ); - }); + describe('compare throughput values', () => { + let throughputValues: PromiseReturnType; + before(async () => { + throughputValues = await getThroughputValues(); + }); - it('returns same throughput value on service inventory and obs mean throughput timeseries', () => { - const { serviceInventoryThroughputSum, observabilityOverview } = throughputValues; - const obsThroughputMean = roundNumber( - meanBy(observabilityOverview.transactionPerMinute.timeseries, 'y') - ); - [serviceInventoryThroughputSum, obsThroughputMean].forEach((value) => - expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE + JAVA_PROD_RATE)) - ); + it('returns same number of service as shown on service inventory API', () => { + const { serviceInventoryCount, observabilityOverview } = throughputValues; + [serviceInventoryCount, observabilityOverview.serviceCount].forEach((value) => + expect(value).to.be.equal(2) + ); + }); + + it('returns same throughput value on service inventory and obs throughput count', () => { + const { serviceInventoryThroughputSum, observabilityOverview } = throughputValues; + const obsThroughputCount = roundNumber( + observabilityOverview.transactionPerMinute.value + ); + [serviceInventoryThroughputSum, obsThroughputCount].forEach((value) => + expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE + JAVA_PROD_RATE)) + ); + }); + + it('returns same throughput value on service inventory and obs mean throughput timeseries', () => { + const { serviceInventoryThroughputSum, observabilityOverview } = throughputValues; + const obsThroughputMean = roundNumber( + meanBy(observabilityOverview.transactionPerMinute.timeseries, 'y') + ); + [serviceInventoryThroughputSum, obsThroughputMean].forEach((value) => + expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE + JAVA_PROD_RATE)) + ); + }); }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts index 1e9051b64a90..7d4efa14b2d8 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances_main_statistics.ts @@ -285,7 +285,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when( 'Service overview instances main statistics when data is generated', - { config: 'basic', archives: ['apm_8.0.0_empty'] }, + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { describe('for two go instances and one java instance', () => { const GO_A_INSTANCE_RATE_SUCCESS = 10; diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.ts new file mode 100644 index 000000000000..54bbd4eb0bf7 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_detailed_statistics.ts @@ -0,0 +1,199 @@ +/* + * 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 { first, last, sumBy } from 'lodash'; +import moment from 'moment'; +import { isFiniteNumber } from '../../../../../plugins/apm/common/utils/is_finite_number'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { RecursivePartial } from '../../../../../plugins/apm/typings/common'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { registry } from '../../../common/registry'; +import { config, generateData } from './generate_data'; +import { getErrorGroupIds } from './get_error_group_ids'; + +type ErrorGroupsDetailedStatistics = + APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const serviceName = 'synth-go'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>['params'] + > + ) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics`, + params: { + path: { serviceName, ...overrides?.path }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + numBuckets: 20, + transactionType: 'request', + groupIds: JSON.stringify(['foo']), + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + } + + registry.when( + 'Error groups detailed statistics when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body).to.be.eql({ currentPeriod: {}, previousPeriod: {} }); + }); + } + ); + + registry.when( + 'Error groups detailed statistics', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('when data is loaded', () => { + const { PROD_LIST_ERROR_RATE, PROD_ID_ERROR_RATE } = config; + before(async () => { + await generateData({ serviceName, start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('without data comparison', () => { + let errorGroupsDetailedStatistics: ErrorGroupsDetailedStatistics; + let errorIds: string[] = []; + before(async () => { + errorIds = await getErrorGroupIds({ serviceName, start, end, apmApiClient }); + const response = await callApi({ + query: { + groupIds: JSON.stringify(errorIds), + }, + }); + errorGroupsDetailedStatistics = response.body; + }); + + it('return detailed statistics for all errors found', () => { + expect(Object.keys(errorGroupsDetailedStatistics.currentPeriod).sort()).to.eql( + errorIds + ); + }); + + it('returns correct number of occurrencies', () => { + const numberOfBuckets = 15; + const detailedStatisticsOccurrenciesSum = Object.values( + errorGroupsDetailedStatistics.currentPeriod + ) + .sort() + .map(({ timeseries }) => { + return sumBy(timeseries, 'y'); + }); + + expect(detailedStatisticsOccurrenciesSum).to.eql([ + PROD_ID_ERROR_RATE * numberOfBuckets, + PROD_LIST_ERROR_RATE * numberOfBuckets, + ]); + }); + }); + + describe('return empty state when invalid group id', () => { + let errorGroupsDetailedStatistics: ErrorGroupsDetailedStatistics; + before(async () => { + const response = await callApi({ + query: { + groupIds: JSON.stringify(['foo']), + }, + }); + errorGroupsDetailedStatistics = response.body; + }); + + it('returns empty state', () => { + expect(errorGroupsDetailedStatistics).to.be.eql({ + currentPeriod: {}, + previousPeriod: {}, + }); + }); + }); + + describe('with comparison', () => { + let errorGroupsDetailedStatistics: ErrorGroupsDetailedStatistics; + let errorIds: string[] = []; + before(async () => { + errorIds = await getErrorGroupIds({ serviceName, start, end, apmApiClient }); + const response = await callApi({ + query: { + groupIds: JSON.stringify(errorIds), + start: moment(end).subtract(7, 'minutes').toISOString(), + end: new Date(end).toISOString(), + comparisonStart: new Date(start).toISOString(), + comparisonEnd: moment(start).add(7, 'minutes').toISOString(), + }, + }); + errorGroupsDetailedStatistics = response.body; + }); + + it('returns some data', () => { + expect( + Object.keys(errorGroupsDetailedStatistics.currentPeriod).length + ).to.be.greaterThan(0); + expect( + Object.keys(errorGroupsDetailedStatistics.previousPeriod).length + ).to.be.greaterThan(0); + + const hasCurrentPeriodData = Object.values( + errorGroupsDetailedStatistics.currentPeriod + )[0].timeseries.some(({ y }) => isFiniteNumber(y)); + + const hasPreviousPeriodData = Object.values( + errorGroupsDetailedStatistics.previousPeriod + )[0].timeseries.some(({ y }) => isFiniteNumber(y)); + + expect(hasCurrentPeriodData).to.equal(true); + expect(hasPreviousPeriodData).to.equal(true); + }); + + it('has same start time for both periods', () => { + expect( + first(Object.values(errorGroupsDetailedStatistics.currentPeriod)[0].timeseries)?.x + ).to.equal( + first(Object.values(errorGroupsDetailedStatistics.previousPeriod)[0].timeseries)?.x + ); + }); + + it('has same end time for both periods', () => { + expect( + last(Object.values(errorGroupsDetailedStatistics.currentPeriod)[0].timeseries)?.x + ).to.equal( + last(Object.values(errorGroupsDetailedStatistics.previousPeriod)[0].timeseries)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + expect( + Object.values(errorGroupsDetailedStatistics.currentPeriod)[0].timeseries.length + ).to.equal( + Object.values(errorGroupsDetailedStatistics.previousPeriod)[0].timeseries.length + ); + }); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.ts new file mode 100644 index 000000000000..bc6bd023a0f5 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/error_groups_main_statistics.ts @@ -0,0 +1,110 @@ +/* + * 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 moment from 'moment'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { RecursivePartial } from '../../../../../plugins/apm/typings/common'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { registry } from '../../../common/registry'; +import { generateData, config } from './generate_data'; + +type ErrorGroupsMainStatistics = + APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const serviceName = 'synth-go'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function callApi( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>['params'] + > + ) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/error_groups/main_statistics`, + params: { + path: { serviceName, ...overrides?.path }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + transactionType: 'request', + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + } + + registry.when( + 'Error groups main statistics when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles empty state', async () => { + const response = await callApi(); + expect(response.status).to.be(200); + expect(response.body.error_groups).to.empty(); + expect(response.body.is_aggregation_accurate).to.eql(true); + }); + } + ); + + registry.when( + 'Error groups main statistics', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('when data is loaded', () => { + const { PROD_LIST_ERROR_RATE, PROD_ID_ERROR_RATE, ERROR_NAME_1, ERROR_NAME_2 } = config; + + before(async () => { + await generateData({ serviceName, start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('returns the correct data', () => { + let errorGroupMainStatistics: ErrorGroupsMainStatistics; + before(async () => { + const response = await callApi(); + errorGroupMainStatistics = response.body; + }); + + it('returns correct number of occurrencies', () => { + expect(errorGroupMainStatistics.error_groups.length).to.equal(2); + expect(errorGroupMainStatistics.error_groups.map((error) => error.name).sort()).to.eql([ + ERROR_NAME_1, + ERROR_NAME_2, + ]); + }); + + it('returns correct occurences', () => { + const numberOfBuckets = 15; + expect( + errorGroupMainStatistics.error_groups.map((error) => error.occurrences).sort() + ).to.eql([ + PROD_LIST_ERROR_RATE * numberOfBuckets, + PROD_ID_ERROR_RATE * numberOfBuckets, + ]); + }); + + it('has same last seen value as end date', () => { + errorGroupMainStatistics.error_groups.map((error) => { + expect(error.lastSeen).to.equal(moment(end).startOf('minute').valueOf()); + }); + }); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts new file mode 100644 index 000000000000..1a9d6683244e --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/generate_data.ts @@ -0,0 +1,92 @@ +/* + * 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 { service, timerange } from '@elastic/apm-synthtrace'; +import type { SynthtraceEsClient } from '../../../common/synthtrace_es_client'; + +export const config = { + PROD_LIST_RATE: 75, + PROD_LIST_ERROR_RATE: 25, + PROD_ID_RATE: 50, + PROD_ID_ERROR_RATE: 50, + ERROR_NAME_1: 'Error test 1', + ERROR_NAME_2: 'Error test 2', +}; + +export async function generateData({ + synthtraceEsClient, + serviceName, + start, + end, +}: { + synthtraceEsClient: SynthtraceEsClient; + serviceName: string; + start: number; + end: number; +}) { + const serviceGoProdInstance = service(serviceName, 'production', 'go').instance('instance-a'); + + const transactionNameProductList = 'GET /api/product/list'; + const transactionNameProductId = 'GET /api/product/:id'; + + const { + PROD_LIST_RATE, + PROD_LIST_ERROR_RATE, + PROD_ID_RATE, + PROD_ID_ERROR_RATE, + ERROR_NAME_1, + ERROR_NAME_2, + } = config; + + await synthtraceEsClient.index([ + ...timerange(start, end) + .interval('1m') + .rate(PROD_LIST_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionNameProductList) + .timestamp(timestamp) + .duration(1000) + .success() + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(PROD_LIST_ERROR_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionNameProductList) + .errors(serviceGoProdInstance.error(ERROR_NAME_1, 'foo').timestamp(timestamp)) + .duration(1000) + .timestamp(timestamp) + .failure() + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(PROD_ID_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionNameProductId) + .timestamp(timestamp) + .duration(1000) + .success() + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(PROD_ID_ERROR_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionNameProductId) + .errors(serviceGoProdInstance.error(ERROR_NAME_2, 'bar').timestamp(timestamp)) + .duration(1000) + .timestamp(timestamp) + .failure() + .serialize() + ), + ]); +} diff --git a/x-pack/test/apm_api_integration/tests/services/get_error_group_ids.ts b/x-pack/test/apm_api_integration/tests/services/error_groups/get_error_group_ids.ts similarity index 66% rename from x-pack/test/apm_api_integration/tests/services/get_error_group_ids.ts rename to x-pack/test/apm_api_integration/tests/services/error_groups/get_error_group_ids.ts index 9fa7232240db..cfc0867fdcfb 100644 --- a/x-pack/test/apm_api_integration/tests/services/get_error_group_ids.ts +++ b/x-pack/test/apm_api_integration/tests/services/error_groups/get_error_group_ids.ts @@ -5,28 +5,29 @@ * 2.0. */ import { take } from 'lodash'; -import { ApmApiSupertest } from '../../common/apm_api_supertest'; +import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; +import { ApmServices } from '../../../common/config'; export async function getErrorGroupIds({ - apmApiSupertest, + apmApiClient, start, end, serviceName = 'opbeans-java', count = 5, }: { - apmApiSupertest: ApmApiSupertest; - start: string; - end: string; + apmApiClient: PromiseReturnType; + start: number; + end: number; serviceName?: string; count?: number; }) { - const { body } = await apmApiSupertest({ + const { body } = await apmApiClient.readUser({ endpoint: `GET /internal/apm/services/{serviceName}/error_groups/main_statistics`, params: { path: { serviceName }, query: { - start, - end, + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), transactionType: 'request', environment: 'ENVIRONMENT_ALL', kuery: '', diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts deleted file mode 100644 index 3587a3e96c0d..000000000000 --- a/x-pack/test/apm_api_integration/tests/services/error_groups_detailed_statistics.ts +++ /dev/null @@ -1,202 +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 url from 'url'; -import expect from '@kbn/expect'; -import moment from 'moment'; -import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { registry } from '../../common/registry'; -import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { createApmApiClient } from '../../common/apm_api_supertest'; -import { getErrorGroupIds } from './get_error_group_ids'; - -type ErrorGroupsDetailedStatistics = - APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('legacySupertestAsApmReadUser'); - const apmApiSupertest = createApmApiClient(supertest); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - const { start, end } = metadata; - - registry.when( - 'Error groups detailed statistics when data is not loaded', - { config: 'basic', archives: [] }, - () => { - it('handles empty state', async () => { - const groupIds = await getErrorGroupIds({ apmApiSupertest, start, end }); - - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, - query: { - start, - end, - numBuckets: 20, - transactionType: 'request', - groupIds: JSON.stringify(groupIds), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.be.eql({ currentPeriod: {}, previousPeriod: {} }); - }); - } - ); - - registry.when( - 'Error groups detailed statistics when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - it('returns the correct data', async () => { - const groupIds = await getErrorGroupIds({ apmApiSupertest, start, end }); - - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, - query: { - start, - end, - numBuckets: 20, - transactionType: 'request', - groupIds: JSON.stringify(groupIds), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - - const errorGroupsComparisonStatistics = response.body as ErrorGroupsDetailedStatistics; - expect(Object.keys(errorGroupsComparisonStatistics.currentPeriod).sort()).to.eql( - groupIds.sort() - ); - - groupIds.forEach((groupId) => { - expect(errorGroupsComparisonStatistics.currentPeriod[groupId]).not.to.be.empty(); - }); - - const errorgroupsComparisonStatistics = - errorGroupsComparisonStatistics.currentPeriod[groupIds[0]]; - expect( - errorgroupsComparisonStatistics.timeseries.map(({ y }) => y && isFinite(y)).length - ).to.be.greaterThan(0); - expectSnapshot(errorgroupsComparisonStatistics).toMatch(); - }); - - it('returns an empty state when requested groupIds are not available in the given time range', async () => { - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, - query: { - start, - end, - numBuckets: 20, - transactionType: 'request', - groupIds: JSON.stringify(['foo']), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.be.eql({ currentPeriod: {}, previousPeriod: {} }); - }); - } - ); - - registry.when( - 'Error groups detailed statistics when data is loaded with previous data', - { config: 'basic', archives: [archiveName] }, - () => { - describe('returns the correct data', async () => { - let response: { - status: number; - body: ErrorGroupsDetailedStatistics; - }; - let groupIds: string[]; - - before(async () => { - groupIds = await getErrorGroupIds({ apmApiSupertest, start, end }); - - response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, - query: { - numBuckets: 20, - transactionType: 'request', - groupIds: JSON.stringify(groupIds), - start: moment(end).subtract(15, 'minutes').toISOString(), - end, - comparisonStart: start, - comparisonEnd: moment(start).add(15, 'minutes').toISOString(), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - }); - - it('returns correct timeseries', () => { - const errorGroupsComparisonStatistics = response.body as ErrorGroupsDetailedStatistics; - const errorgroupsComparisonStatistics = - errorGroupsComparisonStatistics.currentPeriod[groupIds[0]]; - expect( - errorgroupsComparisonStatistics.timeseries.map(({ y }) => y && isFinite(y)).length - ).to.be.greaterThan(0); - expectSnapshot(errorgroupsComparisonStatistics).toMatch(); - }); - - it('matches x-axis on current period and previous period', () => { - const errorGroupsComparisonStatistics = response.body as ErrorGroupsDetailedStatistics; - - const currentPeriodItems = Object.values(errorGroupsComparisonStatistics.currentPeriod); - const previousPeriodItems = Object.values(errorGroupsComparisonStatistics.previousPeriod); - - const currentPeriodFirstItem = currentPeriodItems[0]; - const previousPeriodFirstItem = previousPeriodItems[0]; - - expect(currentPeriodFirstItem.timeseries.map(({ x }) => x)).to.be.eql( - previousPeriodFirstItem.timeseries.map(({ x }) => x) - ); - }); - }); - - it('returns an empty state when requested groupIds are not available in the given time range', async () => { - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/detailed_statistics`, - query: { - numBuckets: 20, - transactionType: 'request', - groupIds: JSON.stringify(['foo']), - start: moment(end).subtract(15, 'minutes').toISOString(), - end, - comparisonStart: start, - comparisonEnd: moment(start).add(15, 'minutes').toISOString(), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.be.eql({ currentPeriod: {}, previousPeriod: {} }); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts b/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts deleted file mode 100644 index b6fb0696f3f7..000000000000 --- a/x-pack/test/apm_api_integration/tests/services/error_groups_main_statistics.ts +++ /dev/null @@ -1,123 +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 url from 'url'; -import expect from '@kbn/expect'; -import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { registry } from '../../common/registry'; -import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; - -type ErrorGroupsMainStatistics = - APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('legacySupertestAsApmReadUser'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - const { start, end } = metadata; - - registry.when( - 'Error groups main statistics when data is not loaded', - { config: 'basic', archives: [] }, - () => { - it('handles empty state', async () => { - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/main_statistics`, - query: { - start, - end, - transactionType: 'request', - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - - expect(response.status).to.be(200); - expect(response.body.error_groups).to.empty(); - expect(response.body.is_aggregation_accurate).to.eql(true); - }); - } - ); - - registry.when( - 'Error groups main statistics when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - it('returns the correct data', async () => { - const response = await supertest.get( - url.format({ - pathname: `/internal/apm/services/opbeans-java/error_groups/main_statistics`, - query: { - start, - end, - transactionType: 'request', - environment: 'production', - kuery: '', - }, - }) - ); - - expect(response.status).to.be(200); - - const errorGroupMainStatistics = response.body as ErrorGroupsMainStatistics; - - expect(errorGroupMainStatistics.is_aggregation_accurate).to.eql(true); - expect(errorGroupMainStatistics.error_groups.length).to.be.greaterThan(0); - - expectSnapshot(errorGroupMainStatistics.error_groups.map(({ name }) => name)) - .toMatchInline(` - Array [ - "Response status 404", - "No converter found for return value of type: class com.sun.proxy.$Proxy162", - "Response status 404", - "Broken pipe", - "java.io.IOException: Connection reset by peer", - "Request method 'POST' not supported", - "java.io.IOException: Connection reset by peer", - "null", - ] - `); - - const occurences = errorGroupMainStatistics.error_groups.map( - ({ occurrences }) => occurrences - ); - - occurences.forEach((occurence) => expect(occurence).to.be.greaterThan(0)); - - expectSnapshot(occurences).toMatchInline(` - Array [ - 17, - 12, - 4, - 4, - 3, - 2, - 1, - 1, - ] - `); - - const firstItem = errorGroupMainStatistics.error_groups[0]; - - expectSnapshot(firstItem).toMatchInline(` - Object { - "group_id": "d16d39e7fa133b8943cea035430a7b4e", - "lastSeen": 1627975146078, - "name": "Response status 404", - "occurrences": 17, - } - `); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/services/throughput.ts b/x-pack/test/apm_api_integration/tests/services/throughput.ts index abc7988af823..a9865a0c3bb3 100644 --- a/x-pack/test/apm_api_integration/tests/services/throughput.ts +++ b/x-pack/test/apm_api_integration/tests/services/throughput.ts @@ -63,217 +63,223 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - registry.when('data is loaded', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { - describe('Throughput chart api', () => { - const GO_PROD_RATE = 50; - const GO_DEV_RATE = 5; - const JAVA_PROD_RATE = 45; - - before(async () => { - const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( - 'instance-a' - ); - const serviceGoDevInstance = service(serviceName, 'development', 'go').instance( - 'instance-b' - ); - - const serviceJavaInstance = service('synth-java', 'development', 'java').instance( - 'instance-c' - ); - - await synthtraceEsClient.index([ - ...timerange(start, end) - .interval('1m') - .rate(GO_PROD_RATE) - .flatMap((timestamp) => - serviceGoProdInstance - .transaction('GET /api/product/list') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ...timerange(start, end) - .interval('1m') - .rate(GO_DEV_RATE) - .flatMap((timestamp) => - serviceGoDevInstance - .transaction('GET /api/product/:id') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ...timerange(start, end) - .interval('1m') - .rate(JAVA_PROD_RATE) - .flatMap((timestamp) => - serviceJavaInstance - .transaction('POST /api/product/buy') - .duration(1000) - .timestamp(timestamp) - .serialize() - ), - ]); - }); - - after(() => synthtraceEsClient.clean()); - - describe('compare transactions and metrics based throughput', () => { - let throughputMetrics: ThroughputReturn; - let throughputTransactions: ThroughputReturn; + registry.when( + 'data is loaded', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('Throughput chart api', () => { + const GO_PROD_RATE = 50; + const GO_DEV_RATE = 5; + const JAVA_PROD_RATE = 45; before(async () => { - const [throughputMetricsResponse, throughputTransactionsResponse] = await Promise.all([ - callApi({ query: { kuery: 'processor.event : "metric"' } }), - callApi({ query: { kuery: 'processor.event : "transaction"' } }), + const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( + 'instance-a' + ); + const serviceGoDevInstance = service(serviceName, 'development', 'go').instance( + 'instance-b' + ); + + const serviceJavaInstance = service('synth-java', 'development', 'java').instance( + 'instance-c' + ); + + await synthtraceEsClient.index([ + ...timerange(start, end) + .interval('1m') + .rate(GO_PROD_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction('GET /api/product/list') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(GO_DEV_RATE) + .flatMap((timestamp) => + serviceGoDevInstance + .transaction('GET /api/product/:id') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(JAVA_PROD_RATE) + .flatMap((timestamp) => + serviceJavaInstance + .transaction('POST /api/product/buy') + .duration(1000) + .timestamp(timestamp) + .serialize() + ), ]); - throughputMetrics = throughputMetricsResponse.body; - throughputTransactions = throughputTransactionsResponse.body; }); - it('returns some transactions data', () => { - expect(throughputTransactions.currentPeriod.length).to.be.greaterThan(0); - const hasData = throughputTransactions.currentPeriod.some(({ y }) => isFiniteNumber(y)); - expect(hasData).to.equal(true); - }); + after(() => synthtraceEsClient.clean()); - it('returns some metrics data', () => { - expect(throughputMetrics.currentPeriod.length).to.be.greaterThan(0); - const hasData = throughputMetrics.currentPeriod.some(({ y }) => isFiniteNumber(y)); - expect(hasData).to.equal(true); - }); + describe('compare transactions and metrics based throughput', () => { + let throughputMetrics: ThroughputReturn; + let throughputTransactions: ThroughputReturn; - it('has same mean value for metrics and transactions data', () => { - const transactionsMean = meanBy(throughputTransactions.currentPeriod, 'y'); - const metricsMean = meanBy(throughputMetrics.currentPeriod, 'y'); - [transactionsMean, metricsMean].forEach((value) => - expect(roundNumber(value)).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE)) - ); - }); - - it('has a bucket size of 10 seconds for transactions data', () => { - const firstTimerange = throughputTransactions.currentPeriod[0].x; - const secondTimerange = throughputTransactions.currentPeriod[1].x; - const timeIntervalAsSeconds = (secondTimerange - firstTimerange) / 1000; - expect(timeIntervalAsSeconds).to.equal(10); - }); - - it('has a bucket size of 1 minute for metrics data', () => { - const firstTimerange = throughputMetrics.currentPeriod[0].x; - const secondTimerange = throughputMetrics.currentPeriod[1].x; - const timeIntervalAsMinutes = (secondTimerange - firstTimerange) / 1000 / 60; - expect(timeIntervalAsMinutes).to.equal(1); - }); - }); - - describe('production environment', () => { - let throughput: ThroughputReturn; - - before(async () => { - const throughputResponse = await callApi({ query: { environment: 'production' } }); - throughput = throughputResponse.body; - }); - - it('returns some data', () => { - expect(throughput.currentPeriod.length).to.be.greaterThan(0); - const hasData = throughput.currentPeriod.some(({ y }) => isFiniteNumber(y)); - expect(hasData).to.equal(true); - }); - - it('returns correct average throughput', () => { - const throughputMean = meanBy(throughput.currentPeriod, 'y'); - expect(roundNumber(throughputMean)).to.be.equal(roundNumber(GO_PROD_RATE)); - }); - }); - - describe('when synth-java is selected', () => { - let throughput: ThroughputReturn; - - before(async () => { - const throughputResponse = await callApi({ path: { serviceName: 'synth-java' } }); - throughput = throughputResponse.body; - }); - - it('returns some data', () => { - expect(throughput.currentPeriod.length).to.be.greaterThan(0); - const hasData = throughput.currentPeriod.some(({ y }) => isFiniteNumber(y)); - expect(hasData).to.equal(true); - }); - - it('returns throughput related to java agent', () => { - const throughputMean = meanBy(throughput.currentPeriod, 'y'); - expect(roundNumber(throughputMean)).to.be.equal(roundNumber(JAVA_PROD_RATE)); - }); - }); - - describe('time comparisons', () => { - let throughputResponse: ThroughputReturn; - - before(async () => { - const response = await callApi({ - query: { - start: moment(end).subtract(7, 'minutes').toISOString(), - end: new Date(end).toISOString(), - comparisonStart: new Date(start).toISOString(), - comparisonEnd: moment(start).add(7, 'minutes').toISOString(), - }, + before(async () => { + const [throughputMetricsResponse, throughputTransactionsResponse] = await Promise.all([ + callApi({ query: { kuery: 'processor.event : "metric"' } }), + callApi({ query: { kuery: 'processor.event : "transaction"' } }), + ]); + throughputMetrics = throughputMetricsResponse.body; + throughputTransactions = throughputTransactionsResponse.body; + }); + + it('returns some transactions data', () => { + expect(throughputTransactions.currentPeriod.length).to.be.greaterThan(0); + const hasData = throughputTransactions.currentPeriod.some(({ y }) => isFiniteNumber(y)); + expect(hasData).to.equal(true); + }); + + it('returns some metrics data', () => { + expect(throughputMetrics.currentPeriod.length).to.be.greaterThan(0); + const hasData = throughputMetrics.currentPeriod.some(({ y }) => isFiniteNumber(y)); + expect(hasData).to.equal(true); + }); + + it('has same mean value for metrics and transactions data', () => { + const transactionsMean = meanBy(throughputTransactions.currentPeriod, 'y'); + const metricsMean = meanBy(throughputMetrics.currentPeriod, 'y'); + [transactionsMean, metricsMean].forEach((value) => + expect(roundNumber(value)).to.be.equal(roundNumber(GO_PROD_RATE + GO_DEV_RATE)) + ); + }); + + it('has a bucket size of 10 seconds for transactions data', () => { + const firstTimerange = throughputTransactions.currentPeriod[0].x; + const secondTimerange = throughputTransactions.currentPeriod[1].x; + const timeIntervalAsSeconds = (secondTimerange - firstTimerange) / 1000; + expect(timeIntervalAsSeconds).to.equal(10); + }); + + it('has a bucket size of 1 minute for metrics data', () => { + const firstTimerange = throughputMetrics.currentPeriod[0].x; + const secondTimerange = throughputMetrics.currentPeriod[1].x; + const timeIntervalAsMinutes = (secondTimerange - firstTimerange) / 1000 / 60; + expect(timeIntervalAsMinutes).to.equal(1); }); - throughputResponse = response.body; }); - it('returns some data', () => { - expect(throughputResponse.currentPeriod.length).to.be.greaterThan(0); - expect(throughputResponse.previousPeriod.length).to.be.greaterThan(0); + describe('production environment', () => { + let throughput: ThroughputReturn; - const hasCurrentPeriodData = throughputResponse.currentPeriod.some(({ y }) => - isFiniteNumber(y) - ); - const hasPreviousPeriodData = throughputResponse.previousPeriod.some(({ y }) => - isFiniteNumber(y) - ); + before(async () => { + const throughputResponse = await callApi({ query: { environment: 'production' } }); + throughput = throughputResponse.body; + }); - expect(hasCurrentPeriodData).to.equal(true); - expect(hasPreviousPeriodData).to.equal(true); + it('returns some data', () => { + expect(throughput.currentPeriod.length).to.be.greaterThan(0); + const hasData = throughput.currentPeriod.some(({ y }) => isFiniteNumber(y)); + expect(hasData).to.equal(true); + }); + + it('returns correct average throughput', () => { + const throughputMean = meanBy(throughput.currentPeriod, 'y'); + expect(roundNumber(throughputMean)).to.be.equal(roundNumber(GO_PROD_RATE)); + }); }); - it('has same start time for both periods', () => { - expect(first(throughputResponse.currentPeriod)?.x).to.equal( - first(throughputResponse.previousPeriod)?.x - ); + describe('when synth-java is selected', () => { + let throughput: ThroughputReturn; + + before(async () => { + const throughputResponse = await callApi({ path: { serviceName: 'synth-java' } }); + throughput = throughputResponse.body; + }); + + it('returns some data', () => { + expect(throughput.currentPeriod.length).to.be.greaterThan(0); + const hasData = throughput.currentPeriod.some(({ y }) => isFiniteNumber(y)); + expect(hasData).to.equal(true); + }); + + it('returns throughput related to java agent', () => { + const throughputMean = meanBy(throughput.currentPeriod, 'y'); + expect(roundNumber(throughputMean)).to.be.equal(roundNumber(JAVA_PROD_RATE)); + }); }); - it('has same end time for both periods', () => { - expect(last(throughputResponse.currentPeriod)?.x).to.equal( - last(throughputResponse.previousPeriod)?.x - ); - }); + describe('time comparisons', () => { + let throughputResponse: ThroughputReturn; - it('returns same number of buckets for both periods', () => { - expect(throughputResponse.currentPeriod.length).to.be( - throughputResponse.previousPeriod.length - ); - }); + before(async () => { + const response = await callApi({ + query: { + start: moment(end).subtract(7, 'minutes').toISOString(), + end: new Date(end).toISOString(), + comparisonStart: new Date(start).toISOString(), + comparisonEnd: moment(start).add(7, 'minutes').toISOString(), + }, + }); + throughputResponse = response.body; + }); - it('has same mean value for both periods', () => { - const currentPeriodMean = meanBy( - throughputResponse.currentPeriod.filter((item) => isFiniteNumber(item.y) && item.y > 0), - 'y' - ); - const previousPeriodMean = meanBy( - throughputResponse.previousPeriod.filter( - (item) => isFiniteNumber(item.y) && item.y > 0 - ), - 'y' - ); - const currentPeriod = throughputResponse.currentPeriod; - const bucketSize = currentPeriod[1].x - currentPeriod[0].x; - const durationAsMinutes = bucketSize / 1000 / 60; - [currentPeriodMean, previousPeriodMean].every((value) => - expect(roundNumber(value)).to.be.equal( - roundNumber((GO_PROD_RATE + GO_DEV_RATE) / durationAsMinutes) - ) - ); + it('returns some data', () => { + expect(throughputResponse.currentPeriod.length).to.be.greaterThan(0); + expect(throughputResponse.previousPeriod.length).to.be.greaterThan(0); + + const hasCurrentPeriodData = throughputResponse.currentPeriod.some(({ y }) => + isFiniteNumber(y) + ); + const hasPreviousPeriodData = throughputResponse.previousPeriod.some(({ y }) => + isFiniteNumber(y) + ); + + expect(hasCurrentPeriodData).to.equal(true); + expect(hasPreviousPeriodData).to.equal(true); + }); + + it('has same start time for both periods', () => { + expect(first(throughputResponse.currentPeriod)?.x).to.equal( + first(throughputResponse.previousPeriod)?.x + ); + }); + + it('has same end time for both periods', () => { + expect(last(throughputResponse.currentPeriod)?.x).to.equal( + last(throughputResponse.previousPeriod)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + expect(throughputResponse.currentPeriod.length).to.be( + throughputResponse.previousPeriod.length + ); + }); + + it('has same mean value for both periods', () => { + const currentPeriodMean = meanBy( + throughputResponse.currentPeriod.filter( + (item) => isFiniteNumber(item.y) && item.y > 0 + ), + 'y' + ); + const previousPeriodMean = meanBy( + throughputResponse.previousPeriod.filter( + (item) => isFiniteNumber(item.y) && item.y > 0 + ), + 'y' + ); + const currentPeriod = throughputResponse.currentPeriod; + const bucketSize = currentPeriod[1].x - currentPeriod[0].x; + const durationAsMinutes = bucketSize / 1000 / 60; + [currentPeriodMean, previousPeriodMean].every((value) => + expect(roundNumber(value)).to.be.equal( + roundNumber((GO_PROD_RATE + GO_DEV_RATE) / durationAsMinutes) + ) + ); + }); }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.ts b/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.ts index 1aa3ebb7b985..4cc9b2f0679f 100644 --- a/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.ts +++ b/x-pack/test/apm_api_integration/tests/throughput/dependencies_apis.ts @@ -88,7 +88,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when( 'Dependencies throughput value', - { config: 'basic', archives: ['apm_8.0.0_empty'] }, + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { describe('when data is loaded', () => { const GO_PROD_RATE = 75; diff --git a/x-pack/test/apm_api_integration/tests/throughput/service_apis.ts b/x-pack/test/apm_api_integration/tests/throughput/service_apis.ts index ea5252d3490c..c4fffd8d79af 100644 --- a/x-pack/test/apm_api_integration/tests/throughput/service_apis.ts +++ b/x-pack/test/apm_api_integration/tests/throughput/service_apis.ts @@ -104,7 +104,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { let throughputMetricValues: PromiseReturnType; let throughputTransactionValues: PromiseReturnType; - registry.when('Services APIs', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { + registry.when('Services APIs', { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, () => { describe('when data is loaded ', () => { const GO_PROD_RATE = 80; const GO_DEV_RATE = 20; diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts index fe4058004691..e877afc07005 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.ts @@ -76,175 +76,181 @@ export default function ApiTest({ getService }: FtrProviderContext) { } ); - registry.when('data is loaded', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { - describe('transactions groups detailed stats', () => { - const GO_PROD_RATE = 75; - const GO_PROD_ERROR_RATE = 25; - before(async () => { - const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( - 'instance-a' - ); - - const transactionName = 'GET /api/product/list'; - - await synthtraceEsClient.index([ - ...timerange(start, end) - .interval('1m') - .rate(GO_PROD_RATE) - .flatMap((timestamp) => - serviceGoProdInstance - .transaction(transactionName) - .timestamp(timestamp) - .duration(1000) - .success() - .serialize() - ), - ...timerange(start, end) - .interval('1m') - .rate(GO_PROD_ERROR_RATE) - .flatMap((timestamp) => - serviceGoProdInstance - .transaction(transactionName) - .duration(1000) - .timestamp(timestamp) - .failure() - .serialize() - ), - ]); - }); - - after(() => synthtraceEsClient.clean()); - - describe('without comparisons', () => { - let transactionsStatistics: TransactionsGroupsDetailedStatistics; - let metricsStatistics: TransactionsGroupsDetailedStatistics; + registry.when( + 'data is loaded', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + describe('transactions groups detailed stats', () => { + const GO_PROD_RATE = 75; + const GO_PROD_ERROR_RATE = 25; before(async () => { - [metricsStatistics, transactionsStatistics] = await Promise.all([ - callApi({ query: { kuery: 'processor.event : "metric"' } }), - callApi({ query: { kuery: 'processor.event : "transaction"' } }), + const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( + 'instance-a' + ); + + const transactionName = 'GET /api/product/list'; + + await synthtraceEsClient.index([ + ...timerange(start, end) + .interval('1m') + .rate(GO_PROD_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionName) + .timestamp(timestamp) + .duration(1000) + .success() + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(GO_PROD_ERROR_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction(transactionName) + .duration(1000) + .timestamp(timestamp) + .failure() + .serialize() + ), ]); }); - it('returns some transactions data', () => { - expect(isEmpty(transactionsStatistics.currentPeriod)).to.be.equal(false); - }); + after(() => synthtraceEsClient.clean()); - it('returns some metrics data', () => { - expect(isEmpty(metricsStatistics.currentPeriod)).to.be.equal(false); - }); + describe('without comparisons', () => { + let transactionsStatistics: TransactionsGroupsDetailedStatistics; + let metricsStatistics: TransactionsGroupsDetailedStatistics; + before(async () => { + [metricsStatistics, transactionsStatistics] = await Promise.all([ + callApi({ query: { kuery: 'processor.event : "metric"' } }), + callApi({ query: { kuery: 'processor.event : "transaction"' } }), + ]); + }); - it('has same latency mean value for metrics and transactions data', () => { - const transactionsCurrentPeriod = - transactionsStatistics.currentPeriod[transactionNames[0]]; - const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; - const transactionsLatencyMean = meanBy(transactionsCurrentPeriod.latency, 'y'); - const metricsLatencyMean = meanBy(metricsCurrentPeriod.latency, 'y'); - [transactionsLatencyMean, metricsLatencyMean].forEach((value) => - expect(value).to.be.equal(1000000) - ); - }); + it('returns some transactions data', () => { + expect(isEmpty(transactionsStatistics.currentPeriod)).to.be.equal(false); + }); - it('has same error rate mean value for metrics and transactions data', () => { - const transactionsCurrentPeriod = - transactionsStatistics.currentPeriod[transactionNames[0]]; - const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; + it('returns some metrics data', () => { + expect(isEmpty(metricsStatistics.currentPeriod)).to.be.equal(false); + }); - const transactionsErrorRateMean = meanBy(transactionsCurrentPeriod.errorRate, 'y'); - const metricsErrorRateMean = meanBy(metricsCurrentPeriod.errorRate, 'y'); - [transactionsErrorRateMean, metricsErrorRateMean].forEach((value) => - expect(asPercent(value, 1)).to.be.equal(`${GO_PROD_ERROR_RATE}%`) - ); - }); + it('has same latency mean value for metrics and transactions data', () => { + const transactionsCurrentPeriod = + transactionsStatistics.currentPeriod[transactionNames[0]]; + const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; + const transactionsLatencyMean = meanBy(transactionsCurrentPeriod.latency, 'y'); + const metricsLatencyMean = meanBy(metricsCurrentPeriod.latency, 'y'); + [transactionsLatencyMean, metricsLatencyMean].forEach((value) => + expect(value).to.be.equal(1000000) + ); + }); - it('has same throughput mean value for metrics and transactions data', () => { - const transactionsCurrentPeriod = - transactionsStatistics.currentPeriod[transactionNames[0]]; - const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; - const transactionsThroughputMean = roundNumber( - meanBy(transactionsCurrentPeriod.throughput, 'y') - ); - const metricsThroughputMean = roundNumber(meanBy(metricsCurrentPeriod.throughput, 'y')); - [transactionsThroughputMean, metricsThroughputMean].forEach((value) => - expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_PROD_ERROR_RATE)) - ); - }); + it('has same error rate mean value for metrics and transactions data', () => { + const transactionsCurrentPeriod = + transactionsStatistics.currentPeriod[transactionNames[0]]; + const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; - it('has same impact value for metrics and transactions data', () => { - const transactionsCurrentPeriod = - transactionsStatistics.currentPeriod[transactionNames[0]]; - const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; + const transactionsErrorRateMean = meanBy(transactionsCurrentPeriod.errorRate, 'y'); + const metricsErrorRateMean = meanBy(metricsCurrentPeriod.errorRate, 'y'); + [transactionsErrorRateMean, metricsErrorRateMean].forEach((value) => + expect(asPercent(value, 1)).to.be.equal(`${GO_PROD_ERROR_RATE}%`) + ); + }); - const transactionsImpact = transactionsCurrentPeriod.impact; - const metricsImpact = metricsCurrentPeriod.impact; - [transactionsImpact, metricsImpact].forEach((value) => expect(value).to.be.equal(100)); - }); - }); + it('has same throughput mean value for metrics and transactions data', () => { + const transactionsCurrentPeriod = + transactionsStatistics.currentPeriod[transactionNames[0]]; + const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; + const transactionsThroughputMean = roundNumber( + meanBy(transactionsCurrentPeriod.throughput, 'y') + ); + const metricsThroughputMean = roundNumber(meanBy(metricsCurrentPeriod.throughput, 'y')); + [transactionsThroughputMean, metricsThroughputMean].forEach((value) => + expect(value).to.be.equal(roundNumber(GO_PROD_RATE + GO_PROD_ERROR_RATE)) + ); + }); - describe('with comparisons', () => { - let transactionsStatistics: TransactionsGroupsDetailedStatistics; - before(async () => { - transactionsStatistics = await callApi({ - query: { - start: moment(end).subtract(7, 'minutes').toISOString(), - end: new Date(end).toISOString(), - comparisonStart: new Date(start).toISOString(), - comparisonEnd: moment(start).add(7, 'minutes').toISOString(), - }, + it('has same impact value for metrics and transactions data', () => { + const transactionsCurrentPeriod = + transactionsStatistics.currentPeriod[transactionNames[0]]; + const metricsCurrentPeriod = metricsStatistics.currentPeriod[transactionNames[0]]; + + const transactionsImpact = transactionsCurrentPeriod.impact; + const metricsImpact = metricsCurrentPeriod.impact; + [transactionsImpact, metricsImpact].forEach((value) => expect(value).to.be.equal(100)); }); }); - it('returns some data for both periods', () => { - expect(isEmpty(transactionsStatistics.currentPeriod)).to.be.equal(false); - expect(isEmpty(transactionsStatistics.previousPeriod)).to.be.equal(false); - }); - - it('has same start time for both periods', () => { - const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; - const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; - [ - [currentPeriod.latency, previousPeriod.latency], - [currentPeriod.errorRate, previousPeriod.errorRate], - [currentPeriod.throughput, previousPeriod.throughput], - ].forEach(([currentTimeseries, previousTimeseries]) => { - const firstCurrentPeriodDate = new Date( - first(currentTimeseries)?.x ?? NaN - ).toISOString(); - const firstPreviousPeriodDate = new Date( - first(previousPeriod.latency)?.x ?? NaN - ).toISOString(); - - expect(firstCurrentPeriodDate).to.equal(firstPreviousPeriodDate); + describe('with comparisons', () => { + let transactionsStatistics: TransactionsGroupsDetailedStatistics; + before(async () => { + transactionsStatistics = await callApi({ + query: { + start: moment(end).subtract(7, 'minutes').toISOString(), + end: new Date(end).toISOString(), + comparisonStart: new Date(start).toISOString(), + comparisonEnd: moment(start).add(7, 'minutes').toISOString(), + }, + }); }); - }); - it('has same end time for both periods', () => { - const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; - const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; - [ - [currentPeriod.latency, previousPeriod.latency], - [currentPeriod.errorRate, previousPeriod.errorRate], - [currentPeriod.throughput, previousPeriod.throughput], - ].forEach(([currentTimeseries, previousTimeseries]) => { - const lastCurrentPeriodDate = new Date(last(currentTimeseries)?.x ?? NaN).toISOString(); - const lastPreviousPeriodDate = new Date( - last(previousPeriod.latency)?.x ?? NaN - ).toISOString(); - expect(lastCurrentPeriodDate).to.equal(lastPreviousPeriodDate); + it('returns some data for both periods', () => { + expect(isEmpty(transactionsStatistics.currentPeriod)).to.be.equal(false); + expect(isEmpty(transactionsStatistics.previousPeriod)).to.be.equal(false); }); - }); - it('returns same number of buckets for both periods', () => { - const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; - const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; - [ - [currentPeriod.latency, previousPeriod.latency], - [currentPeriod.errorRate, previousPeriod.errorRate], - [currentPeriod.throughput, previousPeriod.throughput], - ].forEach(([currentTimeseries, previousTimeseries]) => { - expect(currentTimeseries.length).to.equal(previousTimeseries.length); + it('has same start time for both periods', () => { + const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; + const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; + [ + [currentPeriod.latency, previousPeriod.latency], + [currentPeriod.errorRate, previousPeriod.errorRate], + [currentPeriod.throughput, previousPeriod.throughput], + ].forEach(([currentTimeseries, previousTimeseries]) => { + const firstCurrentPeriodDate = new Date( + first(currentTimeseries)?.x ?? NaN + ).toISOString(); + const firstPreviousPeriodDate = new Date( + first(previousPeriod.latency)?.x ?? NaN + ).toISOString(); + + expect(firstCurrentPeriodDate).to.equal(firstPreviousPeriodDate); + }); + }); + it('has same end time for both periods', () => { + const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; + const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; + [ + [currentPeriod.latency, previousPeriod.latency], + [currentPeriod.errorRate, previousPeriod.errorRate], + [currentPeriod.throughput, previousPeriod.throughput], + ].forEach(([currentTimeseries, previousTimeseries]) => { + const lastCurrentPeriodDate = new Date( + last(currentTimeseries)?.x ?? NaN + ).toISOString(); + const lastPreviousPeriodDate = new Date( + last(previousPeriod.latency)?.x ?? NaN + ).toISOString(); + + expect(lastCurrentPeriodDate).to.equal(lastPreviousPeriodDate); + }); + }); + + it('returns same number of buckets for both periods', () => { + const currentPeriod = transactionsStatistics.currentPeriod[transactionNames[0]]; + const previousPeriod = transactionsStatistics.previousPeriod[transactionNames[0]]; + [ + [currentPeriod.latency, previousPeriod.latency], + [currentPeriod.errorRate, previousPeriod.errorRate], + [currentPeriod.throughput, previousPeriod.throughput], + ].forEach(([currentTimeseries, previousTimeseries]) => { + expect(currentTimeseries.length).to.equal(previousTimeseries.length); + }); }); }); }); - }); - }); + } + ); }