[APM] Add comparision to service maps popover (#120839)

* adding comparison to service maps

* adding tests

* addressing pr comments

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Cauê Marcondes 2021-12-13 11:39:24 -05:00 committed by GitHub
parent 322e2a48aa
commit 9b5efb2733
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 334 additions and 113 deletions

View file

@ -12,12 +12,20 @@ import { METRIC_TYPE } from '@kbn/analytics';
import React from 'react';
import { useUiTracker } from '../../../../../../observability/public';
import { ContentsProps } from '.';
import { NodeStats } from '../../../../../common/service_map';
import { useAnyOfApmParams } from '../../../../hooks/use_apm_params';
import { useApmRouter } from '../../../../hooks/use_apm_router';
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
import { ApmRoutes } from '../../../routing/apm_route_config';
import { StatsList } from './stats_list';
import { getTimeRangeComparison } from '../../../shared/time_comparison/get_time_range_comparison';
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
type BackendReturn = APIReturnType<'GET /internal/apm/service-map/backend'>;
const INITIAL_STATE: Partial<BackendReturn> = {
currentPeriod: undefined,
previousPeriod: undefined,
};
export function BackendContents({
nodeData,
@ -30,11 +38,20 @@ export function BackendContents({
'/services/{serviceName}/service-map'
);
const { comparisonEnabled, comparisonType } = query;
const { offset } = getTimeRangeComparison({
start,
end,
comparisonEnabled,
comparisonType,
});
const apmRouter = useApmRouter();
const backendName = nodeData.label;
const { data = { transactionStats: {} } as NodeStats, status } = useFetcher(
const { data = INITIAL_STATE, status } = useFetcher(
(callApmApi) => {
if (backendName) {
return callApmApi({
@ -45,15 +62,13 @@ export function BackendContents({
environment,
start,
end,
offset,
},
},
});
}
},
[environment, backendName, start, end],
{
preservePreviousData: false,
}
[environment, backendName, start, end, offset]
);
const isLoading = status === FETCH_STATUS.LOADING;

View file

@ -17,12 +17,21 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { useApmParams } from '../../../../hooks/use_apm_params';
import type { ContentsProps } from '.';
import { NodeStats } from '../../../../../common/service_map';
import { useApmRouter } from '../../../../hooks/use_apm_router';
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
import { AnomalyDetection } from './anomaly_detection';
import { StatsList } from './stats_list';
import { useTimeRange } from '../../../../hooks/use_time_range';
import { getTimeRangeComparison } from '../../../shared/time_comparison/get_time_range_comparison';
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
type ServiceNodeReturn =
APIReturnType<'GET /internal/apm/service-map/service/{serviceName}'>;
const INITIAL_STATE: ServiceNodeReturn = {
currentPeriod: {},
previousPeriod: undefined,
};
export function ServiceContents({
onFocusClick,
@ -42,28 +51,32 @@ export function ServiceContents({
throw new Error('Expected rangeFrom and rangeTo to be set');
}
const { rangeFrom, rangeTo } = query;
const { rangeFrom, rangeTo, comparisonEnabled, comparisonType } = query;
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const { offset } = getTimeRangeComparison({
start,
end,
comparisonEnabled,
comparisonType,
});
const serviceName = nodeData.id!;
const { data = { transactionStats: {} } as NodeStats, status } = useFetcher(
const { data = INITIAL_STATE, status } = useFetcher(
(callApmApi) => {
if (serviceName && start && end) {
return callApmApi({
endpoint: 'GET /internal/apm/service-map/service/{serviceName}',
params: {
path: { serviceName },
query: { environment, start, end },
query: { environment, start, end, offset },
},
});
}
},
[environment, serviceName, start, end],
{
preservePreviousData: false,
}
[environment, serviceName, start, end, offset]
);
const isLoading = status === FETCH_STATUS.LOADING;

View file

@ -14,15 +14,18 @@ import {
import { i18n } from '@kbn/i18n';
import { isNumber } from 'lodash';
import React, { useMemo } from 'react';
import { NodeStats } from '../../../../../common/service_map';
import {
asDuration,
asPercent,
asTransactionRate,
} from '../../../../../common/utils/formatters';
import { Coordinate } from '../../../../../typings/timeseries';
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
import { SparkPlot, Color } from '../../../shared/charts/spark_plot';
type ServiceNodeReturn =
APIReturnType<'GET /internal/apm/service-map/service/{serviceName}'>;
function LoadingSpinner() {
return (
<EuiFlexGroup
@ -47,19 +50,21 @@ function NoDataMessage() {
interface StatsListProps {
isLoading: boolean;
data: NodeStats;
data: Partial<ServiceNodeReturn>;
}
interface Item {
title: string;
valueLabel: string | null;
timeseries?: Coordinate[];
previousPeriodTimeseries?: Coordinate[];
color: Color;
}
export function StatsList({ data, isLoading }: StatsListProps) {
const { currentPeriod = {}, previousPeriod } = data;
const { cpuUsage, failedTransactionsRate, memoryUsage, transactionStats } =
data;
currentPeriod;
const hasData = [
cpuUsage?.value,
@ -78,10 +83,10 @@ export function StatsList({ data, isLoading }: StatsListProps) {
defaultMessage: 'Latency (avg.)',
}
),
valueLabel: isNumber(transactionStats?.latency?.value)
? asDuration(transactionStats?.latency?.value)
: null,
timeseries: transactionStats?.latency?.timeseries,
valueLabel: asDuration(currentPeriod?.transactionStats?.latency?.value),
timeseries: currentPeriod?.transactionStats?.latency?.timeseries,
previousPeriodTimeseries:
previousPeriod?.transactionStats?.latency?.timeseries,
color: 'euiColorVis1',
},
{
@ -91,24 +96,35 @@ export function StatsList({ data, isLoading }: StatsListProps) {
defaultMessage: 'Throughput (avg.)',
}
),
valueLabel: asTransactionRate(transactionStats?.throughput?.value),
timeseries: transactionStats?.throughput?.timeseries,
valueLabel: asTransactionRate(
currentPeriod?.transactionStats?.throughput?.value
),
timeseries: currentPeriod?.transactionStats?.throughput?.timeseries,
previousPeriodTimeseries:
previousPeriod?.transactionStats?.throughput?.timeseries,
color: 'euiColorVis0',
},
{
title: i18n.translate('xpack.apm.serviceMap.errorRatePopoverStat', {
defaultMessage: 'Failed transaction rate (avg.)',
}),
valueLabel: asPercent(failedTransactionsRate?.value, 1, ''),
timeseries: failedTransactionsRate?.timeseries,
valueLabel: asPercent(
currentPeriod?.failedTransactionsRate?.value,
1,
''
),
timeseries: currentPeriod?.failedTransactionsRate?.timeseries,
previousPeriodTimeseries:
previousPeriod?.failedTransactionsRate?.timeseries,
color: 'euiColorVis7',
},
{
title: i18n.translate('xpack.apm.serviceMap.avgCpuUsagePopoverStat', {
defaultMessage: 'CPU usage (avg.)',
}),
valueLabel: asPercent(cpuUsage?.value, 1, ''),
timeseries: cpuUsage?.timeseries,
valueLabel: asPercent(currentPeriod?.cpuUsage?.value, 1, ''),
timeseries: currentPeriod?.cpuUsage?.timeseries,
previousPeriodTimeseries: previousPeriod?.cpuUsage?.timeseries,
color: 'euiColorVis3',
},
{
@ -118,15 +134,16 @@ export function StatsList({ data, isLoading }: StatsListProps) {
defaultMessage: 'Memory usage (avg.)',
}
),
valueLabel: asPercent(memoryUsage?.value, 1, ''),
timeseries: memoryUsage?.timeseries,
valueLabel: asPercent(currentPeriod?.memoryUsage?.value, 1, ''),
timeseries: currentPeriod?.memoryUsage?.timeseries,
previousPeriodTimeseries: previousPeriod?.memoryUsage?.timeseries,
color: 'euiColorVis8',
},
],
[cpuUsage, failedTransactionsRate, memoryUsage, transactionStats]
[currentPeriod, previousPeriod]
);
if (isLoading) {
if (isLoading && !hasData) {
return <LoadingSpinner />;
}
@ -136,38 +153,47 @@ export function StatsList({ data, isLoading }: StatsListProps) {
return (
<EuiFlexGroup direction="column" responsive={false} gutterSize="m">
{items.map(({ title, valueLabel, timeseries, color }) => {
if (!valueLabel) {
return null;
{items.map(
({
title,
valueLabel,
timeseries,
color,
previousPeriodTimeseries,
}) => {
if (!valueLabel) {
return null;
}
return (
<EuiFlexItem key={title}>
<EuiFlexGroup gutterSize="none" responsive={false}>
<EuiFlexItem
style={{
display: 'flex',
justifyContent: 'end',
}}
>
<EuiText color="subdued" size="s">
{title}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{timeseries ? (
<SparkPlot
series={timeseries}
color={color}
valueLabel={valueLabel}
comparisonSeries={previousPeriodTimeseries}
/>
) : (
<div>{valueLabel}</div>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
}
return (
<EuiFlexItem key={title}>
<EuiFlexGroup gutterSize="none" responsive={false}>
<EuiFlexItem
style={{
display: 'flex',
justifyContent: 'end',
}}
>
<EuiText color="subdued" size="s">
{title}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{timeseries ? (
<SparkPlot
series={timeseries}
color={color}
valueLabel={valueLabel}
/>
) : (
<div>{valueLabel}</div>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
})}
)}
</EuiFlexGroup>
);
}

View file

@ -180,7 +180,7 @@ export function ServiceMap({
return (
<>
<SearchBar showKueryBar={false} />
<SearchBar showKueryBar={false} showTimeComparison />
<EuiPanel hasBorder={true} paddingSize="none">
<div
data-test-subj="ServiceMap"

View file

@ -21,6 +21,7 @@ import { Setup } from '../../lib/helpers/setup_request';
import { getBucketSize } from '../../lib/helpers/get_bucket_size';
import { getFailedTransactionRateTimeSeries } from '../../lib/helpers/transaction_error_rate';
import { NodeStats } from '../../../common/service_map';
import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms';
interface Options {
setup: Setup;
@ -28,6 +29,7 @@ interface Options {
backendName: string;
start: number;
end: number;
offset?: string;
}
export function getServiceMapBackendNodeInfo({
@ -36,11 +38,21 @@ export function getServiceMapBackendNodeInfo({
setup,
start,
end,
offset,
}: Options): Promise<NodeStats> {
return withApmSpan('get_service_map_backend_node_stats', async () => {
const { apmEventClient } = setup;
const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({
start,
end,
offset,
});
const { intervalString } = getBucketSize({ start, end, numBuckets: 20 });
const { intervalString } = getBucketSize({
start: startWithOffset,
end: endWithOffset,
numBuckets: 20,
});
const subAggs = {
latency_sum: {
@ -66,7 +78,7 @@ export function getServiceMapBackendNodeInfo({
bool: {
filter: [
{ term: { [SPAN_DESTINATION_SERVICE_RESOURCE]: backendName } },
...rangeQuery(start, end),
...rangeQuery(startWithOffset, endWithOffset),
...environmentQuery(environment),
],
},
@ -78,7 +90,7 @@ export function getServiceMapBackendNodeInfo({
field: '@timestamp',
fixed_interval: intervalString,
min_doc_count: 0,
extended_bounds: { min: start, max: end },
extended_bounds: { min: startWithOffset, max: endWithOffset },
},
aggs: subAggs,
},
@ -95,8 +107,8 @@ export function getServiceMapBackendNodeInfo({
const avgFailedTransactionsRate = failedTransactionsRateCount / count;
const latency = latencySum / count;
const throughput = calculateThroughputWithRange({
start,
end,
start: startWithOffset,
end: endWithOffset,
value: count,
});
@ -116,7 +128,7 @@ export function getServiceMapBackendNodeInfo({
timeseries: response.aggregations?.timeseries
? getFailedTransactionRateTimeSeries(
response.aggregations.timeseries.buckets
)
).map(({ x, y }) => ({ x: x + offsetInMs, y }))
: undefined,
},
transactionStats: {
@ -125,7 +137,7 @@ export function getServiceMapBackendNodeInfo({
timeseries: response.aggregations?.timeseries.buckets.map(
(bucket) => {
return {
x: bucket.key,
x: bucket.key + offsetInMs,
y: calculateThroughputWithRange({
start,
end,
@ -139,7 +151,7 @@ export function getServiceMapBackendNodeInfo({
value: latency,
timeseries: response.aggregations?.timeseries.buckets.map(
(bucket) => ({
x: bucket.key,
x: bucket.key + offsetInMs,
y: bucket.latency_sum.value,
})
),

View file

@ -22,6 +22,7 @@ import {
TRANSACTION_REQUEST,
} from '../../../common/transaction_types';
import { environmentQuery } from '../../../common/utils/environment_query';
import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms';
import { getBucketSizeForAggregatedTransactions } from '../../lib/helpers/get_bucket_size_for_aggregated_transactions';
import { Setup } from '../../lib/helpers/setup_request';
import {
@ -43,6 +44,7 @@ interface Options {
searchAggregatedTransactions: boolean;
start: number;
end: number;
offset?: string;
}
interface TaskParameters {
@ -57,6 +59,7 @@ interface TaskParameters {
intervalString: string;
bucketSize: number;
numBuckets: number;
offsetInMs: number;
}
export function getServiceMapServiceNodeInfo({
@ -66,11 +69,18 @@ export function getServiceMapServiceNodeInfo({
searchAggregatedTransactions,
start,
end,
offset,
}: Options): Promise<NodeStats> {
return withApmSpan('get_service_map_node_stats', async () => {
const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({
start,
end,
offset,
});
const filter: ESFilter[] = [
{ term: { [SERVICE_NAME]: serviceName } },
...rangeQuery(start, end),
...rangeQuery(startWithOffset, endWithOffset),
...environmentQuery(environment),
];
@ -90,11 +100,12 @@ export function getServiceMapServiceNodeInfo({
minutes,
serviceName,
setup,
start,
end,
start: startWithOffset,
end: endWithOffset,
intervalString,
bucketSize,
numBuckets,
offsetInMs,
};
const [failedTransactionsRate, transactionStats, cpuUsage, memoryUsage] =
@ -121,6 +132,7 @@ async function getFailedTransactionsRateStats({
start,
end,
numBuckets,
offsetInMs,
}: TaskParameters): Promise<NodeStats['failedTransactionsRate']> {
return withApmSpan('get_error_rate_for_service_map_node', async () => {
const { average, timeseries } = await getFailedTransactionRate({
@ -133,7 +145,10 @@ async function getFailedTransactionsRateStats({
kuery: '',
numBuckets,
});
return { value: average, timeseries };
return {
value: average,
timeseries: timeseries.map(({ x, y }) => ({ x: x + offsetInMs, y })),
};
});
}
@ -145,6 +160,7 @@ async function getTransactionStats({
start,
end,
intervalString,
offsetInMs,
}: TaskParameters): Promise<NodeStats['transactionStats']> {
const { apmEventClient } = setup;
@ -204,7 +220,7 @@ async function getTransactionStats({
latency: {
value: response.aggregations?.duration.value ?? null,
timeseries: response.aggregations?.timeseries.buckets.map((bucket) => ({
x: bucket.key,
x: bucket.key + offsetInMs,
y: bucket.latency.value,
})),
},
@ -212,7 +228,7 @@ async function getTransactionStats({
value: totalRequests > 0 ? totalRequests / minutes : null,
timeseries: response.aggregations?.timeseries.buckets.map((bucket) => {
return {
x: bucket.key,
x: bucket.key + offsetInMs,
y: bucket.doc_count ?? 0,
};
}),
@ -226,6 +242,7 @@ async function getCpuStats({
intervalString,
start,
end,
offsetInMs,
}: TaskParameters): Promise<NodeStats['cpuUsage']> {
const { apmEventClient } = setup;
@ -266,7 +283,7 @@ async function getCpuStats({
return {
value: response.aggregations?.avgCpuUsage.value ?? null,
timeseries: response.aggregations?.timeseries.buckets.map((bucket) => ({
x: bucket.key,
x: bucket.key + offsetInMs,
y: bucket.cpuAvg.value,
})),
};
@ -278,6 +295,7 @@ function getMemoryStats({
intervalString,
start,
end,
offsetInMs,
}: TaskParameters) {
return withApmSpan('get_memory_stats_for_service_map_node', async () => {
const { apmEventClient } = setup;
@ -324,7 +342,7 @@ function getMemoryStats({
return {
value: response.aggregations?.avgMemoryUsage.value ?? null,
timeseries: response.aggregations?.timeseries.buckets.map((bucket) => ({
x: bucket.key,
x: bucket.key + offsetInMs,
y: bucket.memoryAvg.value,
})),
};

View file

@ -17,7 +17,7 @@ import { getServiceMapBackendNodeInfo } from './get_service_map_backend_node_inf
import { getServiceMapServiceNodeInfo } from './get_service_map_service_node_info';
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository';
import { environmentRt, rangeRt } from '../default_api_types';
import { environmentRt, offsetRt, rangeRt } from '../default_api_types';
const serviceMapRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/service-map',
@ -75,7 +75,7 @@ const serviceMapServiceNodeRoute = createApmServerRoute({
path: t.type({
serviceName: t.string,
}),
query: t.intersection([environmentRt, rangeRt]),
query: t.intersection([environmentRt, rangeRt, offsetRt]),
}),
options: { tags: ['access:apm'] },
handler: async (resources) => {
@ -91,7 +91,7 @@ const serviceMapServiceNodeRoute = createApmServerRoute({
const {
path: { serviceName },
query: { environment, start, end },
query: { environment, start, end, offset },
} = params;
const searchAggregatedTransactions = await getSearchAggregatedTransactions({
@ -102,14 +102,23 @@ const serviceMapServiceNodeRoute = createApmServerRoute({
kuery: '',
});
return getServiceMapServiceNodeInfo({
const commonProps = {
environment,
setup,
serviceName,
searchAggregatedTransactions,
start,
end,
});
};
const [currentPeriod, previousPeriod] = await Promise.all([
getServiceMapServiceNodeInfo(commonProps),
offset
? getServiceMapServiceNodeInfo({ ...commonProps, offset })
: undefined,
]);
return { currentPeriod, previousPeriod };
},
});
@ -120,6 +129,7 @@ const serviceMapBackendNodeRoute = createApmServerRoute({
t.type({ backendName: t.string }),
environmentRt,
rangeRt,
offsetRt,
]),
}),
options: { tags: ['access:apm'] },
@ -135,16 +145,19 @@ const serviceMapBackendNodeRoute = createApmServerRoute({
const setup = await setupRequest(resources);
const {
query: { backendName, environment, start, end },
query: { backendName, environment, start, end, offset },
} = params;
return getServiceMapBackendNodeInfo({
environment,
setup,
backendName,
start,
end,
});
const commonProps = { environment, setup, backendName, start, end };
const [currentPeriod, previousPeriod] = await Promise.all([
getServiceMapBackendNodeInfo(commonProps),
offset
? getServiceMapBackendNodeInfo({ ...commonProps, offset })
: undefined,
]);
return { currentPeriod, previousPeriod };
},
});

View file

@ -6,7 +6,7 @@
*/
import expect from '@kbn/expect';
import { isEmpty, orderBy, uniq } from 'lodash';
import { first, isEmpty, last, orderBy, uniq } from 'lodash';
import { ServiceConnectionNode } from '../../../../plugins/apm/common/service_map';
import { ApmApiError, SupertestReturnType } from '../../common/apm_api_supertest';
import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata';
@ -90,11 +90,11 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext)
it('returns an object with nulls', async () => {
[
response.body.failedTransactionsRate?.value,
response.body.memoryUsage?.value,
response.body.cpuUsage?.value,
response.body.transactionStats?.latency?.value,
response.body.transactionStats?.throughput?.value,
response.body.currentPeriod?.failedTransactionsRate?.value,
response.body.currentPeriod?.memoryUsage?.value,
response.body.currentPeriod?.cpuUsage?.value,
response.body.currentPeriod?.transactionStats?.latency?.value,
response.body.currentPeriod?.transactionStats?.throughput?.value,
].forEach((value) => {
expect(value).to.be.eql(null);
});
@ -122,7 +122,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext)
});
it('returns undefined values', () => {
expect(response.body).to.eql({ transactionStats: {} });
expect(response.body.currentPeriod).to.eql({ transactionStats: {} });
});
});
});
@ -343,23 +343,31 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext)
});
it('returns some error rate', () => {
expect(response.body.failedTransactionsRate?.value).to.eql(0);
expect(response.body.failedTransactionsRate?.timeseries?.length).to.be.greaterThan(0);
expect(response.body.currentPeriod?.failedTransactionsRate?.value).to.eql(0);
expect(
response.body.currentPeriod?.failedTransactionsRate?.timeseries?.length
).to.be.greaterThan(0);
});
it('returns some latency', () => {
expect(response.body.transactionStats?.latency?.value).to.be.greaterThan(0);
expect(response.body.transactionStats?.latency?.timeseries?.length).to.be.greaterThan(0);
expect(response.body.currentPeriod?.transactionStats?.latency?.value).to.be.greaterThan(0);
expect(
response.body.currentPeriod?.transactionStats?.latency?.timeseries?.length
).to.be.greaterThan(0);
});
it('returns some throughput', () => {
expect(response.body.transactionStats?.throughput?.value).to.be.greaterThan(0);
expect(response.body.transactionStats?.throughput?.timeseries?.length).to.be.greaterThan(0);
expect(response.body.currentPeriod?.transactionStats?.throughput?.value).to.be.greaterThan(
0
);
expect(
response.body.currentPeriod?.transactionStats?.throughput?.timeseries?.length
).to.be.greaterThan(0);
});
it('returns some cpu usage', () => {
expect(response.body.cpuUsage?.value).to.be.greaterThan(0);
expect(response.body.cpuUsage?.timeseries?.length).to.be.greaterThan(0);
expect(response.body.currentPeriod?.cpuUsage?.value).to.be.greaterThan(0);
expect(response.body.currentPeriod?.cpuUsage?.timeseries?.length).to.be.greaterThan(0);
});
});
@ -384,18 +392,134 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext)
});
it('returns some error rate', () => {
expect(response.body.failedTransactionsRate?.value).to.eql(0);
expect(response.body.failedTransactionsRate?.timeseries?.length).to.be.greaterThan(0);
expect(response.body.currentPeriod?.failedTransactionsRate?.value).to.eql(0);
expect(
response.body.currentPeriod?.failedTransactionsRate?.timeseries?.length
).to.be.greaterThan(0);
});
it('returns some latency', () => {
expect(response.body.transactionStats?.latency?.value).to.be.greaterThan(0);
expect(response.body.transactionStats?.latency?.timeseries?.length).to.be.greaterThan(0);
expect(response.body.currentPeriod?.transactionStats?.latency?.value).to.be.greaterThan(0);
expect(
response.body.currentPeriod?.transactionStats?.latency?.timeseries?.length
).to.be.greaterThan(0);
});
it('returns some throughput', () => {
expect(response.body.transactionStats?.throughput?.value).to.be.greaterThan(0);
expect(response.body.transactionStats?.throughput?.timeseries?.length).to.be.greaterThan(0);
expect(response.body.currentPeriod?.transactionStats?.throughput?.value).to.be.greaterThan(
0
);
expect(
response.body.currentPeriod?.transactionStats?.throughput?.timeseries?.length
).to.be.greaterThan(0);
});
});
describe('With comparison', () => {
describe('/internal/apm/service-map/backend', () => {
let response: BackendResponse;
before(async () => {
response = await apmApiClient.readUser({
endpoint: `GET /internal/apm/service-map/backend`,
params: {
query: {
backendName: 'postgresql',
start: metadata.start,
end: metadata.end,
environment: 'ENVIRONMENT_ALL',
offset: '5m',
},
},
});
});
it('returns some data', () => {
const { currentPeriod, previousPeriod } = response.body;
[
currentPeriod.failedTransactionsRate,
previousPeriod?.failedTransactionsRate,
currentPeriod.transactionStats?.latency,
previousPeriod?.transactionStats?.latency,
currentPeriod.transactionStats?.throughput,
previousPeriod?.transactionStats?.throughput,
].map((value) => expect(value?.timeseries?.length).to.be.greaterThan(0));
});
it('has same start time for both periods', () => {
const { currentPeriod, previousPeriod } = response.body;
expect(first(currentPeriod.failedTransactionsRate?.timeseries)?.x).to.equal(
first(previousPeriod?.failedTransactionsRate?.timeseries)?.x
);
});
it('has same end time for both periods', () => {
const { currentPeriod, previousPeriod } = response.body;
expect(last(currentPeriod.failedTransactionsRate?.timeseries)?.x).to.equal(
last(previousPeriod?.failedTransactionsRate?.timeseries)?.x
);
});
it('returns same number of buckets for both periods', () => {
const { currentPeriod, previousPeriod } = response.body;
expect(currentPeriod.failedTransactionsRate?.timeseries?.length).to.be(
previousPeriod?.failedTransactionsRate?.timeseries?.length
);
});
});
describe('/internal/apm/service-map/service/{serviceName}', () => {
let response: ServiceNodeResponse;
before(async () => {
response = await apmApiClient.readUser({
endpoint: `GET /internal/apm/service-map/service/{serviceName}`,
params: {
path: { serviceName: 'opbeans-node' },
query: {
start: metadata.start,
end: metadata.end,
environment: 'ENVIRONMENT_ALL',
offset: '5m',
},
},
});
});
it('returns some data', () => {
const { currentPeriod, previousPeriod } = response.body;
[
currentPeriod.failedTransactionsRate,
previousPeriod?.failedTransactionsRate,
currentPeriod.transactionStats?.latency,
previousPeriod?.transactionStats?.latency,
currentPeriod.transactionStats?.throughput,
previousPeriod?.transactionStats?.throughput,
currentPeriod.cpuUsage,
previousPeriod?.cpuUsage,
currentPeriod.memoryUsage,
previousPeriod?.memoryUsage,
].map((value) => expect(value?.timeseries?.length).to.be.greaterThan(0));
});
it('has same start time for both periods', () => {
const { currentPeriod, previousPeriod } = response.body;
expect(first(currentPeriod.failedTransactionsRate?.timeseries)?.x).to.equal(
first(previousPeriod?.failedTransactionsRate?.timeseries)?.x
);
});
it('has same end time for both periods', () => {
const { currentPeriod, previousPeriod } = response.body;
expect(last(currentPeriod.failedTransactionsRate?.timeseries)?.x).to.equal(
last(previousPeriod?.failedTransactionsRate?.timeseries)?.x
);
});
it('returns same number of buckets for both periods', () => {
const { currentPeriod, previousPeriod } = response.body;
expect(currentPeriod.failedTransactionsRate?.timeseries?.length).to.be(
previousPeriod?.failedTransactionsRate?.timeseries?.length
);
});
});
});
});