sitespeed.io
Version:
sitespeed.io is an open-source tool for comprehensive web performance analysis, enabling you to test, monitor, and optimize your website’s speed using real browsers in various environments.
168 lines (146 loc) • 4.38 kB
JavaScript
import { getLogger } from '@sitespeed.io/log';
const log = getLogger('sitespeedio');
function joinNonEmpty(strings, delimeter) {
return strings.filter(Boolean).join(delimeter);
}
function toSafeKey(key) {
// U+2013 : EN DASH – as used on https://en.wikipedia.org/wiki/2019–20_coronavirus_pandemic
return key.replaceAll(/[ %&()+,./:?|~–]|%7C/g, '_');
}
export function keypathFromUrl(
urlString,
includeQueryParameters,
useHash,
group
) {
function flattenQueryParameters(parameters) {
return Object.keys(parameters).reduce(
(result, key) => joinNonEmpty([result, key, parameters[key]], '_'),
''
);
}
const url = new URL(urlString);
let path = toSafeKey(url.pathname);
if (includeQueryParameters) {
const parameters = {};
for (const [key, value] of url.searchParams) {
parameters[key] = value;
}
path = joinNonEmpty(
[path, toSafeKey(flattenQueryParameters(parameters))],
'_'
);
}
if (useHash && url.hash) {
path = joinNonEmpty([path, toSafeKey(url.hash)], '_');
}
const keys = [toSafeKey(group || url.hostname), path];
return joinNonEmpty(keys, '.');
}
function isNumericString(n) {
// eslint-disable-next-line unicorn/prefer-number-properties
return !isNaN(Number.parseFloat(n)) && isFinite(n);
}
export function flattenMessageData({ data, type }) {
function recursiveFlatten(target, keyPrefix, value) {
// super simple version to avoid flatten HAR and screenshot data
if (/(screenshots\.|har\.)/.test(keyPrefix)) {
return;
}
// Google is overloading User Timing marks
// See https://github.com/sitespeedio/browsertime/issues/257
if (keyPrefix.includes('userTimings.marks.goog_')) {
return;
}
// Google is overloading User Timing marks = the same using WebPageTest
// See https://github.com/sitespeedio/browsertime/issues/257
if (keyPrefix.includes('userTimes.goog_')) {
return;
}
// Hack to remove visual progress from default metrics
if (keyPrefix.includes('visualMetrics.VisualProgress')) {
return;
}
if (keyPrefix.includes('visualMetrics.videoRecordingStart')) {
return;
}
const valueType = typeof value;
switch (valueType) {
case 'number': {
{
if (Number.isFinite(value)) {
target[keyPrefix] = value;
} else {
log.warn(
`Non-finite number '${value}' found at path '${keyPrefix}' for '${type}' message (url = ${data.url})`
);
}
}
break;
}
case 'object': {
{
if (value === null) {
break;
}
for (const key of Object.keys(value)) {
// Hey are you coming to the future from 1980s? Please don't
// look at this code, it's a ugly hack to make sure we can send assets
// to Graphite and don't send them with array position, instead
// use the url to generate the key
if (type === 'pagexray.pageSummary' && keyPrefix === 'assets') {
recursiveFlatten(
target,
joinNonEmpty(
[keyPrefix, toSafeKey(value[key].url || key)],
'.'
),
value[key]
);
} else {
recursiveFlatten(
target,
joinNonEmpty([keyPrefix, toSafeKey(key)], '.'),
value[key]
);
}
}
}
break;
}
case 'string': {
{
if (isNumericString(value)) {
target[keyPrefix] = Number.parseFloat(value);
}
}
break;
}
case 'boolean': {
{
target[keyPrefix] = value ? 1 : 0;
}
break;
}
case 'undefined': {
{
log.debug(
`Undefined value found at path '${keyPrefix}' for '${type}' message (url = ${data.url})`
);
}
break;
}
default: {
throw new Error(
'Unhandled value type ' +
valueType +
' found when flattening data for prefix ' +
keyPrefix
);
}
}
}
let returnValueValue = {};
recursiveFlatten(returnValueValue, '', data);
return returnValueValue;
}