UNPKG

browsertime

Version:

Get performance metrics from your web page using Browsertime.

380 lines (341 loc) 10.4 kB
import { createRequire } from 'node:module'; import merge from 'lodash.merge'; import pick from 'lodash.pick'; import isEmpty from 'lodash.isempty'; import get from 'lodash.get'; import dayjs from 'dayjs'; import { pathToFolder } from '../pathToFolder.js'; import intel from 'intel'; const log = intel.getLogger('browsertime'); const require = createRequire(import.meta.url); const version = require('../../../package.json').version; function generateUniquePageId(baseId, existingIdMap) { let newId = baseId; while (existingIdMap.has(newId)) { newId = newId + '-1'; } return newId; } function getDocumentRequests(entries, pageId) { let pageEntries = [...entries]; if (pageId) { pageEntries = entries.filter(entry => entry.pageref === pageId); } const requests = []; let entry; do { entry = pageEntries.shift(); requests.push(entry); } while (entry.response.redirectURL); return requests; } function getFinalURL(entries, pageref) { const requests = getDocumentRequests(entries, pageref); const finalEntry = requests.pop(); return finalEntry.request.url; } function addExtrasToHAR( harPage, visualMetricsData, timings, cpu, googleWebVitals ) { const harPageTimings = harPage.pageTimings; const _visualMetrics = (harPage._visualMetrics = {}); harPage._cpu = cpu; if (googleWebVitals) { harPage._googleWebVitals = googleWebVitals; } // We add the timings both as a hidden field and add // in pageTimings so we can easily show them in PerfCascade if (visualMetricsData) { const DO_NOT_INCLUDE_IN_HAR_TIMINGS = new Set([ 'VisualReadiness', 'videoRecordingStart', 'SpeedIndex', 'PerceptualSpeedIndex', 'ContentfulSpeedIndex', 'VisualProgress', 'ContentfulSpeedIndexProgress', 'PerceptualSpeedIndexProgress' ]); for (let key of Object.keys(visualMetricsData)) { if (!DO_NOT_INCLUDE_IN_HAR_TIMINGS.has(key)) { harPageTimings['_' + key.charAt(0).toLowerCase() + key.slice(1)] = visualMetricsData[key]; _visualMetrics[key] = visualMetricsData[key]; } else if (!key.endsWith('Progress')) { _visualMetrics[key] = visualMetricsData[key]; } } // Make the visual progress structure more JSON _visualMetrics.VisualProgress = jsonifyVisualProgress( visualMetricsData.VisualProgress ); } else if (timings && timings.firstPaint) { // only add first paint if we don't have visual metrics harPageTimings._firstPaint = timings.firstPaint; } /* if (cpu && cpu.longTasks && cpu.longTasks.lastLongTask) { harPageTimings._lastCPULongTask = cpu.longTasks.lastLongTask; } */ if (timings && timings.largestContentfulPaint) { harPageTimings._largestContentfulPaint = timings.largestContentfulPaint.renderTime; } if ( timings && timings.paintTiming && timings.paintTiming['first-contentful-paint'] ) { harPageTimings._firstContentfulPaint = timings.paintTiming['first-contentful-paint']; } if (timings && timings.pageTimings) { harPageTimings._domInteractiveTime = timings.pageTimings.domInteractiveTime; harPageTimings._domContentLoadedTime = timings.pageTimings.domContentLoadedTime; } } function addMetaToHAR(index, harPage, url, browserScript, options) { const _meta = (harPage._meta = {}); _meta.connectivity = get(options, 'connectivity.profile', 'native'); _meta.connectivity = get(options, 'connectivity.alias', _meta.connectivity); if (options.resultURL) { const base = options.resultURL.endsWith('/') ? options.resultURL : options.resultURL + '/'; if (options.screenshot) { _meta.screenshot = `${base}${pathToFolder(url, options)}screenshots/${ index + 1 }/afterPageCompleteCheck.${options.screenshotParams.type}`; } if (options.video) { _meta.video = `${base}${pathToFolder(url, options)}video/${ index + 1 }.mp4`; } if (options.chrome && options.chrome.timeline) { _meta.timeline = `${base}${pathToFolder(url, options)}trace-${ index + 1 }.json.gz`; } } if (browserScript && browserScript.pageinfo) { _meta.generator = browserScript.pageinfo.generator; } } function jsonifyVisualProgress(visualProgress) { // Original data looks like // "0=0%, 1500=81%, 1516=81%, 1533=84%, 1550=84%, 1566=84%, 1600=95%, 1683=95%, 1833=100%" const visualProgressJSON = {}; for (let value of visualProgress) { visualProgressJSON[value.timestamp] = value.percent; } return visualProgressJSON; } export function addBrowser(har, name, version, comment) { merge(har.log, { browser: { name, version, comment } }); if (!comment) { delete har.log.browser.comment; } return har; } export function addCreator(har, comment) { merge(har.log, { creator: { name: 'Browsertime', version: version, comment: comment } }); if (!comment) { delete har.log.creator.comment; } return har; } export function getMainDocumentTimings(har) { const timings = []; const entries = [...har.log.entries]; for (let page of har.log.pages) { const pageId = page.id; const url = page._url; let pageEntries = [...entries]; const finalURL = getFinalURL(pageEntries, pageId); pageEntries = pageEntries.filter( entry => entry.pageref === pageId && entry.request.url === finalURL ); timings.push({ url, timings: pageEntries[0].timings }); } return timings; } export function getFullyLoaded(har) { const fullyLoaded = []; const entries = [...har.log.entries]; for (let page of har.log.pages) { const pageStartDateTime = new Date(page.startedDateTime).getTime(); const pageId = page.id; const url = page._url; let pageEntries = [...entries]; pageEntries = pageEntries.filter(entry => entry.pageref === pageId); let pageEnd = 0; for (let entry of pageEntries) { let entryEnd = new Date(entry.startedDateTime).getTime() + entry.time - new Date(pageStartDateTime).getTime(); if (entryEnd > pageEnd) { pageEnd = entryEnd; } } fullyLoaded.push({ url, fullyLoaded: Number(pageEnd.toFixed(0)) }); } return fullyLoaded; } export function mergeHars(hars) { if (isEmpty(hars)) { return; } if (hars.length === 1) { return hars[0]; } let firstLog = hars[0].log; let combinedHar = { log: pick(firstLog, ['version', 'creator', 'browser', 'comment']) }; let pagesById = new Map(); let allEntries = []; for (const har of hars) { let pages = har.log.pages; let entries = har.log.entries; for (const page of pages) { let pageId = page.id; if (pagesById.has(pageId)) { const oldPageId = pageId; pageId = generateUniquePageId(oldPageId, pagesById); page.id = pageId; entries = entries.map(entry => { if (entry.pageref === oldPageId) { entry.pageref = pageId; } return entry; }); } pagesById.set(pageId, page); } allEntries = [...allEntries, ...entries]; } combinedHar.log.pages = [...pagesById.values()]; combinedHar.log.entries = allEntries; return combinedHar; } export function getEmptyHAR(url, browser) { return { log: { version: '1.2', creator: { name: 'Browsertime', version: version, comment: '' }, browser: { name: browser, version: '' }, pages: [ { startedDateTime: dayjs().format(), id: 'failing_page', title: url, pageTimings: {}, comment: '' } ], entries: [], comment: '' } }; } export function addExtraFieldsToHar(totalResults, har, options) { if (har) { let harPageNumber = 0; // We test one page // Let's do a better fix for this later on // right now this fixes https://github.com/sitespeedio/browsertime/issues/754 if (har.log.pages.length === options.iterations) { for (let harPage of har.log.pages) { let pageNumber = harPageNumber % totalResults.length; const visualMetric = totalResults[pageNumber].visualMetrics[harPageNumber]; const browserScript = totalResults[pageNumber].browserScripts[harPageNumber]; const cpu = totalResults[pageNumber].cpu[harPageNumber]; const googleWebVitals = totalResults[pageNumber].googleWebVitals[harPageNumber]; addMetaToHAR( harPageNumber, harPage, totalResults[pageNumber].info.url, browserScript, options ); if (browserScript) { addExtrasToHAR( harPage, visualMetric, browserScript.timings, cpu, googleWebVitals ); } harPageNumber++; } } else { const numberOfPages = totalResults.length; for (let page = 0; page < numberOfPages; page++) { for (let iteration = 0; iteration < options.iterations; iteration++) { let harIndex = iteration * totalResults.length; harIndex += page; const visualMetric = totalResults[page].visualMetrics[iteration]; const browserScript = totalResults[page].browserScripts[iteration]; const cpu = totalResults[page].cpu[iteration]; const googleWebVitals = totalResults[page].googleWebVitals[iteration]; const harPage = har.log.pages[harIndex]; // Only add meta if we have a HAR if (harPage) { addMetaToHAR( iteration, harPage, totalResults[page].info.url, browserScript, options ); } else { log.error( 'Could not add meta data to the HAR, miss page ' + harIndex ); } // Only add the metrics if we was able to collect the metrics and have a HAR if (browserScript && harPage) { addExtrasToHAR( harPage, visualMetric, browserScript.timings, cpu, googleWebVitals ); } } } } } }