Skip to content

Commit

Permalink
[Synthetics] Monitor overview item display median duration (#155694)
Browse files Browse the repository at this point in the history
## Summary

Display median duration instead of avg on monitor overview item

<img width="1722" alt="image"
src="https://user-images.githubusercontent.com/3505601/234247520-91aace9f-101b-4ed1-89f6-4a7a885872cf.png">
  • Loading branch information
shahzad31 committed Apr 25, 2023
1 parent 7c69a34 commit 3864554
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const DurationPanel = (props: DurationPanelProps) => {
attributes={[
{
time: props,
name: AVG_DURATION_LABEL,
name: MEDIAN_DURATION_LABEL,
dataType: 'synthetics',
selectedMetricField: 'monitor_duration',
reportDefinitions: {
Expand All @@ -55,9 +55,9 @@ export const DurationPanel = (props: DurationPanelProps) => {
);
};

export const AVG_DURATION_LABEL = i18n.translate(
'xpack.synthetics.monitorDetails.summary.avgDuration',
export const MEDIAN_DURATION_LABEL = i18n.translate(
'xpack.synthetics.monitorDetails.summary.medianDuration',
{
defaultMessage: 'Avg. duration',
defaultMessage: 'Median duration',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ReportTypes } from '@kbn/exploratory-view-plugin/public';
import { useTheme } from '@kbn/observability-plugin/public';
import { AVG_DURATION_LABEL } from './duration_panel';
import { MEDIAN_DURATION_LABEL } from './duration_panel';
import { useMonitorQueryId } from '../hooks/use_monitor_query_id';
import { ClientPluginsStart } from '../../../../../plugin';
import { useSelectedLocation } from '../hooks/use_selected_location';
Expand Down Expand Up @@ -47,7 +47,7 @@ export const DurationSparklines = (props: DurationSparklinesProps) => {
{
seriesType: 'area',
time: props,
name: AVG_DURATION_LABEL,
name: MEDIAN_DURATION_LABEL,
dataType: 'synthetics',
selectedMetricField: 'monitor.duration.us',
reportDefinitions: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import React, { useState } from 'react';
import { Chart, Settings, Metric, MetricTrendShape } from '@elastic/charts';
import { EuiPanel } from '@elastic/eui';
import { EuiPanel, EuiIconTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { DARK_THEME } from '@elastic/charts';
import { useTheme } from '@kbn/observability-plugin/public';
import { useDispatch, useSelector } from 'react-redux';
Expand Down Expand Up @@ -46,13 +46,19 @@ export const getColor = (

export const MetricItem = ({
monitor,
averageDuration,
medianDuration,
maxDuration,
minDuration,
avgDuration,
data,
onClick,
}: {
monitor: MonitorOverviewItem;
data: Array<{ x: number; y: number }>;
averageDuration: number;
medianDuration: number;
avgDuration: number;
minDuration: number;
maxDuration: number;
onClick: (params: { id: string; configId: string; location: string; locationId: string }) => void;
}) => {
const [isMouseOver, setIsMouseOver] = useState(false);
Expand Down Expand Up @@ -119,15 +125,42 @@ export const MetricItem = ({
{
title: monitor.name,
subtitle: locationName,
value: averageDuration,
value: medianDuration,
trendShape: MetricTrendShape.Area,
trend: data,
extra: (
<span>
{i18n.translate('xpack.synthetics.overview.duration.label', {
defaultMessage: 'Duration Avg.',
})}
</span>
<EuiFlexGroup
alignItems="center"
gutterSize="xs"
justifyContent="flexEnd"
// empty title to prevent default title from showing
title=""
>
<EuiFlexItem grow={false}>
{i18n.translate('xpack.synthetics.overview.duration.label', {
defaultMessage: 'Duration',
})}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIconTip
title={i18n.translate('xpack.synthetics.overview.duration.description', {
defaultMessage: 'Median duration of last 24 checks',
})}
content={i18n.translate(
'xpack.synthetics.overview.duration.description.values',
{
defaultMessage: 'Avg: {avg}, Min: {min}, Max: {max}',
values: {
avg: formatDuration(avgDuration, { noSpace: true }),
min: formatDuration(minDuration, { noSpace: true }),
max: formatDuration(maxDuration, { noSpace: true }),
},
}
)}
position="top"
/>
</EuiFlexItem>
</EuiFlexGroup>
),
valueFormatter: (d: number) => formatDuration(d),
color: getColor(theme, monitor.isEnabled, status),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,14 @@ describe('Overview Grid', () => {
const perPage = 20;

it('renders correctly', async () => {
jest
.spyOn(hooks, 'useLast50DurationChart')
.mockReturnValue({ data: getMockChart(), averageDuration: 30000, loading: false });
jest.spyOn(hooks, 'useLast50DurationChart').mockReturnValue({
data: getMockChart(),
avgDuration: 30000,
minDuration: 0,
maxDuration: 50000,
medianDuration: 15000,
loading: false,
});

const { getByText, getAllByTestId, queryByText } = render(<OverviewGrid />, {
state: {
Expand Down Expand Up @@ -124,9 +129,14 @@ describe('Overview Grid', () => {
});

it('displays showing all monitors label when reaching the end of the list', async () => {
jest
.spyOn(hooks, 'useLast50DurationChart')
.mockReturnValue({ data: getMockChart(), averageDuration: 30000, loading: false });
jest.spyOn(hooks, 'useLast50DurationChart').mockReturnValue({
data: getMockChart(),
avgDuration: 30000,
minDuration: 0,
maxDuration: 50000,
medianDuration: 15000,
loading: false,
});

const { getByText } = render(<OverviewGrid />, {
state: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,20 @@ export const OverviewGridItem = ({

const { timestamp } = useStatusByLocationOverview(monitor.configId, locationName);

const { data, averageDuration } = useLast50DurationChart({
const { data, medianDuration, maxDuration, avgDuration, minDuration } = useLast50DurationChart({
locationId: monitor.location?.id,
monitorId: monitor.id,
timestamp,
});
return (
<MetricItem data={data} monitor={monitor} averageDuration={averageDuration} onClick={onClick} />
<MetricItem
data={data}
monitor={monitor}
medianDuration={medianDuration}
maxDuration={maxDuration}
avgDuration={avgDuration}
minDuration={minDuration}
onClick={onClick}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ describe('useLast50DurationChart', () => {
{ wrapper: WrappedHelper }
);
expect(result.current).toEqual({
averageDuration: 4.5,
medianDuration: 5,
maxDuration: 9,
minDuration: 0,
avgDuration: 4.5,
data: [
{
x: 0,
Expand Down Expand Up @@ -132,7 +135,10 @@ describe('useLast50DurationChart', () => {
];

expect(result.current).toEqual({
averageDuration: data.reduce((acc, datum) => (acc += datum.y), 0) / 9,
medianDuration: [...data].sort((a, b) => a.y - b.y)[Math.floor(data.length / 2)].y,
maxDuration: 9,
minDuration: 0,
avgDuration: 4.4,
data,
loading: false,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,23 @@ export function useLast50DurationChart({
size: 50,
timestamp,
});
const { data, averageDuration } = useMemo(() => {
const { data, median, min, max, avg } = useMemo(() => {
if (loading) {
return {
data: [],
averageDuration: 0,
median: 0,
avg: 0,
min: 0,
max: 0,
};
}
let totalDuration = 0;

// calculate min, max, average duration and median

const coords = hits
.reverse() // results are returned in desc order by timestamp. Reverse to ensure the data is in asc order by timestamp
.map((hit, index) => {
const duration = hit?.['monitor.duration.us']?.[0];
totalDuration += duration || 0;
if (duration === undefined) {
return null;
}
Expand All @@ -52,18 +55,30 @@ export function useLast50DurationChart({
})
.filter((item) => item !== null);

const sortedByDuration = [...hits].sort(
(a, b) => (a?.['monitor.duration.us']?.[0] || 0) - (b?.['monitor.duration.us']?.[0] || 0)
);

return {
data: coords as Array<{ x: number; y: number }>,
averageDuration: totalDuration / coords.length,
median: sortedByDuration[Math.floor(hits.length / 2)]?.['monitor.duration.us']?.[0] || 0,
avg:
sortedByDuration.reduce((acc, curr) => acc + (curr?.['monitor.duration.us']?.[0] || 0), 0) /
hits.length,
min: sortedByDuration[0]?.['monitor.duration.us']?.[0] || 0,
max: sortedByDuration[sortedByDuration.length - 1]?.['monitor.duration.us']?.[0] || 0,
};
}, [hits, loading]);

return useMemo(
() => ({
data,
averageDuration,
medianDuration: median,
avgDuration: avg,
minDuration: min,
maxDuration: max,
loading,
}),
[loading, data, averageDuration]
[data, median, avg, min, max, loading]
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,28 @@ export const microsToMillis = (microseconds: number | null): number | null => {
return Math.floor(microseconds / NUM_MICROSECONDS_IN_MILLISECOND);
};

export const formatDuration = (durationMicros: number) => {
export const formatDuration = (durationMicros: number, { noSpace }: { noSpace?: true } = {}) => {
if (durationMicros < MILLIS_LIMIT) {
if (noSpace) {
return i18n.translate('xpack.synthetics.overview.durationMsFormattingNoSpace', {
values: { millis: microsToMillis(durationMicros) },
defaultMessage: '{millis}ms',
});
}
return i18n.translate('xpack.synthetics.overview.durationMsFormatting', {
values: { millis: microsToMillis(durationMicros) },
defaultMessage: '{millis} ms',
});
}
const seconds = (durationMicros / ONE_SECOND_AS_MICROS).toFixed(0);

if (noSpace) {
return i18n.translate('xpack.synthetics.overview.durationSecondsFormattingNoSpace', {
values: { seconds },
defaultMessage: '{seconds}s',
});
}

return i18n.translate('xpack.synthetics.overview.durationSecondsFormatting', {
values: { seconds },
defaultMessage: '{seconds} s',
Expand Down
3 changes: 1 addition & 2 deletions x-pack/plugins/translations/translations/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -34806,7 +34806,6 @@
"xpack.synthetics.monitorDetails.statusBar.pingType.icmp": "ICMP",
"xpack.synthetics.monitorDetails.statusBar.pingType.tcp": "TCP",
"xpack.synthetics.monitorDetails.summary.availability": "Disponibilité",
"xpack.synthetics.monitorDetails.summary.avgDuration": "Durée moy.",
"xpack.synthetics.monitorDetails.summary.brushArea": "Brosser une zone pour une plus haute fidélité",
"xpack.synthetics.monitorDetails.summary.complete": "Terminé",
"xpack.synthetics.monitorDetails.summary.duration": "Durée",
Expand Down Expand Up @@ -37947,4 +37946,4 @@
"xpack.painlessLab.title": "Painless Lab",
"xpack.painlessLab.walkthroughButtonLabel": "Présentation"
}
}
}
3 changes: 1 addition & 2 deletions x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -34785,7 +34785,6 @@
"xpack.synthetics.monitorDetails.statusBar.pingType.icmp": "ICMP",
"xpack.synthetics.monitorDetails.statusBar.pingType.tcp": "TCP",
"xpack.synthetics.monitorDetails.summary.availability": "可用性",
"xpack.synthetics.monitorDetails.summary.avgDuration": "平均期間",
"xpack.synthetics.monitorDetails.summary.brushArea": "信頼度を高めるためにエリアを精査",
"xpack.synthetics.monitorDetails.summary.complete": "完了",
"xpack.synthetics.monitorDetails.summary.duration": "期間",
Expand Down Expand Up @@ -37915,4 +37914,4 @@
"xpack.painlessLab.title": "Painless Lab",
"xpack.painlessLab.walkthroughButtonLabel": "実地検証"
}
}
}
3 changes: 1 addition & 2 deletions x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -34802,7 +34802,6 @@
"xpack.synthetics.monitorDetails.statusBar.pingType.icmp": "ICMP",
"xpack.synthetics.monitorDetails.statusBar.pingType.tcp": "TCP",
"xpack.synthetics.monitorDetails.summary.availability": "可用性",
"xpack.synthetics.monitorDetails.summary.avgDuration": "平均持续时间",
"xpack.synthetics.monitorDetails.summary.brushArea": "轻刷某个区域以提高保真度",
"xpack.synthetics.monitorDetails.summary.complete": "已完成",
"xpack.synthetics.monitorDetails.summary.duration": "持续时间",
Expand Down Expand Up @@ -37943,4 +37942,4 @@
"xpack.painlessLab.title": "Painless 实验室",
"xpack.painlessLab.walkthroughButtonLabel": "指导"
}
}
}

0 comments on commit 3864554

Please sign in to comment.