Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Synthetics] Object types panel and thresholds #149099

Merged
merged 14 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export * from './test_now_mode.journey';
export * from './data_retention.journey';
export * from './monitor_details_page/monitor_summary.journey';
export * from './test_run_details.journey';
export * from './step_details.journey';
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { journey, step, before, after } from '@elastic/synthetics';
import { recordVideo } from '@kbn/observability-plugin/e2e/record_video';
import { syntheticsAppPageProvider } from '../../page_objects/synthetics/synthetics_app';
import { SyntheticsServices } from './services/synthetics_services';

journey(`StepDetailsPage`, async ({ page, params }) => {
recordVideo(page);

page.setDefaultTimeout(60 * 1000);
const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl });

const services = new SyntheticsServices(params);

before(async () => {
await services.cleaUp();
await services.enableMonitorManagedViaApi();
await services.addTestMonitor(
'https://www.google.com',
{
type: 'browser',
urls: 'https://www.google.com',
custom_heartbeat_id: 'a47bfc4e-361a-4eb0-83f3-b5bb68781b5b',
locations: [
{ id: 'us_central', label: 'North America - US Central', isServiceManaged: true },
],
},
'a47bfc4e-361a-4eb0-83f3-b5bb68781b5b'
);
});

after(async () => {
await services.cleaUp();
});

step('Go to step details page', async () => {
await syntheticsApp.navigateToStepDetails({
stepIndex: 1,
checkGroup: 'ab240846-8d22-11ed-8fac-52bb19a2321e',
configId: 'a47bfc4e-361a-4eb0-83f3-b5bb68781b5b',
});
});

step('it shows metrics', async () => {
await page.waitForSelector('text=558 KB');
await page.waitForSelector('text=402 ms');
await page.waitForSelector('text=521 ms');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kib
}
},

async navigateToStepDetails({
configId,
stepIndex,
checkGroup,
doLogin = true,
}: {
checkGroup: string;
configId: string;
stepIndex: number;
doLogin?: boolean;
}) {
const stepDetails = `/monitor/${configId}/test-run/${checkGroup}/step/${stepIndex}?locationId=us_central`;
await page.goto(overview + stepDetails, { waitUntil: 'networkidle' });
if (doLogin) {
await this.loginToKibana();
}
},

