ui/perfherder/perf-helpers/textualSummary.js (187 lines of code) (raw):
import numeral from 'numeral';
import sortBy from 'lodash/sortBy';
import {
thBaseUrl,
uiPerfherderBase,
getPerfAnalysisUrl,
} from '../../helpers/url';
import { alertStatusMap } from './constants';
import { getFrameworkName, getGraphsURL, getTimeRange } from './helpers';
import { Perfdocs } from './perfdocs';
export default class TextualSummary {
constructor(
frameworks,
alerts,
alertSummary,
copySummary = null,
browsertimeAlertsExtraData = [],
) {
this.frameworks = frameworks;
this.alerts = alerts;
this.alertSummary = alertSummary;
this.copySummary = copySummary;
this.browsertimeAlertsExtraData = browsertimeAlertsExtraData;
this.hasProfileUrls = this.browsertimeAlertsExtraData.some(
(a) => a.profile_url && a.prev_profile_url,
);
this.headerRow = `\n|--|--|--|--|--|${this.hasProfileUrls ? '--|' : ''}`;
this.ellipsesRow = `\n|...|...|...|...|...|${
this.hasProfileUrls ? '...|' : ''
}`;
}
get markdown() {
let resultStr = '';
const improved = sortBy(
this.alerts.filter((alert) => !alert.is_regression),
'amount_pct',
).reverse();
const regressed = sortBy(
this.alerts.filter(
(alert) =>
alert.is_regression && alert.status !== alertStatusMap.invalid,
),
'amount_pct',
).reverse();
const regressionsTable = this.getFormattedRegressions(regressed);
resultStr += regressionsTable;
const improvementsTable = this.getFormattedImprovements(improved);
resultStr += improvementsTable;
if (this.copySummary) {
resultStr = this.attachSummaryHeaderAndAlertLink(resultStr);
}
return resultStr;
}
attachSummaryHeaderAndAlertLink = (resultStr) => {
// add summary header if getting text for clipboard only
const created = new Date(this.alertSummary.created);
const summaryHeader = `== Change summary for alert #${
this.alertSummary.id
} (as of ${created.toUTCString()}) ==\n`;
resultStr = summaryHeader + resultStr;
// include link to alert if getting text for clipboard only
const alertLink = `${window.location.origin}/perfherder/alerts?id=${this.alertSummary.id}`;
resultStr += `\nFor up to date results, see: ${alertLink}`;
return resultStr;
};
formatAlert(alert) {
const numFormat = '0,0.00';
let amountPct;
const { repository, framework } = this.alertSummary;
const timeRange = getTimeRange(this.alertSummary);
const baseURL = thBaseUrl + uiPerfherderBase.slice(1);
const graphLink =
baseURL + getGraphsURL(alert, timeRange, repository, framework).slice(1);
if (alert.amount_pct.toFixed(0) === '0') {
// have extra fraction digits when rounding ends up with 0%
amountPct = alert.amount_pct.toFixed(2);
} else {
amountPct = alert.amount_pct.toFixed(0);
}
const prevValue = numeral(alert.prev_value).format(numFormat);
const newValue = numeral(alert.new_value).format(numFormat);
const { suite, test, machine_platform: platform } = alert.series_signature;
const extraOptions = alert.series_signature.extra_options.join(' ');
const updatedAlert = this.browsertimeAlertsExtraData.find(
(a) => alert.id === a.id,
);
const frameworkName = getFrameworkName(
this.frameworks,
this.alertSummary.framework,
);
const perfdocs = new Perfdocs(frameworkName, suite, platform);
const url = perfdocs.documentationURL;
const suiteName = perfdocs.hasDocumentation()
? `[${suite}](${url})`
: suite;
const suiteTestName = suite === test ? suiteName : `${suiteName} ${test}`;
const alertValues =
updatedAlert &&
updatedAlert.results_link &&
updatedAlert.prev_results_link
? `[${prevValue}](${updatedAlert.prev_results_link}) -> [${newValue}](${updatedAlert.results_link})`
: `${prevValue} -> ${newValue}`;
let maybeProfileLinks = '';
if (this.hasProfileUrls) {
// Add an additional column for the profiler before and after so users can
// find the profiler links easily. Only add this column if at least one alert
// has profile urls.
maybeProfileLinks =
updatedAlert &&
updatedAlert.profile_url &&
updatedAlert.prev_profile_url
? `[Before](${getPerfAnalysisUrl(
updatedAlert.prev_profile_url,
)})/[After](${getPerfAnalysisUrl(updatedAlert.profile_url)}) |`
: ' |';
}
return `| [${amountPct}%](${graphLink}) | ${suiteTestName} | ${platform} | ${extraOptions} | ${alertValues} | ${maybeProfileLinks}`;
}
formatAlertBulk(alerts) {
return alerts.map((alert) => this.formatAlert(alert, alerts)).join('\n');
}
getFormattedRegressions(regressed) {
let resultStr = '';
if (regressed.length > 0 && regressed.length <= 15) {
// add a newline if we displayed the header
if (this.copySummary) {
resultStr += '\n';
}
const formattedRegressions = this.formatAlertBulk(regressed);
// Add a column for the profiler links if at least one alert has them.
const maybeProfileColumn = this.hasProfileUrls
? ' **Performance Profiles** |'
: '';
resultStr += `### Regressions:\n\n| **Ratio** | **Test** | **Platform** | **Options** | **Absolute values (old vs new)** | ${maybeProfileColumn} ${this.headerRow} \n${formattedRegressions}\n`;
}
if (regressed.length > 15) {
// add a newline if we displayed the header
if (this.copySummary) {
resultStr += '\n';
}
const sortedRegressed = regressed.sort(
(a, b) => b.amount_pct - a.amount_pct,
);
const biggestTenRegressed = sortedRegressed.slice(0, 10);
const smallestFiveRegressed = sortedRegressed.slice(-5);
const formattedBiggestRegressions = this.formatAlertBulk(
biggestTenRegressed,
);
const formattedSmallestRegressions = this.formatAlertBulk(
smallestFiveRegressed,
);
// Add a column for the profiler links if at least one alert has them.
const maybeProfileColumn = this.hasProfileUrls
? ' **Performance Profiles** |'
: '';
resultStr += `### Regressions:\n\n| **Ratio** | **Test** | **Platform** | **Options** | **Absolute values (old vs new)** | ${maybeProfileColumn} ${this.headerRow} \n${formattedBiggestRegressions}`;
resultStr += this.ellipsesRow;
resultStr += `\n${formattedSmallestRegressions}\n`;
}
return resultStr;
}
getFormattedImprovements(improved) {
let resultStr = '';
if (improved.length > 0 && improved.length <= 6) {
// Add a newline if we displayed some regressions
if (resultStr.length > 0) {
resultStr += '\n';
}
const formattedImprovements = this.formatAlertBulk(improved);
// Add a column for the profiler links if at least one alert has them.
const maybeProfileColumn = this.hasProfileUrls
? ' **Performance Profiles** |'
: '';
resultStr += `### Improvements:\n\n| **Ratio** | **Test** | **Platform** | **Options** | **Absolute values (old vs new)** | ${maybeProfileColumn} ${this.headerRow} \n${formattedImprovements}\n`;
} else if (improved.length > 6) {
// Add a newline if we displayed some regressions
if (resultStr.length > 0) {
resultStr += '\n';
}
const sortedImproved = improved.sort(
(a, b) => b.amount_pct - a.amount_pct,
);
const biggestFiveImprovements = sortedImproved.slice(0, 5);
const smallestImprovement = sortedImproved.slice(-1);
const formattedBiggestImprovements = this.formatAlertBulk(
biggestFiveImprovements,
);
const formattedSmallestImprovement = this.formatAlertBulk(
smallestImprovement,
);
// Add a column for the profiler links if at least one alert has them.
const maybeProfileColumn = this.hasProfileUrls
? ' **Performance Profiles** |'
: '';
resultStr += `### Improvements:\n\n| **Ratio** | **Test** | **Platform** | **Options** | **Absolute values (old vs new)** | ${maybeProfileColumn} ${this.headerRow} \n${formattedBiggestImprovements}`;
resultStr += this.ellipsesRow;
resultStr += `\n${formattedSmallestImprovement}\n`;
}
return resultStr;
}
}