From 946e11ae6325620b5a02449ba0ddd73939d92f02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cau=C3=AA=20Marcondes?=
<55978943+cauemarcondes@users.noreply.github.com>
Date: Wed, 21 Apr 2021 16:47:03 -0400
Subject: [PATCH] [APM] add comparison to Instances latency distribution
(#97710)
* adding comparion data to chart
* fixing api test
* addressing comments
* refactoring
---
...ice_overview_instances_chart_and_table.tsx | 68 ++++++-----
.../index.tsx | 2 +-
.../index.tsx | 56 +++++++--
x-pack/plugins/apm/server/routes/services.ts | 44 +++++--
.../instances_main_statistics.ts | 109 ++++++++++++++++--
5 files changed, 215 insertions(+), 64 deletions(-)
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
index 8305b5a0dde3b..8513e0835d373 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
@@ -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({
{i18n.translate('xpack.apm.serviceOverview.instancesTableTitle', {
- defaultMessage: 'All instances',
+ defaultMessage: 'Instances',
})}
diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
index 394d5b5410d41..ce4f36ced7903 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
@@ -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 (
@@ -118,7 +123,7 @@ export function InstancesLatencyDistributionChart({
xDomain={xDomain}
/>
item.latency]}
yScaleType={ScaleType.Linear}
+ bubbleSeriesStyle={{
+ point: {
+ strokeWidth: 0,
+ radius: 4,
+ fill: theme.eui.euiColorVis0,
+ },
+ }}
/>
+
+ {!!comparisonItems.length && (
+ 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,
+ },
+ }}
+ />
+ )}
{
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,
+ }
+ `);
+ });
+ });
+ }
+ );
}