async waitForMonitorManagementLoadingToFinish() {
while (true) {
if ((await page.$(this.byTestId('uptimeLoader'))) === null) break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,101 @@

import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { usePreviousObjectMetrics } from './use_prev_object_metrics';
import { MIME_FILTERS, MimeType, MimeTypesMap } from '../common/network_data/types';
import { networkEventsSelector } from '../../../state/network_events/selectors';

export const useObjectMetrics = () => {
const { checkGroupId, stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>();

const { mimeData: prevMimeData } = usePreviousObjectMetrics();

const _networkEvents = useSelector(networkEventsSelector);
const networkEvents = _networkEvents[checkGroupId ?? '']?.[Number(stepIndex)];

const objectTypeCounts: Record<string, number> = {};
const objectTypeWeights: Record<string, number> = {};
const objectTypeCounts: Record<string, { value: number; prevValue: number }> = {};
const objectTypeWeights: Record<string, { value: number; prevValue: number }> = {};

networkEvents?.events.forEach((event) => {
if (event.mimeType) {
objectTypeCounts[MimeTypesMap[event.mimeType] ?? MimeType.Other] =
(objectTypeCounts[MimeTypesMap[event.mimeType] ?? MimeType.Other] ?? 0) + 1;
objectTypeWeights[MimeTypesMap[event.mimeType] ?? MimeType.Other] =
(objectTypeWeights[MimeTypesMap[event.mimeType] ?? MimeType.Other] ?? 0) +
(event.transferSize || 0);
const mimeType = MimeTypesMap[event.mimeType] ?? MimeType.Other;

if (objectTypeCounts[mimeType]) {
objectTypeCounts[mimeType].value++;
} else {
objectTypeCounts[mimeType] = { value: 1, prevValue: 0 };
}

if (objectTypeWeights[mimeType]) {
objectTypeWeights[mimeType].value += event.transferSize || 0;
} else {
objectTypeWeights[mimeType] = {
value: event.transferSize || 0,
prevValue: 0,
};
}
}
});

const totalObjects = Object.values(objectTypeCounts).reduce((acc, val) => acc + val, 0);
const totalObjects = Object.values(objectTypeCounts).reduce((acc, val) => acc + val.value, 0);

const totalObjectsWeight = Object.values(objectTypeWeights).reduce((acc, val) => acc + val, 0);
const totalObjectsWeight = Object.values(objectTypeWeights).reduce(
(acc, val) => acc + val.value,
0
);

Object.keys(prevMimeData).forEach((mimeType) => {
const mimeTypeKey = MimeTypesMap[mimeType] ?? MimeType.Other;
if (objectTypeCounts[mimeTypeKey]) {
objectTypeCounts[mimeTypeKey].prevValue += prevMimeData[mimeType].count;
}

if (objectTypeWeights[mimeTypeKey]) {
objectTypeWeights[mimeTypeKey].prevValue += prevMimeData[mimeType].weight;
}
});

return {
loading: networkEvents?.loading ?? true,
totalObjects,
totalObjectsWeight: formatBytes(totalObjectsWeight),
items: MIME_FILTERS.map(({ label, mimeType }) => ({
label,
count: objectTypeCounts[mimeType] ?? 0,
total: totalObjects,
mimeType,
percent: ((objectTypeCounts[mimeType] ?? 0) / totalObjects) * 100,
weight: formatBytes(objectTypeWeights[mimeType] ?? 0),
weightPercent: ((objectTypeWeights[mimeType] ?? 0) / totalObjectsWeight) * 100,
total: totalObjects,
count: objectTypeCounts?.[mimeType]?.value ?? 0,
percent: ((objectTypeCounts?.[mimeType]?.value ?? 0) / totalObjects) * 100,
weight: formatBytes(objectTypeWeights[mimeType]?.value ?? 0),
weightPercent: ((objectTypeWeights[mimeType]?.value ?? 0) / totalObjectsWeight) * 100,

countDelta: getDeltaPercent(
objectTypeCounts?.[mimeType]?.value ?? 0,
objectTypeCounts?.[mimeType]?.prevValue ?? 0
),
weightDelta: getWeightDeltaPercent(
objectTypeWeights?.[mimeType]?.value,
objectTypeWeights?.[mimeType]?.prevValue
),
})),
};
};

export const getWeightDeltaPercent = (current: number, previous: number) => {
if (previous === 0 || !previous) {
return 0;
}

return (((current - previous) / previous) * 100).toFixed(0);
};

export const getDeltaPercent = (current: number, previous: number) => {
if (previous === 0) {
return 0;
}

return (((current - previous) / previous) * 100).toFixed(0);
};

export const formatBytes = (bytes: number, decimals = 0) => {
if (bytes === 0) return '0 Bytes';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useParams } from 'react-router-dom';
import moment from 'moment';
import { useJourneySteps } from '../../monitor_details/hooks/use_journey_steps';
import { useReduxEsSearch } from '../../../hooks/use_redux_es_search';
import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants';

export const MONITOR_DURATION_US = 'monitor.duration.us';
export const SYNTHETICS_CLS = 'browser.experience.cls';
export const SYNTHETICS_LCP = 'browser.experience.lcp.us';
export const SYNTHETICS_FCP = 'browser.experience.fcp.us';
export const SYNTHETICS_ONLOAD_EVENT = 'browser.experience.load.us';
export const SYNTHETICS_DCL = 'browser.experience.dcl.us';
export const SYNTHETICS_STEP_NAME = 'synthetics.step.name.keyword';
export const SYNTHETICS_STEP_DURATION = 'synthetics.step.duration.us';

export type PreviousObjectMetrics = ReturnType<typeof usePreviousObjectMetrics>;

export const usePreviousObjectMetrics = () => {
const { monitorId, stepIndex, checkGroupId } = useParams<{
monitorId: string;
stepIndex: string;
checkGroupId: string;
}>();

const { data } = useJourneySteps();

const timestamp = data?.details?.timestamp;

const { data: prevObjectMetrics } = useReduxEsSearch(
{
index: SYNTHETICS_INDEX_PATTERN,
body: {
track_total_hits: false,
sort: [
{
'@timestamp': {
order: 'desc',
},
},
],
size: 0,
runtime_mappings: {
'synthetics.payload.transfer_size': {
type: 'long',
},
},
query: {
bool: {
filter: [
{
range: {
'@timestamp': {
lte: timestamp ?? 'now',
gte: moment(timestamp).subtract(1, 'day').toISOString(),
},
},
},
{
term: {
config_id: monitorId,
},
},
{
term: {
'synthetics.type': 'journey/network_info',
},
},
{
term: {
'synthetics.step.index': stepIndex,
},
},
{
range: {
'@timestamp': {
gte: 'now-24h/h',
lte: 'now',
},
},
},
],
must_not: [
{
term: {
'monitor.check_group': {
value: checkGroupId,
},
},
},
],
},
},
aggs: {
testRuns: {
cardinality: {
field: 'monitor.check_group',
},
},
objectCounts: {
terms: {
field: 'http.response.mime_type',
size: 500,
},
aggs: {
weight: {
sum: {
field: 'synthetics.payload.transfer_size',
},
},
},
},
},
},
},
[stepIndex, monitorId, checkGroupId],
{
name: `previousObjectMetrics/${monitorId}/${checkGroupId}/${stepIndex}/`,
isRequestReady: !!timestamp,
}
);

const mimeData: Record<string, { weight: number; count: number }> = {};

const testRuns = prevObjectMetrics?.aggregations?.testRuns?.value ?? 0;

prevObjectMetrics?.aggregations?.objectCounts?.buckets?.forEach((bucket) => {
mimeData[bucket.key] = {
weight: bucket.weight.value ? bucket.weight.value / testRuns : 0,
count: bucket.doc_count / testRuns,
};
});

return { mimeData };
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { ReportTypes } from '@kbn/observability-plugin/public';
import { EuiSpacer, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ClientPluginsStart } from '../../../../plugin';
import { useSelectedLocation } from '../monitor_details/hooks/use_selected_location';
import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_flyout';

export const NetworkTimingsBreakdown = ({ monitorId }: { monitorId: string }) => {
const { observability } = useKibana<ClientPluginsStart>().services;
Expand All @@ -19,6 +21,11 @@ export const NetworkTimingsBreakdown = ({ monitorId }: { monitorId: string }) =>

const { stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>();

const selectedLocation = useSelectedLocation();
if (!selectedLocation) {
return <LoadingState />;
}

return (
<>
<EuiTitle size="xs">
Expand Down Expand Up @@ -47,6 +54,10 @@ export const NetworkTimingsBreakdown = ({ monitorId }: { monitorId: string }) =>
field: 'synthetics.step.index',
values: [stepIndex],
},
{
field: 'observer.geo.name',
values: [selectedLocation.label],
},
],
},
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const StepDetailPage = () => {
</EuiFlexItem>
<EuiFlexItem grow={2}>
<EuiPanel hasShadow={false} hasBorder>
<EuiFlexGroup>
<EuiFlexGroup gutterSize="xl">
<EuiFlexItem grow={1}>
<ObjectWeightList />
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ const StatThreshold = ({
{title}
<span style={{ marginLeft: 5 }}>
{isSame ? (
<EuiIcon type="minus" size="l" />
<EuiIcon type="minus" size="l" color="subdued" />
) : (
<EuiIcon type={isUp ? 'sortUp' : 'sortDown'} size="l" />
)}
Expand Down
Loading