[UX] Improve page-load axis (#78392)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Shahzad 2020-10-01 10:16:30 +02:00 committed by GitHub
parent 29da04551d
commit 07ebb81a79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 357 additions and 74 deletions

View file

@ -36,7 +36,7 @@ Then(`breakdown series should appear in chart`, () => {
cy.get('div.echLegendItem__label', DEFAULT_TIMEOUT).should(
'have.text',
'ChromeChrome Mobile WebViewSafariFirefoxMobile SafariChrome MobileChrome Mobile iOSOverall'
'OverallChromeChrome Mobile WebViewSafariFirefoxMobile SafariChrome MobileChrome Mobile iOS'
);
});
});

View file

@ -26,7 +26,7 @@ Given(`a user browses the APM UI application for RUM Data`, () => {
});
Then(`should have correct client metrics`, () => {
const metrics = ['4 ms', '0.06 s', '55 '];
const metrics = ['4 ms', '58 ms', '55'];
verifyClientMetrics(metrics, true);
});

View file

@ -56,7 +56,7 @@ Then(/^it filters the client metrics "([^"]*)"$/, (filterName) => {
cy.get('.euiStat__title-isLoading').should('not.be.visible');
const data =
filterName === 'os' ? ['5 ms', '0.06 s', '8 '] : ['4 ms', '0.05 s', '28 '];
filterName === 'os' ? ['5 ms', '64 ms', '8'] : ['4 ms', '55 ms', '28'];
verifyClientMetrics(data, true);

View file

