[APM] Errors api test: error_groups/main_statistics (#116337)

* use apmApiClient

* refacroting

* fixing errors groups main statistics tests

* refactoring

* fixing error group detailed stats test

* fixing ts issue

* renaming empty archiver
This commit is contained in:
Cauê Marcondes 2021-10-28 11:20:51 -04:00 committed by GitHub
parent a23d5e29a4
commit 155a16ed2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 859 additions and 761 deletions

View file

@ -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'

View file

@ -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<typeof synthtraceEsClient>;

View file

@ -114,7 +114,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
let errorRateMetricValues: PromiseReturnType<typeof getErrorRateValues>;
let errorTransactionValues: PromiseReturnType<typeof getErrorRateValues>;
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;

View file

@ -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 () {

View file

@ -116,7 +116,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
let latencyMetricValues: PromiseReturnType<typeof getLatencyValues>;
let latencyTransactionValues: PromiseReturnType<typeof getLatencyValues>;
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;

View file

@ -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<typeof getThroughputValues>;
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<typeof getThroughputValues>;
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))
);
});
});
});
});
});
}
);
}

View file

@ -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;

View file

@ -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
);
});
});
});
}
);
}

View file

@ -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());
});
});
});
});
}
);
}

View file

@ -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()
),
]);
}

View file

@ -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<ApmServices['apmApiClient']>;
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: '',

View file

@ -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: {} });
});
}
);
}

View file

@ -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,
}
`);
});
}
);
}

View file

@ -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)
)
);
});
});
});
});
});
}
);
}

View file

@ -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;

View file

@ -104,7 +104,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
let throughputMetricValues: PromiseReturnType<typeof getThroughputValues>;
let throughputTransactionValues: PromiseReturnType<typeof getThroughputValues>;
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;

View file

@ -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);
});
});
});
});
});
});
}
);
}