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

feat(rum-explorer): perf tweaks (backport from oversight) #652

Merged
merged 1 commit into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
68 changes: 36 additions & 32 deletions tools/rum/charts/skyline.js
Original file line number Diff line number Diff line change
Expand Up @@ -514,39 +514,43 @@ export default class SkylineChart extends AbstractChart {
const group = this.dataChunks.group(this.groupBy);
const chartLabels = Object.keys(group).sort();

const iGoodLCPs = Object.entries(this.dataChunks.aggregates)
const {
iGoodLCPs,
iNiLCPs,
iPoorLCPs,
iGoodCLSs,
iNiCLSs,
iPoorCLSs,
iGoodINPs,
iNiINPs,
iPoorINPs,
allTraffic,
} = Object.entries(this.dataChunks.aggregates)
.sort(([a], [b]) => a.localeCompare(b))
.map(([, totals]) => -totals.iGoodLCP.weight);
const iNiLCPs = Object.entries(this.dataChunks.aggregates)
.sort(([a], [b]) => a.localeCompare(b))
.map(([, totals]) => -totals.iNiLCP.weight);
const iPoorLCPs = Object.entries(this.dataChunks.aggregates)
.sort(([a], [b]) => a.localeCompare(b))
.map(([, totals]) => -totals.iPoorLCP.weight);

const iGoodCLSs = Object.entries(this.dataChunks.aggregates)
.sort(([a], [b]) => a.localeCompare(b))
.map(([, totals]) => -totals.iGoodCLS.weight);
const iNiCLSs = Object.entries(this.dataChunks.aggregates)
.sort(([a], [b]) => a.localeCompare(b))
.map(([, totals]) => -totals.iNiCLS.weight);
const iPoorCLSs = Object.entries(this.dataChunks.aggregates)
.sort(([a], [b]) => a.localeCompare(b))
.map(([, totals]) => -totals.iPoorCLS.weight);

const iGoodINPs = Object.entries(this.dataChunks.aggregates)
.sort(([a], [b]) => a.localeCompare(b))
.map(([, totals]) => -totals.iGoodINP.weight);
const iNiINPs = Object.entries(this.dataChunks.aggregates)
.sort(([a], [b]) => a.localeCompare(b))
.map(([, totals]) => -totals.iNiINP.weight);
const iPoorINPs = Object.entries(this.dataChunks.aggregates)
.sort(([a], [b]) => a.localeCompare(b))
.map(([, totals]) => -totals.iPoorINP.weight);

const allTraffic = Object.entries(this.dataChunks.aggregates)
.sort(([a], [b]) => a.localeCompare(b))
.map(([, totals]) => totals.pageViews.sum);
.reduce((acc, [, totals]) => {
acc.iGoodLCPs.push(-totals.iGoodLCP.weight);
acc.iNiLCPs.push(-totals.iNiLCP.weight);
acc.iPoorLCPs.push(-totals.iPoorLCP.weight);
acc.iGoodCLSs.push(-totals.iGoodCLS.weight);
acc.iNiCLSs.push(-totals.iNiCLS.weight);
acc.iPoorCLSs.push(-totals.iPoorCLS.weight);
acc.iGoodINPs.push(-totals.iGoodINP.weight);
acc.iNiINPs.push(-totals.iNiINP.weight);
acc.iPoorINPs.push(-totals.iPoorINP.weight);
acc.allTraffic.push(totals.pageViews.sum);
return acc;
}, {
iGoodLCPs: [],
iNiLCPs: [],
iPoorLCPs: [],
iGoodCLSs: [],
iNiCLSs: [],
iPoorCLSs: [],
iGoodINPs: [],
iNiINPs: [],
iPoorINPs: [],
allTraffic: [],
});

this.chart.data.datasets[0].data = allTraffic;

Expand Down
40 changes: 19 additions & 21 deletions tools/rum/cruncher.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,16 @@ function groupFn(groupByFn) {
* @typedef {Object} Aggregate - an object that contains aggregate metrics
*/
class Aggregate {
constructor(parent = null) {
constructor(parentProvider = () => null) {
this.count = 0;
this.sum = 0;
this.weight = 0;
this.values = [];
this.parent = parent;
this.parentProvider = parentProvider;
}

get parent() {
return this.parentProvider();
}

get min() {
Expand Down Expand Up @@ -654,14 +658,11 @@ export class DataChunks {
.filter(skipFilterFn)
.filter(([, desiredValues]) => desiredValues.length)
.filter(existenceFilterFn);
return bundles.filter((bundle) => {
const matches = filterBy.map(([attributeName, desiredValues]) => {
const actualValues = valuesExtractorFn(attributeName, bundle, this);
const combiner = combinerExtractorFn(attributeName, this);
return desiredValues[combiner]((value) => actualValues.includes(value));
});
return matches.every((match) => match);
});
return bundles.filter((bundle) => filterBy.every(([attributeName, desiredValues]) => {
const actualValues = valuesExtractorFn(attributeName, bundle, this);
const combiner = combinerExtractorFn(attributeName, this);
return desiredValues[combiner]((value) => actualValues.includes(value));
}));
}

/**
Expand Down Expand Up @@ -764,7 +765,7 @@ export class DataChunks {
aggregateFn(valueFn),
// we reference the totals object here, so that we can
// calculate the share and percentage metrics
new Aggregate(this.totals[seriesName]),
new Aggregate(() => this.totals[seriesName]),
);
return accInner;
}, {});
Expand Down Expand Up @@ -810,20 +811,17 @@ export class DataChunks {
// go over each function in this.series and each value in filteredIn
// and appy the function to the value
if (Object.keys(this.totalsIn).length) return this.totalsIn;
const parentTotals = Object.entries(this.series)
.reduce((acc, [seriesName, valueFn]) => {
acc[seriesName] = this.filtered.reduce(
aggregateFn(valueFn),
new Aggregate(),
);
return acc;
}, {});
this.totalsIn = Object.entries(this.series)
.reduce((acc, [seriesName, valueFn]) => {
acc[seriesName] = this.filtered.reduce(
const parent = this.filtered.reduce(
aggregateFn(valueFn),
new Aggregate(parentTotals[seriesName]),
new Aggregate(),
);
// we need to clone the aggregate object, so that we can use it as its own parent
// this is necessary for calculating the share and percentage metrics
// the alternative would be to calculate the totals for each group twice (which is slower)
acc[seriesName] = Object.assign(Object.create(Object.getPrototypeOf(parent)), parent);
acc[seriesName].parentProvider = () => parent;
return acc;
}, {});
return this.totalsIn;
Expand Down
6 changes: 2 additions & 4 deletions tools/rum/slicer.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,6 @@ async function loadData(scope) {
if (scope === 'year') {
dataChunks.load(await loader.fetchPrevious12Months(endDate));
}

draw();
}

export function updateState() {
Expand Down Expand Up @@ -339,7 +337,7 @@ const io = new IntersectionObserver((entries) => {
elems.incognito.addEventListener('change', async () => {
loader.domainKey = elems.incognito.getAttribute('domainkey');
await loadData(view);
herochart.draw();
draw();
});

herochart.render();
Expand All @@ -353,7 +351,7 @@ const io = new IntersectionObserver((entries) => {
elems.timezoneElement.textContent = timezone;

if (elems.incognito.getAttribute('domainkey')) {
loadData(view);
loadData(view).then(draw);
}

elems.filterInput.addEventListener('input', () => {
Expand Down
39 changes: 26 additions & 13 deletions tools/rum/utils.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
/* helpers */
export function scoreValue(value, ni, poor) {
if (value >= poor) return 'poor';
if (value >= ni) return 'ni';
return 'good';
}

export function isKnownFacet(key) {
return false // TODO: find a better way to filter out non-facet keys
|| key === 'userAgent'
Expand All @@ -30,13 +24,32 @@ export function isKnownFacet(key) {

export function scoreCWV(value, name) {
if (value === undefined || value === null) return null;
const limits = {
lcp: [2500, 4000],
cls: [0.1, 0.25],
inp: [200, 500],
ttfb: [800, 1800],
};
return scoreValue(value, ...limits[name]);
let poor;
let ni;
// this is unrolled on purpose as this method becomes a bottleneck
if (name === 'lcp') {
poor = 4000;
ni = 2500;
}
if (name === 'cls') {
poor = 0.25;
ni = 0.1;
}
if (name === 'inp') {
poor = 500;
ni = 200;
}
if (name === 'ttfb') {
poor = 1800;
ni = 800;
}
if (value >= poor) {
return 'poor';
}
if (value >= ni) {
return 'ni';
}
return 'good';
}
export const UA_KEY = 'userAgent';
export function toHumanReadable(num) {
Expand Down
Loading