[APM] Use doc_count instead of hits to get service map throughput (#129859)

* [APM] Use doc_count intead of hits to get service map throughput

* use lodash, improve api test

* fix api test and add explicit test

* fix import conflicts

* filter back the transactions per type, imporve test description

* make roundNumber return a number or null

* fix failing test
This commit is contained in:
Miriam 2022-04-19 11:25:51 +01:00 committed by GitHub
parent 7d20ffb32d
commit ef5637030f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 45 additions and 22 deletions

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { sumBy } from 'lodash';
import { ESFilter } from '@kbn/core/types/elasticsearch';
import { rangeQuery } from '@kbn/observability-plugin/server';
import {
@ -84,7 +84,7 @@ export function getServiceMapServiceNodeInfo({
...environmentQuery(environment),
];
const minutes = Math.abs((end - start) / (1000 * 60));
const minutes = (end - start) / 1000 / 60;
const numBuckets = 20;
const { intervalString, bucketSize } =
getBucketSizeForAggregatedTransactions({
@ -193,7 +193,6 @@ async function getTransactionStats({
],
},
},
track_total_hits: true,
aggs: {
duration: { avg: { field: durationField } },
timeseries: {
@ -215,7 +214,10 @@ async function getTransactionStats({
params
);
const totalRequests = response.hits.total.value;
const throughputValue = sumBy(
response.aggregations?.timeseries.buckets,
'doc_count'
);
return {
latency: {
@ -226,7 +228,7 @@ async function getTransactionStats({
})),
},
throughput: {
value: totalRequests > 0 ? totalRequests / minutes : null,
value: throughputValue ? throughputValue / minutes : null,
timeseries: response.aggregations?.timeseries.buckets.map((bucket) => {
return {
x: bucket.key + offsetInMs,

View file

@ -23,7 +23,7 @@ import {
getDurationFieldForTransactions,
getProcessorEventForTransactions,
} from '../../../lib/helpers/transactions';
import { calculateThroughput } from '../../../lib/helpers/calculate_throughput';
import { calculateThroughputWithRange } from '../../../lib/helpers/calculate_throughput';
import {
calculateFailedTransactionRate,
getOutcomeAggregation,
@ -145,7 +145,7 @@ export async function getServiceTransactionStats({
transactionErrorRate: calculateFailedTransactionRate(
topTransactionTypeBucket.outcomes
),
throughput: calculateThroughput({
throughput: calculateThroughputWithRange({
start,
end,
value: topTransactionTypeBucket.doc_count,

View file

@ -267,7 +267,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const lastValue = roundNumber(last(opbeansNode?.currentStats.latency.timeseries)?.y);
expect(firstValue).to.be(roundNumber(20 / 3));
expect(lastValue).to.be('1.000');
expect(lastValue).to.be(1);
});
it('returns postgres as an external dependency', () => {

View file

@ -6,7 +6,6 @@
*/
import { apm, timerange } from '@elastic/apm-synthtrace';
import expect from '@kbn/expect';
import { meanBy } from 'lodash';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { roundNumber } from '../../utils';
@ -19,12 +18,23 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const start = new Date('2021-01-01T00:00:00.000Z').getTime();
const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
const commonQuery = {
start: new Date(start).toISOString(),
end: new Date(end).toISOString(),
environment: 'ENVIRONMENT_ALL',
};
async function callApi() {
return await apmApiClient.readUser({
endpoint: `GET /internal/apm/service-map/service/{serviceName}`,
params: {
path: { serviceName },
query: commonQuery,
},
});
}
async function getThroughputValues(processorEvent: 'transaction' | 'metric') {
const commonQuery = {
start: new Date(start).toISOString(),
end: new Date(end).toISOString(),
environment: 'ENVIRONMENT_ALL',
};
const [serviceInventoryAPIResponse, serviceMapsNodeDetails] = await Promise.all([
apmApiClient.readUser({
endpoint: 'GET /internal/apm/services',
@ -46,10 +56,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const serviceInventoryThroughput = serviceInventoryAPIResponse.body.items[0].throughput;
const serviceMapsNodeDetailsThroughput = meanBy(
serviceMapsNodeDetails.body.currentPeriod.transactionStats?.throughput?.timeseries,
'y'
);
const serviceMapsNodeDetailsThroughput =
serviceMapsNodeDetails.body.currentPeriod.transactionStats?.throughput?.value;
return {
serviceInventoryThroughput,
@ -81,7 +89,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.rate(GO_PROD_RATE)
.generator((timestamp) =>
serviceGoProdInstance
.transaction('GET /api/product/list', 'Worker')
.transaction('GET /apple 🍎 ', 'Worker')
.duration(1000)
.timestamp(timestamp)
),
@ -90,7 +98,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.rate(GO_DEV_RATE)
.generator((timestamp) =>
serviceGoDevInstance
.transaction('GET /api/product/:id')
.transaction('GET /apple 🍎 ')
.duration(1000)
.timestamp(timestamp)
),
@ -111,7 +119,20 @@ export default function ApiTest({ getService }: FtrProviderContext) {
[
...Object.values(throughputTransactionValues),
...Object.values(throughputMetricValues),
].forEach((value) => expect(roundNumber(value)).to.be.equal(roundNumber(GO_DEV_RATE)));
].forEach((value) => expect(roundNumber(value)).to.be.equal(GO_DEV_RATE));
});
});
describe('when calling service maps transactions stats api', () => {
let serviceMapsNodeThroughput: number | null | undefined;
before(async () => {
const response = await callApi();
serviceMapsNodeThroughput =
response.body.currentPeriod.transactionStats?.throughput?.value;
});
it('returns expected throughput value', () => {
expect(roundNumber(serviceMapsNodeThroughput)).to.be.equal(GO_DEV_RATE);
});
});
});

View file

@ -10,7 +10,7 @@ import { isFiniteNumber } from '@kbn/apm-plugin/common/utils/is_finite_number';
import { Maybe } from '@kbn/apm-plugin/typings/common';
export function roundNumber(num: Maybe<number>) {
return isFiniteNumber(num) ? num.toPrecision(4) : '';
return isFiniteNumber(num) ? Number(num.toPrecision(4)) : null;
}
export function removeEmptyCoordinates(coordinates: Coordinate[]) {