mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[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:
parent
322e2a48aa
commit
9b5efb2733
8 changed files with 334 additions and 113 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
})),
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue