Skip to content

Commit

Permalink
Use timings directly from the Agent: elastic/synthetics#168
Browse files Browse the repository at this point in the history
  • Loading branch information
Kerry350 committed Dec 10, 2020
1 parent 7669f23 commit 149bfe6
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 292 deletions.
28 changes: 10 additions & 18 deletions x-pack/plugins/uptime/common/runtime_types/network_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,16 @@
import * as t from 'io-ts';

const NetworkTimingsType = t.type({
dns_start: t.number,
push_end: t.number,
worker_fetch_start: t.number,
worker_respond_with_settled: t.number,
proxy_end: t.number,
worker_start: t.number,
worker_ready: t.number,
send_end: t.number,
connect_end: t.number,
connect_start: t.number,
send_start: t.number,
proxy_start: t.number,
push_start: t.number,
ssl_end: t.number,
receive_headers_end: t.number,
ssl_start: t.number,
request_time: t.number,
dns_end: t.number,
queueing: t.number,
connect: t.number,
total: t.number,
send: t.number,
blocked: t.number,
receive: t.number,
wait: t.number,
dns: t.number,
proxy: t.number,
ssl: t.number,
});

export type NetworkTimings = t.TypeOf<typeof NetworkTimingsType>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,133 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { colourPalette, getTimings, getSeriesAndDomain, extractItems } from './data_formatting';

const networkEvent = {
timestamp: '2020-12-06T19:55:01.273Z',
method: 'GET',
url: 'https://www.some-fake-url.css',
status: 200,
mimeType: 'text/css',
requestSentTime: 1723033619.631,
requestStartTime: 1723033621.036,
loadEndTime: 1723033729.635,
timings: {
ssl_start: 32.875,
proxy_end: -1,
send_start: 69.738,
send_end: 69.923,
connect_start: 0.591,
receive_headers_end: 106.076,
dns_end: 0.591,
connect_end: 69.557,
worker_fetch_start: -1,
worker_ready: -1,
push_start: 0,
dns_start: 0.539,
ssl_end: 69.542,
request_time: 1723033.621036,
worker_respond_with_settled: -1,
worker_start: -1,
push_end: 0,
proxy_start: -1,
},
};

describe('getTimings', () => {
it('Calculates timings for network events correctly', () => {
const timings = getTimings(
networkEvent.timings,
networkEvent.requestSentTime,
networkEvent.loadEndTime
);
expect(timings).toEqual({
blocked: 1.9439999713897707,
connect: 32.480000000000004,
dns: 0.051999999999999935,
receive: 2.5230000019073486,
send: 0.18500000000000227,
ssl: 36.667,
wait: 36.15299999999999,
});
});
});

describe('getSeriesAndDomain', () => {
let seriesAndDomain: any;
let NetworkItems: any;

beforeAll(() => {
NetworkItems = extractItems([networkEvent]);
seriesAndDomain = getSeriesAndDomain(NetworkItems);
});

it('Correctly calculates the domain', () => {
expect(seriesAndDomain.domain).toEqual({ max: 110.00399997329711, min: 0 });
});

it('Correctly calculates the series', () => {
expect(seriesAndDomain.series).toEqual([
{
config: {
colour: '#b9a888',
tooltipProps: { colour: '#b9a888', value: 'Queued / Blocked: 1.944ms' },
},
x: 0,
y: 1.9439999713897707,
y0: 0,
},
{
config: { colour: '#54b399', tooltipProps: { colour: '#54b399', value: 'DNS: 0.052ms' } },
x: 0,
y: 1.9959999713897707,
y0: 1.9439999713897707,
},
{
config: {
colour: '#da8b45',
tooltipProps: { colour: '#da8b45', value: 'Connecting: 32.480ms' },
},
x: 0,
y: 34.475999971389776,
y0: 1.9959999713897707,
},
{
config: { colour: '#edc5a2', tooltipProps: { colour: '#edc5a2', value: 'SSL: 36.667ms' } },
x: 0,
y: 71.14299997138977,
y0: 34.475999971389776,
},
{
config: {
colour: '#d36086',
tooltipProps: { colour: '#d36086', value: 'Sending request: 0.185ms' },
},
x: 0,
y: 71.32799997138977,
y0: 71.14299997138977,
},
{
config: {
colour: '#b0c9e0',
tooltipProps: { colour: '#b0c9e0', value: 'Waiting (TTFB): 36.153ms' },
},
x: 0,
y: 107.48099997138976,
y0: 71.32799997138977,
},
{
config: {
colour: '#ca8eae',
tooltipProps: { colour: '#ca8eae', value: 'Content downloading: 2.523ms' },
},
x: 0,
y: 110.00399997329711,
y0: 107.48099997138976,
},
]);
});
});
import { colourPalette } from './data_formatting';

describe('Palettes', () => {
it('A colour palette comprising timing and mime type colours is correctly generated', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import { euiPaletteColorBlind } from '@elastic/eui';

import {
PayloadTimings,
CalculatedTimings,
NetworkItems,
NetworkItem,
FriendlyTimingLabels,
Expand All @@ -23,112 +21,10 @@ import {
import { WaterfallData } from '../../waterfall';
import { NetworkEvent } from '../../../../../../common/runtime_types';

const microToMillis = (micro: number): number => (micro === -1 ? -1 : micro * 1000);

// The timing calculations here are based off several sources:
// https://github.com/ChromeDevTools/devtools-frontend/blob/2fe91adefb2921b4deb2b4b125370ef9ccdb8d1b/front_end/sdk/HARLog.js#L307
// and
// https://chromium.googlesource.com/chromium/blink.git/+/master/Source/devtools/front_end/sdk/HAREntry.js#131
// and
// https://github.com/cyrus-and/chrome-har-capturer/blob/master/lib/har.js#L195
// and
// https://github.com/sitespeedio/chrome-har/blob/4586d2961fe8752982120c3f613b8da42cf3648b/lib/finalizeEntry.js#L7
// Order of events: request_start = 0, [proxy], [dns], [connect [ssl]], [send], receive_headers_end

export const getTimings = (
timings: NetworkEvent['timings'],
requestSentTime: NetworkEvent['requestSentTime'],
loadEndTime: NetworkEvent['loadEndTime']
): CalculatedTimings => {
if (!timings) return { blocked: -1, dns: -1, connect: -1, send: 0, wait: 0, receive: 0, ssl: -1 };

const getLeastNonNegative = (values: number[]) =>
values.reduce<number>((best, value) => (value >= 0 && value < best ? value : best), Infinity);
const getOptionalTiming = (_timings: PayloadTimings, key: keyof PayloadTimings) =>
_timings[key] >= 0 ? _timings[key] : -1;

// NOTE: Request sent and request start can differ due to queue times
const requestStartTime = microToMillis(timings.request_time);

// Queued
const queuedTime = requestSentTime < requestStartTime ? requestStartTime - requestSentTime : -1;

// Blocked
// "blocked" represents both queued time + blocked/stalled time + proxy time (ie: anything before the request was actually started).
let blocked = queuedTime;

const blockedStart = getLeastNonNegative([
timings.dns_start,
timings.connect_start,
timings.send_start,
]);

if (blockedStart !== Infinity) {
blocked += blockedStart;
}

// Proxy
// Proxy is part of blocked, but it can be quirky in that blocked can be -1 even though there are proxy timings. This can happen with
// protocols like Quic.
if (timings.proxy_end !== -1) {
const blockedProxy = timings.proxy_end - timings.proxy_start;

if (blockedProxy && blockedProxy > blocked) {
blocked = blockedProxy;
}
}

// DNS
const dnsStart = timings.dns_end >= 0 ? blockedStart : 0;
const dnsEnd = getOptionalTiming(timings, 'dns_end');
const dns = dnsEnd - dnsStart;

// SSL
const sslStart = getOptionalTiming(timings, 'ssl_start');
const sslEnd = getOptionalTiming(timings, 'ssl_end');
let ssl;

if (sslStart >= 0 && sslEnd >= 0) {
ssl = timings.ssl_end - timings.ssl_start;
}

// Connect
let connect = -1;
if (timings.connect_start >= 0) {
connect = timings.send_start - timings.connect_start;
}

// Send
const send = timings.send_end - timings.send_start;

// Wait
const waitStart = timings.send_end;
const waitEnd = timings.receive_headers_end;
const wait = waitEnd - waitStart;

const receive = loadEndTime - (requestStartTime + timings.receive_headers_end);

// SSL connection is a part of the overall connection time
if (connect && ssl) {
connect = connect - ssl;
}

return { blocked, dns, connect, send, wait, receive, ssl };
};

export const extractItems = (data: NetworkEvent[]): NetworkItems => {
return data
.map((entry) => {
return {
...entry,
timings: entry.timings
? getTimings(entry.timings, entry.requestSentTime, entry.loadEndTime)
: undefined,
};
})
.sort((a: NetworkItem, b: NetworkItem) => {
return a.requestSentTime - b.requestSentTime;
});
return data.sort((a: NetworkItem, b: NetworkItem) => {
return a.requestSentTime - b.requestSentTime;
});
};

const formatValueForDisplay = (value: number, points: number = 3) => {
Expand All @@ -141,50 +37,36 @@ const getColourForMimeType = (mimeType?: string) => {
};

export const getSeriesAndDomain = (items: NetworkItems) => {
const getValueForOffset = (item: NetworkItem) => {
return item.requestSentTime;
};

// The earliest point in time a request is sent or started. This will become our notion of "0".
const zeroOffset = items.reduce<number>((acc, item) => {
const { requestSentTime } = item;
return requestSentTime < acc ? requestSentTime : acc;
const offsetValue = getValueForOffset(item);
return offsetValue < acc ? offsetValue : acc;
}, Infinity);

const series = items.reduce<WaterfallData>((acc, item, index) => {
const { requestSentTime } = item;
const getValue = (timings: NetworkEvent['timings'], timing: Timings) => {
if (!timings) return;

// Entries without timings should be handled differently:
// https://github.com/ChromeDevTools/devtools-frontend/blob/ed2a064ac194bfae4e25c4748a9fa3513b3e9f7d/front_end/network/RequestTimingView.js#L140
// If there are no concrete timings just plot one block via request start and response end
if (!item.timings || item.timings === null) {
const duration = item.loadEndTime - item.requestSentTime;
const colour = getColourForMimeType(item.mimeType);
return [
...acc,
{
x: index,
y0: item.requestSentTime - zeroOffset,
// NOTE: The loadEndTime can sometimes be "0"
y:
item.loadEndTime && item.loadEndTime > 0
? item.loadEndTime - zeroOffset
: item.requestSentTime - zeroOffset,
config: {
colour,
tooltipProps: {
// NOTE: The loadEndTime can sometimes be "0"
value:
item.loadEndTime && item.loadEndTime > 0
? `${formatValueForDisplay(duration)}ms`
: "Response time couldn't be determined",
colour,
},
},
},
];
// SSL is a part of the connect timing
if (timing === Timings.Connect && timings.ssl > 0) {
return timings.connect - timings.ssl;
} else {
return timings[timing];
}
};

const series = items.reduce<WaterfallData>((acc, item, index) => {
if (!item.timings) return acc;

const offsetValue = getValueForOffset(item);

let currentOffset = requestSentTime - zeroOffset;
let currentOffset = offsetValue - zeroOffset;

TIMING_ORDER.forEach((timing) => {
const value = item.timings![timing];
const value = getValue(item.timings, timing);
const colour =
timing === Timings.Receive ? getColourForMimeType(item.mimeType) : colourPalette[timing];
if (value && value >= 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const FriendlyTimingLabels = {
}
),
[Timings.Ssl]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.ssl', {
defaultMessage: 'SSL',
defaultMessage: 'TLS',
}),
[Timings.Send]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.send', {
defaultMessage: 'Sending request',
Expand Down Expand Up @@ -145,9 +145,7 @@ export const MimeTypesMap: Record<string, MimeType> = {
'application/font-sfnt': MimeType.Font,
};

export type NetworkItem = Omit<NetworkEvent, 'timings'> & {
timings?: CalculatedTimings;
};
export type NetworkItem = NetworkEvent;
export type NetworkItems = NetworkItem[];

// NOTE: A number will always be present if the property exists, but that number might be -1, which represents no value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const getNetworkEvents: UMElasticsearchQueryFn<
requestSentTime,
requestStartTime,
loadEndTime,
timings: event._source.synthetics.payload.response?.timing,
timings: event._source.synthetics.payload.timings,
};
});
};

0 comments on commit 149bfe6

Please sign in to comment.