[APM] add comparison to Instances latency distribution (#97710)

* adding comparion data to chart

* fixing api test

* addressing comments

* refactoring
This commit is contained in:
Cauê Marcondes 2021-04-21 16:47:03 -04:00 committed by GitHub
parent 1fb00900e1
commit e76987e13c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 215 additions and 64 deletions

View file

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

View file

@ -133,7 +133,7 @@ export function ServiceOverviewInstancesTable({
<EuiTitle size="xs">
<h2>
{i18n.translate('xpack.apm.serviceOverview.instancesTableTitle', {
defaultMessage: 'All instances',
defaultMessage: 'Instances',
})}
</h2>
</EuiTitle>

View file

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

View file

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

View file

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