@ -18,7 +18,7 @@ When('the user changes the selected percentile', () => {
});
Then(`it displays client metric related to that percentile`, () => {
const metrics = ['14 ms', '0.13 s', '55 '];
const metrics = ['14 ms', '131 ms', '55'];
verifyClientMetrics(metrics, false);

View file

@ -15,7 +15,7 @@ When('the user changes the selected service name', () => {
});
Then(`it displays relevant client metrics`, () => {
const metrics = ['4 ms', '0.06 s', '55 '];
const metrics = ['4 ms', '58 ms', '55'];
verifyClientMetrics(metrics, false);
});

View file

@ -88,6 +88,10 @@ export function PageLoadDistChart({
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
const euiChartTheme = darkMode
? EUI_CHARTS_THEME_DARK
: EUI_CHARTS_THEME_LIGHT;
return (
<ChartWrapper
loading={loading || breakdownLoading}
@ -97,11 +101,7 @@ export function PageLoadDistChart({
<PageLoadChart>
<Settings
baseTheme={darkMode ? DARK_THEME : LIGHT_THEME}
theme={
darkMode
? EUI_CHARTS_THEME_DARK.theme
: EUI_CHARTS_THEME_LIGHT.theme
}
theme={euiChartTheme.theme}
onBrushEnd={onBrushEnd}
tooltip={tooltipProps}
showLegend
@ -116,9 +116,10 @@ export function PageLoadDistChart({
id="left"
title={I18LABELS.percPageLoaded}
position={Position.Left}
tickFormat={(d) => numeral(d).format('0.0') + '%'}
labelFormat={(d) => d + ' %'}
/>
<LineSeries
sortIndex={0}
fit={Fit.Linear}
id={'PagesPercentage'}
name={I18LABELS.overall}
@ -126,7 +127,12 @@ export function PageLoadDistChart({
yScaleType={ScaleType.Linear}
data={data?.pageLoadDistribution ?? []}
curve={CurveType.CURVE_CATMULL_ROM}
lineSeriesStyle={{ point: { visible: false } }}
lineSeriesStyle={{
point: { visible: false },
line: { strokeWidth: 3 },
}}
color={euiChartTheme.theme.colors?.vizColors?.[1]}
tickFormat={(d) => numeral(d).format('0.0') + ' %'}
/>
{breakdown && (
<BreakdownSeries

View file

@ -71,6 +71,8 @@ export function PageViewsChart({ data, loading }: Props) {
});
};
const hasBreakdowns = !!data?.topItems?.length;
const breakdownAccessors = data?.topItems?.length ? data?.topItems : ['y'];
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
@ -83,17 +85,17 @@ export function PageViewsChart({ data, loading }: Props) {
return yAccessor;
};
const euiChartTheme = darkMode
? EUI_CHARTS_THEME_DARK
: EUI_CHARTS_THEME_LIGHT;
return (
<ChartWrapper loading={loading} height="calc(100% - 72px)">
{(!loading || data) && (
<Chart>
<Settings
baseTheme={darkMode ? DARK_THEME : LIGHT_THEME}
theme={
darkMode
? EUI_CHARTS_THEME_DARK.theme
: EUI_CHARTS_THEME_LIGHT.theme
}
theme={euiChartTheme.theme}
showLegend
onBrushEnd={onBrushEnd}
xDomain={{
@ -122,6 +124,11 @@ export function PageViewsChart({ data, loading }: Props) {
stackAccessors={['x']}
data={data?.items ?? []}
name={customSeriesNaming}
color={
!hasBreakdowns
? euiChartTheme.theme.colors?.vizColors?.[1]
: undefined
}
/>
</Chart>
)}

View file

@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiToolTip } from '@elastic/eui';
import { useFetcher } from '../../../../hooks/useFetcher';
import { I18LABELS } from '../translations';
import { useUxQuery } from '../hooks/useUxQuery';
import { formatToSec } from '../UXMetrics/KeyUXMetrics';
import { CsmSharedContext } from '../CsmSharedContext';
const ClFlexGroup = styled(EuiFlexGroup)`
@ -49,14 +50,14 @@ export function ClientMetrics() {
const STAT_STYLE = { width: '240px' };
const pageViewsTotal = data?.pageViews?.value ?? 0;
return (
<ClFlexGroup responsive={false}>
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="l"
title={
(((data?.backEnd?.value ?? 0) * 1000).toFixed(0) ?? '-') + ' ms'
}
title={formatToSec(data?.backEnd?.value ?? 0, 'ms')}
description={I18LABELS.backEnd}
isLoading={status !== 'success'}
/>
@ -64,7 +65,7 @@ export function ClientMetrics() {
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="l"
title={((data?.frontEnd?.value ?? 0)?.toFixed(2) ?? '-') + ' s'}
title={formatToSec(data?.frontEnd?.value ?? 0, 'ms')}
description={I18LABELS.frontEnd}
isLoading={status !== 'success'}
/>
@ -73,9 +74,13 @@ export function ClientMetrics() {
<EuiStat
titleSize="l"
title={
<EuiToolTip content={data?.pageViews?.value}>
<>{numeral(data?.pageViews?.value).format('0 a') ?? '-'}</>
</EuiToolTip>
pageViewsTotal < 10000 ? (
numeral(pageViewsTotal).format('0,0')
) : (
<EuiToolTip content={numeral(pageViewsTotal).format('0,0')}>
<>{numeral(pageViewsTotal).format('0 a')}</>
</EuiToolTip>
)
}
description={I18LABELS.pageViews}
isLoading={status !== 'success'}

View file

@ -6,8 +6,13 @@
import { CurveType, Fit, LineSeries, ScaleType } from '@elastic/charts';
import React, { useEffect } from 'react';
import {
EUI_CHARTS_THEME_DARK,
EUI_CHARTS_THEME_LIGHT,
} from '@elastic/eui/dist/eui_charts_theme';
import { PercentileRange } from './index';
import { useBreakdowns } from './use_breakdowns';
import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public';
interface Props {
field: string;
@ -22,6 +27,12 @@ export function BreakdownSeries({
percentileRange,
onLoadingChange,
}: Props) {
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
const euiChartTheme = darkMode
? EUI_CHARTS_THEME_DARK
: EUI_CHARTS_THEME_LIGHT;
const { data, status } = useBreakdowns({
field,
value,
@ -32,9 +43,11 @@ export function BreakdownSeries({
onLoadingChange(status !== 'success');
}, [status, onLoadingChange]);
// sort index 1 color vizColors1 is already used for overall,
// so don't user that here
return (
<>
{data?.map(({ data: seriesData, name }) => (
{data?.map(({ data: seriesData, name }, sortIndex) => (
<LineSeries
id={`${field}-${value}-${name}`}
key={`${field}-${value}-${name}`}
@ -45,6 +58,11 @@ export function BreakdownSeries({
data={seriesData ?? []}
lineSeriesStyle={{ point: { visible: false } }}
fit={Fit.Linear}
color={
euiChartTheme.theme.colors?.vizColors?.[
sortIndex === 0 ? 0 : sortIndex + 1
]
}
/>
))}
</>

View file

@ -6,6 +6,7 @@
import React from 'react';
import { EuiFlexItem, EuiStat, EuiFlexGroup } from '@elastic/eui';
import numeral from '@elastic/numeral';
import { UXMetrics } from './index';
import {
FCP_LABEL,
@ -77,7 +78,7 @@ export function KeyUXMetrics({ data, loading }: Props) {
<EuiFlexItem grow={false} style={STAT_STYLE}>
<EuiStat
titleSize="s"
title={longTaskData?.noOfLongTasks ?? 0}
title={numeral(longTaskData?.noOfLongTasks ?? 0).format('0,0')}
description={NO_OF_LONG_TASK}
isLoading={status !== 'success'}
/>

View file

@ -245,10 +245,214 @@ Object {
],
},
},
"minDuration": Object {
"min": Object {
"loadDistribution": Object {
"percentile_ranks": Object {
"field": "transaction.duration.us",
"missing": 0,
"hdr": Object {
"number_of_significant_value_digits": 3,
},
"keyed": false,
"values": Array [
0,
500000,
1000000,
1500000,
2000000,
2500000,
3000000,
3500000,
4000000,
4500000,
5000000,
5500000,
6000000,
6500000,
7000000,
7500000,
8000000,
8500000,
9000000,
9500000,
10000000,
10500000,
11000000,
11500000,
12000000,
12500000,
13000000,
13500000,
14000000,
14500000,
15000000,
15500000,
16000000,
16500000,
17000000,
17500000,
18000000,
18500000,
19000000,
19500000,
20000000,
20500000,
21000000,
21500000,
22000000,
22500000,
23000000,
23500000,
24000000,
24500000,
25000000,
25500000,
26000000,
26500000,
27000000,
27500000,
28000000,
28500000,
29000000,
29500000,
30000000,
30500000,
31000000,
31500000,
32000000,
32500000,
33000000,
33500000,
34000000,
34500000,
35000000,
35500000,
36000000,
36500000,
37000000,
37500000,
38000000,
38500000,
39000000,
39500000,
40000000,
40500000,
41000000,
41500000,
42000000,
42500000,
43000000,
43500000,
44000000,
44500000,
45000000,
45500000,
46000000,
46500000,
47000000,
47500000,
48000000,
48500000,
49000000,
49500000,
50000000,
50500000,
51000000,
51500000,
52000000,
52500000,
53000000,
53500000,
54000000,
54500000,
55000000,
55500000,
56000000,
56500000,
57000000,
57500000,
58000000,
58500000,
59000000,
59500000,
60000000,
60500000,
61000000,
61500000,
62000000,
62500000,
63000000,
63500000,
64000000,
64500000,
65000000,
65500000,
66000000,
66500000,
67000000,
67500000,
68000000,
68500000,
69000000,
69500000,
70000000,
70500000,
71000000,
71500000,
72000000,
72500000,
73000000,
73500000,
74000000,
74500000,
75000000,
75500000,
76000000,
76500000,
77000000,
77500000,
78000000,
78500000,
79000000,
79500000,
80000000,
80500000,
81000000,
81500000,
82000000,
82500000,
83000000,
83500000,
84000000,
84500000,
85000000,
85500000,
86000000,
86500000,
87000000,
87500000,
88000000,
88500000,
89000000,
89500000,
90000000,
90500000,
91000000,
91500000,
92000000,
92500000,
93000000,
93500000,
94000000,
94500000,
95000000,
95500000,
96000000,
96500000,
97000000,
97500000,
98000000,
98500000,
99000000,
],
},
},
},

View file

@ -72,11 +72,9 @@ export async function getClientMetrics({
// Divide by 1000 to convert ms into seconds
return {
pageViews,
backEnd: { value: (backEnd.values[pkey] || 0) / 1000 },
backEnd: { value: backEnd.values[pkey] || 0 },
frontEnd: {
value:
((domInteractive.values[pkey] || 0) - (backEnd.values[pkey] || 0)) /
1000,
value: (domInteractive.values[pkey] || 0) - (backEnd.values[pkey] || 0),
},
};
}

View file

@ -15,8 +15,6 @@ import {
export const MICRO_TO_SEC = 1000000;
const NUMBER_OF_PLD_STEPS = 100;
export function microToSec(val: number) {
return Math.round((val / MICRO_TO_SEC + Number.EPSILON) * 100) / 100;
}
@ -24,15 +22,31 @@ export function microToSec(val: number) {
export const getPLDChartSteps = ({
maxDuration,
minDuration,
initStepValue,
}: {
maxDuration: number;
minDuration: number;
initStepValue?: number;
}) => {
const stepValue = (maxDuration - minDuration) / NUMBER_OF_PLD_STEPS;
const stepValues = [];
for (let i = 1; i < NUMBER_OF_PLD_STEPS + 1; i++) {
stepValues.push((stepValue * i + minDuration).toFixed(2));
let stepValue = 0.5;
// if diff is too low, let's lower
// down the steps value to increase steps
if (maxDuration - minDuration <= 5 * MICRO_TO_SEC) {
stepValue = 0.1;
}
if (initStepValue) {
stepValue = initStepValue;
}
let initValue = minDuration;
const stepValues = [initValue];
while (initValue < maxDuration) {
initValue += stepValue * MICRO_TO_SEC;
stepValues.push(initValue);
}
return stepValues;
};
@ -52,16 +66,21 @@ export async function getPageLoadDistribution({
urlQuery,
});
// we will first get 100 steps using 0sec and 50sec duration,
// most web apps will cover this use case
// if 99th percentile is greater than 50sec,
// we will fetch additional 5 steps beyond 99th percentile
let maxDuration = (maxPercentile ? +maxPercentile : 50) * MICRO_TO_SEC;
const minDuration = minPercentile ? +minPercentile * MICRO_TO_SEC : 0;
const stepValues = getPLDChartSteps({
maxDuration,
minDuration,
});
const params = mergeProjection(projection, {
body: {
size: 0,
aggs: {
minDuration: {
min: {
field: TRANSACTION_DURATION,
missing: 0,
},
},
durPercentiles: {
percentiles: {
field: TRANSACTION_DURATION,
@ -71,6 +90,16 @@ export async function getPageLoadDistribution({
},
},
},
loadDistribution: {
percentile_ranks: {
field: TRANSACTION_DURATION,
values: stepValues,
keyed: false,
hdr: {
number_of_significant_value_digits: 3,
},
},
},
},
},
});
@ -86,22 +115,40 @@ export async function getPageLoadDistribution({
return null;
}
const { durPercentiles, minDuration } = aggregations ?? {};
const { durPercentiles, loadDistribution } = aggregations ?? {};
const minPerc = minPercentile
? +minPercentile * MICRO_TO_SEC
: minDuration?.value ?? 0;
let pageDistVals = loadDistribution?.values ?? [];
const maxPercQuery = durPercentiles?.values['99.0'] ?? 10000;
const maxPercQuery = durPercentiles?.values['99.0'] ?? 0;
const maxPerc = maxPercentile ? +maxPercentile * MICRO_TO_SEC : maxPercQuery;
// we assumed that page load will never exceed 50secs, if 99th percentile is
// greater then let's fetch additional 10 steps, to cover that on the chart
if (maxPercQuery > maxDuration && !maxPercentile) {
const additionalStepsPageVals = await getPercentilesDistribution({
setup,
maxDuration: maxPercQuery,
// we pass 50sec as min to get next steps
minDuration: maxDuration,
});
const pageDist = await getPercentilesDistribution({
setup,
minDuration: minPerc,
maxDuration: maxPerc,
pageDistVals = pageDistVals.concat(additionalStepsPageVals);
maxDuration = maxPercQuery;
}
// calculate the diff to get actual page load on specific duration value
const pageDist = pageDistVals.map(({ key, value }, index: number, arr) => {
return {
x: microToSec(key),
y: index === 0 ? value : value - arr[index - 1].value,
};
});
if (pageDist.length > 0) {
while (pageDist[pageDist.length - 1].y === 0) {
pageDist.pop();
}
}
Object.entries(durPercentiles?.values ?? {}).forEach(([key, val]) => {
if (durPercentiles?.values?.[key]) {
durPercentiles.values[key] = microToSec(val as number);
@ -111,8 +158,8 @@ export async function getPageLoadDistribution({
return {
pageLoadDistribution: pageDist,
percentiles: durPercentiles?.values,
minDuration: microToSec(minPerc),
maxDuration: microToSec(maxPerc),
minDuration: microToSec(minDuration),
maxDuration: microToSec(maxDuration),
};
}
@ -125,7 +172,11 @@ const getPercentilesDistribution = async ({
minDuration: number;
maxDuration: number;
}) => {
const stepValues = getPLDChartSteps({ maxDuration, minDuration });
const stepValues = getPLDChartSteps({
minDuration: minDuration + 0.5 * MICRO_TO_SEC,
maxDuration,
initStepValue: 0.5,
});
const projection = getRumPageLoadTransactionsProjection({
setup,
@ -153,12 +204,5 @@ const getPercentilesDistribution = async ({
const { aggregations } = await apmEventClient.search(params);
const pageDist = aggregations?.loadDistribution.values ?? [];
return pageDist.map(({ key, value }, index: number, arr) => {
return {
x: microToSec(key),
y: index === 0 ? value : value - arr[index - 1].value,
};
});
return aggregations?.loadDistribution.values ?? [];
};

View file

@ -41,21 +41,21 @@ export const getBreakdownField = (breakdown: string) => {
export const getPageLoadDistBreakdown = async ({
setup,
minDuration,
maxDuration,
minPercentile,
maxPercentile,
breakdown,
urlQuery,
}: {
setup: Setup & SetupTimeRange & SetupUIFilters;
minDuration: number;
maxDuration: number;
minPercentile: number;
maxPercentile: number;
breakdown: string;
urlQuery?: string;
}) => {
// convert secs to micros
const stepValues = getPLDChartSteps({
minDuration: minDuration * MICRO_TO_SEC,
maxDuration: maxDuration * MICRO_TO_SEC,
maxDuration: (maxPercentile ? +maxPercentile : 50) * MICRO_TO_SEC,
minDuration: minPercentile ? +minPercentile * MICRO_TO_SEC : 0,
});
const projection = getRumPageLoadTransactionsProjection({

View file

@ -89,8 +89,8 @@ export const rumPageLoadDistBreakdownRoute = createRoute(() => ({
return getPageLoadDistBreakdown({
setup,
minDuration: Number(minPercentile),
maxDuration: Number(maxPercentile),
minPercentile: Number(minPercentile),
maxPercentile: Number(maxPercentile),
breakdown,
urlQuery,
});