[APM] add comparison to Instances latency distribution (#97710)
* adding comparion data to chart * fixing api test * addressing comments * refactoring
This commit is contained in:
parent
1fb00900e1
commit
e76987e13c
|
@ -33,15 +33,16 @@ export interface MainStatsServiceInstanceItem {
|
|||
cpuUsage: number;
|
||||
memoryUsage: number;
|
||||
}
|
||||
type ApiResponseMainStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>;
|
||||
type ApiResponseDetailedStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>;
|
||||
|
||||
const INITIAL_STATE_MAIN_STATS = {
|
||||
mainStatsItems: [] as MainStatsServiceInstanceItem[],
|
||||
mainStatsRequestId: undefined,
|
||||
mainStatsItemCount: 0,
|
||||
currentPeriodItems: [] as ApiResponseMainStats['currentPeriod'],
|
||||
previousPeriodItems: [] as ApiResponseMainStats['previousPeriod'],
|
||||
requestId: undefined,
|
||||
currentPeriodItemsCount: 0,
|
||||
};
|
||||
|
||||
type ApiResponseDetailedStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>;
|
||||
|
||||
const INITIAL_STATE_DETAILED_STATISTICS: ApiResponseDetailedStats = {
|
||||
currentPeriod: {},
|
||||
previousPeriod: {},
|
||||
|
@ -117,28 +118,17 @@ export function ServiceOverviewInstancesChartAndTable({
|
|||
start,
|
||||
end,
|
||||
transactionType,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
},
|
||||
},
|
||||
}).then((response) => {
|
||||
const mainStatsItems = orderBy(
|
||||
// need top-level sortable fields for the managed table
|
||||
response.serviceInstances.map((item) => ({
|
||||
...item,
|
||||
latency: item.latency ?? 0,
|
||||
throughput: item.throughput ?? 0,
|
||||
errorRate: item.errorRate ?? 0,
|
||||
cpuUsage: item.cpuUsage ?? 0,
|
||||
memoryUsage: item.memoryUsage ?? 0,
|
||||
})),
|
||||
field,
|
||||
direction
|
||||
).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE);
|
||||
|
||||
return {
|
||||
// Everytime the main statistics is refetched, updates the requestId making the detailed API to be refetched.
|
||||
mainStatsRequestId: uuid(),
|
||||
mainStatsItems,
|
||||
mainStatsItemCount: response.serviceInstances.length,
|
||||
requestId: uuid(),
|
||||
currentPeriodItems: response.currentPeriod,
|
||||
currentPeriodItemsCount: response.currentPeriod.length,
|
||||
previousPeriodItems: response.previousPeriod,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
@ -162,11 +152,26 @@ export function ServiceOverviewInstancesChartAndTable({
|
|||
);
|
||||
|
||||
const {
|
||||
mainStatsItems,
|
||||
mainStatsRequestId,
|
||||
mainStatsItemCount,
|
||||
currentPeriodItems,
|
||||
previousPeriodItems,
|
||||
requestId,
|
||||
currentPeriodItemsCount,
|
||||
} = mainStatsData;
|
||||
|
||||
const currentPeriodOrderedItems = orderBy(
|
||||
// need top-level sortable fields for the managed table
|
||||
currentPeriodItems.map((item) => ({
|
||||
...item,
|
||||
latency: item.latency ?? 0,
|
||||
throughput: item.throughput ?? 0,
|
||||
errorRate: item.errorRate ?? 0,
|
||||
cpuUsage: item.cpuUsage ?? 0,
|
||||
memoryUsage: item.memoryUsage ?? 0,
|
||||
})),
|
||||
field,
|
||||
direction
|
||||
).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE);
|
||||
|
||||
const {
|
||||
data: detailedStatsData = INITIAL_STATE_DETAILED_STATISTICS,
|
||||
status: detailedStatsStatus,
|
||||
|
@ -177,7 +182,7 @@ export function ServiceOverviewInstancesChartAndTable({
|
|||
!end ||
|
||||
!transactionType ||
|
||||
!latencyAggregationType ||
|
||||
!mainStatsItemCount
|
||||
!currentPeriodItemsCount
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
@ -198,7 +203,7 @@ export function ServiceOverviewInstancesChartAndTable({
|
|||
numBuckets: 20,
|
||||
transactionType,
|
||||
serviceNodeIds: JSON.stringify(
|
||||
mainStatsItems.map((item) => item.serviceNodeName)
|
||||
currentPeriodOrderedItems.map((item) => item.serviceNodeName)
|
||||
),
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
|
@ -208,7 +213,7 @@ export function ServiceOverviewInstancesChartAndTable({
|
|||
},
|
||||
// only fetches detailed statistics when requestId is invalidated by main statistics api call
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[mainStatsRequestId],
|
||||
[requestId],
|
||||
{ preservePreviousData: false }
|
||||
);
|
||||
|
||||
|
@ -217,16 +222,17 @@ export function ServiceOverviewInstancesChartAndTable({
|
|||
<EuiFlexItem grow={3}>
|
||||
<InstancesLatencyDistributionChart
|
||||
height={chartHeight}
|
||||
items={mainStatsItems}
|
||||
items={currentPeriodItems}
|
||||
status={mainStatsStatus}
|
||||
comparisonItems={previousPeriodItems}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={7}>
|
||||
<EuiPanel>
|
||||
<ServiceOverviewInstancesTable
|
||||
mainStatsItems={mainStatsItems}
|
||||
mainStatsItems={currentPeriodOrderedItems}
|
||||
mainStatsStatus={mainStatsStatus}
|
||||
mainStatsItemCount={mainStatsItemCount}
|
||||
mainStatsItemCount={currentPeriodItemsCount}
|
||||
detailedStatsData={detailedStatsData}
|
||||
serviceName={serviceName}
|
||||
tableOptions={tableOptions}
|
||||
|
|
|
@ -133,7 +133,7 @@ export function ServiceOverviewInstancesTable({
|
|||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.serviceOverview.instancesTableTitle', {
|
||||
defaultMessage: 'All instances',
|
||||
defaultMessage: 'Instances',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
|
|
@ -30,33 +30,32 @@ import {
|
|||
} from '../../../../../common/utils/formatters';
|
||||
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
import { MainStatsServiceInstanceItem } from '../../../app/service_overview/service_overview_instances_chart_and_table';
|
||||
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
|
||||
import * as urlHelpers from '../../Links/url_helpers';
|
||||
import { ChartContainer } from '../chart_container';
|
||||
import { getResponseTimeTickFormatter } from '../transaction_charts/helper';
|
||||
import { CustomTooltip } from './custom_tooltip';
|
||||
|
||||
type ApiResponseMainStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>;
|
||||
|
||||
export interface InstancesLatencyDistributionChartProps {
|
||||
height: number;
|
||||
items?: MainStatsServiceInstanceItem[];
|
||||
items?: ApiResponseMainStats['currentPeriod'];
|
||||
status: FETCH_STATUS;
|
||||
comparisonItems?: ApiResponseMainStats['previousPeriod'];
|
||||
}
|
||||
|
||||
export function InstancesLatencyDistributionChart({
|
||||
height,
|
||||
items = [],
|
||||
status,
|
||||
comparisonItems = [],
|
||||
}: InstancesLatencyDistributionChartProps) {
|
||||
const history = useHistory();
|
||||
const hasData = items.length > 0;
|
||||
|
||||
const theme = useTheme();
|
||||
const chartTheme = {
|
||||
...useChartTheme(),
|
||||
bubbleSeriesStyle: {
|
||||
point: { strokeWidth: 0, fill: theme.eui.euiColorVis1, radius: 4 },
|
||||
},
|
||||
};
|
||||
const chartTheme = useChartTheme();
|
||||
|
||||
const maxLatency = Math.max(...items.map((item) => item.latency ?? 0));
|
||||
const latencyFormatter = getDurationFormatter(maxLatency);
|
||||
|
@ -96,7 +95,13 @@ export function InstancesLatencyDistributionChart({
|
|||
// there's just a single instance) they'll show along the origin. Make sure
|
||||
// the x-axis domain is [0, maxThroughput].
|
||||
const maxThroughput = Math.max(...items.map((item) => item.throughput ?? 0));
|
||||
const xDomain = { min: 0, max: maxThroughput };
|
||||
const maxComparisonThroughput = Math.max(
|
||||
...comparisonItems.map((item) => item.throughput ?? 0)
|
||||
);
|
||||
const xDomain = {
|
||||
min: 0,
|
||||
max: Math.max(maxThroughput, maxComparisonThroughput),
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPanel>
|
||||
|
@ -118,7 +123,7 @@ export function InstancesLatencyDistributionChart({
|
|||
xDomain={xDomain}
|
||||
/>
|
||||
<BubbleSeries
|
||||
color={theme.eui.euiColorVis1}
|
||||
color={theme.eui.euiColorVis0}
|
||||
data={items}
|
||||
id={i18n.translate(
|
||||
'xpack.apm.instancesLatencyDistributionChartLegend',
|
||||
|
@ -128,7 +133,38 @@ export function InstancesLatencyDistributionChart({
|
|||
xScaleType={ScaleType.Linear}
|
||||
yAccessors={[(item) => item.latency]}
|
||||
yScaleType={ScaleType.Linear}
|
||||
bubbleSeriesStyle={{
|
||||
point: {
|
||||
strokeWidth: 0,
|
||||
radius: 4,
|
||||
fill: theme.eui.euiColorVis0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
{!!comparisonItems.length && (
|
||||
<BubbleSeries
|
||||
data={comparisonItems}
|
||||
id={i18n.translate(
|
||||
'xpack.apm.instancesLatencyDistributionChartLegend.previousPeriod',
|
||||
{ defaultMessage: 'Previous period' }
|
||||
)}
|
||||
xAccessor={(item) => item.throughput}
|
||||
xScaleType={ScaleType.Linear}
|
||||
yAccessors={[(item) => item.latency]}
|
||||
yScaleType={ScaleType.Linear}
|
||||
color={theme.eui.euiColorMediumShade}
|
||||
bubbleSeriesStyle={{
|
||||
point: {
|
||||
shape: 'square',
|
||||
radius: 4,
|
||||
fill: theme.eui.euiColorLightestShade,
|
||||
stroke: theme.eui.euiColorMediumShade,
|
||||
strokeWidth: 2,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Axis
|
||||
id="x-axis"
|
||||
labelFormat={asTransactionRate}
|
||||
|
|
|
@ -457,6 +457,7 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({
|
|||
latencyAggregationType: latencyAggregationTypeRt,
|
||||
transactionType: t.string,
|
||||
}),
|
||||
comparisonRangeRt,
|
||||
environmentRt,
|
||||
kueryRt,
|
||||
rangeRt,
|
||||
|
@ -472,6 +473,8 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({
|
|||
kuery,
|
||||
transactionType,
|
||||
latencyAggregationType,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
} = params.query;
|
||||
|
||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||
|
@ -480,19 +483,36 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({
|
|||
|
||||
const { start, end } = setup;
|
||||
|
||||
const serviceInstances = await getServiceInstancesMainStatistics({
|
||||
environment,
|
||||
kuery,
|
||||
latencyAggregationType,
|
||||
serviceName,
|
||||
setup,
|
||||
transactionType,
|
||||
searchAggregatedTransactions,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
const [currentPeriod, previousPeriod] = await Promise.all([
|
||||
getServiceInstancesMainStatistics({
|
||||
environment,
|
||||
kuery,
|
||||
latencyAggregationType,
|
||||
serviceName,
|
||||
setup,
|
||||
transactionType,
|
||||
searchAggregatedTransactions,
|
||||
start,
|
||||
end,
|
||||
}),
|
||||
...(comparisonStart && comparisonEnd
|
||||
? [
|
||||
getServiceInstancesMainStatistics({
|
||||
environment,
|
||||
kuery,
|
||||
latencyAggregationType,
|
||||
serviceName,
|
||||
setup,
|
||||
transactionType,
|
||||
searchAggregatedTransactions,
|
||||
start: comparisonStart,
|
||||
end: comparisonEnd,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
|
||||
return { serviceInstances };
|
||||
return { currentPeriod, previousPeriod };
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { pick, sortBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi';
|
||||
import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
|
@ -33,22 +34,24 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
path: { serviceName: 'opbeans-java' },
|
||||
query: {
|
||||
latencyAggregationType: LatencyAggregationType.avg,
|
||||
start,
|
||||
end,
|
||||
transactionType: 'request',
|
||||
start: moment(end).subtract(15, 'minutes').toISOString(),
|
||||
end,
|
||||
comparisonStart: start,
|
||||
comparisonEnd: moment(start).add(15, 'minutes').toISOString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.serviceInstances).to.eql([]);
|
||||
expect(response.body.currentPeriod).to.eql([]);
|
||||
expect(response.body.previousPeriod).to.eql([]);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
registry.when(
|
||||
'Service overview instances main statistics when data is loaded',
|
||||
'Service overview instances main statistics when data is loaded without comparison',
|
||||
{ config: 'basic', archives: [archiveName] },
|
||||
() => {
|
||||
describe('fetching java data', () => {
|
||||
|
@ -72,11 +75,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('returns a service node item', () => {
|
||||
expect(response.body.serviceInstances.length).to.be.greaterThan(0);
|
||||
expect(response.body.currentPeriod.length).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('returns statistics for each service node', () => {
|
||||
const item = response.body.serviceInstances[0];
|
||||
const item = response.body.currentPeriod[0];
|
||||
|
||||
expect(isFiniteNumber(item.cpuUsage)).to.be(true);
|
||||
expect(isFiniteNumber(item.memoryUsage)).to.be(true);
|
||||
|
@ -86,7 +89,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('returns the right data', () => {
|
||||
const items = sortBy(response.body.serviceInstances, 'serviceNodeName');
|
||||
const items = sortBy(response.body.currentPeriod, 'serviceNodeName');
|
||||
|
||||
const serviceNodeNames = items.map((item) => item.serviceNodeName);
|
||||
|
||||
|
@ -141,7 +144,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('returns statistics for each service node', () => {
|
||||
const item = response.body.serviceInstances[0];
|
||||
const item = response.body.currentPeriod[0];
|
||||
|
||||
expect(isFiniteNumber(item.cpuUsage)).to.be(true);
|
||||
expect(isFiniteNumber(item.memoryUsage)).to.be(true);
|
||||
|
@ -151,7 +154,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('returns the right data', () => {
|
||||
const items = sortBy(response.body.serviceInstances, 'serviceNodeName');
|
||||
const items = sortBy(response.body.currentPeriod, 'serviceNodeName');
|
||||
|
||||
const serviceNodeNames = items.map((item) => item.serviceNodeName);
|
||||
|
||||
|
@ -181,4 +184,90 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
}
|
||||
);
|
||||
|
||||
registry.when(
|
||||
'Service overview instances main statistics when data is loaded with comparison',
|
||||
{ config: 'basic', archives: [archiveName] },
|
||||
() => {
|
||||
describe('fetching java data', () => {
|
||||
let response: {
|
||||
body: APIReturnType<`GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`>;
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
response = await apmApiSupertest({
|
||||
endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`,
|
||||
params: {
|
||||
path: { serviceName: 'opbeans-java' },
|
||||
query: {
|
||||
latencyAggregationType: LatencyAggregationType.avg,
|
||||
transactionType: 'request',
|
||||
start: moment(end).subtract(15, 'minutes').toISOString(),
|
||||
end,
|
||||
comparisonStart: start,
|
||||
comparisonEnd: moment(start).add(15, 'minutes').toISOString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a service node item', () => {
|
||||
expect(response.body.currentPeriod.length).to.be.greaterThan(0);
|
||||
expect(response.body.previousPeriod.length).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('returns statistics for each service node', () => {
|
||||
const currentItem = response.body.currentPeriod[0];
|
||||
|
||||
expect(isFiniteNumber(currentItem.cpuUsage)).to.be(true);
|
||||
expect(isFiniteNumber(currentItem.memoryUsage)).to.be(true);
|
||||
expect(isFiniteNumber(currentItem.errorRate)).to.be(true);
|
||||
expect(isFiniteNumber(currentItem.throughput)).to.be(true);
|
||||
expect(isFiniteNumber(currentItem.latency)).to.be(true);
|
||||
|
||||
const previousItem = response.body.previousPeriod[0];
|
||||
|
||||
expect(isFiniteNumber(previousItem.cpuUsage)).to.be(true);
|
||||
expect(isFiniteNumber(previousItem.memoryUsage)).to.be(true);
|
||||
expect(isFiniteNumber(previousItem.errorRate)).to.be(true);
|
||||
expect(isFiniteNumber(previousItem.throughput)).to.be(true);
|
||||
expect(isFiniteNumber(previousItem.latency)).to.be(true);
|
||||
});
|
||||
|
||||
it('returns the right data', () => {
|
||||
const items = sortBy(response.body.previousPeriod, 'serviceNodeName');
|
||||
|
||||
const serviceNodeNames = items.map((item) => item.serviceNodeName);
|
||||
|
||||
expectSnapshot(items.length).toMatchInline(`1`);
|
||||
|
||||
expectSnapshot(serviceNodeNames).toMatchInline(`
|
||||
Array [
|
||||
"02950c4c5fbb0fda1cc98c47bf4024b473a8a17629db6530d95dcee68bd54c6c",
|
||||
]
|
||||
`);
|
||||
|
||||
const item = items[0];
|
||||
|
||||
const values = pick(item, [
|
||||
'cpuUsage',
|
||||
'memoryUsage',
|
||||
'errorRate',
|
||||
'throughput',
|
||||
'latency',
|
||||
]);
|
||||
|
||||
expectSnapshot(values).toMatchInline(`
|
||||
Object {
|
||||
"cpuUsage": 0.0120666666666667,
|
||||
"errorRate": 0.111111111111111,
|
||||
"latency": 379742.555555556,
|
||||
"memoryUsage": 0.939879608154297,
|
||||
"throughput": 3,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue