in hack/jenkins/test-flake-chart/flake_chart.js [416:665]
function displayEnvironmentChart(testData, environmentName) {
// Number of days to use to look for "flaky-est" tests.
const dateRange = 15;
// Number of tests to display in chart.
const topFlakes = 10;
testData = testData
// Filter to only contain unskipped runs of the requested environment.
.filter(test => test.environment === environmentName && test.status !== testStatus.SKIPPED);
const testRuns = testData
.groupBy(test => test.name);
const aggregatedRuns = new Map(testRuns.map(test => [
test[0].name,
new Map(aggregateRuns(test)
.map(runDate => [ runDate.date.getTime(), runDate ]))]));
const uniqueDates = new Set();
for (const [_, runDateMap] of aggregatedRuns) {
for (const [dateTime, _] of runDateMap) {
uniqueDates.add(dateTime);
}
}
const orderedDates = Array.from(uniqueDates).sort();
const recentDates = orderedDates.slice(-dateRange);
const previousDates = orderedDates.slice(-2 * dateRange, -dateRange);
const computeFlakePercentage = (runs, dates) => Array.from(runs).map(([testName, data]) => {
const {flakeCount, totalCount} = dates.map(date => {
const dateInfo = data.get(date);
return dateInfo === undefined ? null : {
flakeRate: dateInfo.flakeRate,
runs: dateInfo.jobs.length
};
}).filter(dateInfo => dateInfo != null)
.reduce(({flakeCount, totalCount}, {flakeRate, runs}) => ({
flakeCount: flakeRate * runs + flakeCount,
totalCount: runs + totalCount
}), {flakeCount: 0, totalCount: 0});
return {
testName,
flakeRate: totalCount === 0 ? 0 : flakeCount / totalCount,
};
});
const recentFlakePercentage = computeFlakePercentage(aggregatedRuns, recentDates)
.sort((a, b) => b.flakeRate - a.flakeRate);
const previousFlakePercentageMap = new Map(
computeFlakePercentage(aggregatedRuns, previousDates)
.map(({testName, flakeRate}) => [testName, flakeRate]));
const recentTopFlakes = recentFlakePercentage
.slice(0, topFlakes)
.map(({testName}) => testName);
const chartsContainer = document.getElementById('chart_div');
{
const data = new google.visualization.DataTable();
data.addColumn('date', 'Date');
for (const name of recentTopFlakes) {
data.addColumn('number', `Flake Percentage - ${name}`);
data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
}
data.addRows(
orderedDates.map(dateTime => [new Date(dateTime)].concat(recentTopFlakes.map(name => {
const data = aggregatedRuns.get(name).get(dateTime);
return data !== undefined ? [
data.flakeRate,
`<div style="padding: 1rem; font-family: 'Arial'; font-size: 14">
<b style="display: block">${name}</b><br>
<b>Date:</b> ${data.date.toLocaleString([], {dateStyle: 'medium'})}<br>
<b>Flake Percentage:</b> ${data.flakeRate.toFixed(2)}%<br>
<b>Jobs:</b><br>
${data.jobs.map(({ id, status }) => ` - <a href="${testGopoghLink(id, environmentName, name)}">${id}</a> (${status})`).join("<br>")}
</div>`
] : [null, null];
})).flat())
);
const options = {
title: `Flake rate by day of top ${topFlakes} of recent test flakiness (past ${dateRange} days) on ${environmentName}`,
width: window.innerWidth,
height: window.innerHeight,
pointSize: 10,
pointShape: "circle",
vAxes: {
0: { title: "Flake rate", minValue: 0, maxValue: 100 },
},
tooltip: { trigger: "selection", isHtml: true }
};
const flakeRateContainer = document.createElement("div");
flakeRateContainer.style.width = "100vw";
flakeRateContainer.style.height = "100vh";
chartsContainer.appendChild(flakeRateContainer);
const chart = new google.visualization.LineChart(flakeRateContainer);
chart.draw(data, options);
}
{
const dates = testData.map(run => run.date.getTime());
const startDate = new Date(dates.min());
const endDate = new Date(dates.max());
const weekDates = [];
let currentDate = startDate;
while (currentDate < endDate) {
weekDates.push(currentDate);
currentDate = new Date(currentDate);
currentDate.setDate(currentDate.getDate() + 7);
}
weekDates.push(currentDate);
weekDates.findRounded = function (value) {
let index = this.findIndex(v => value < v);
if (index == 0) {
return this[index];
}
if (index < 0) {
index = this.length;
}
return this[index - 1];
}
const aggregatedWeeklyRuns = new Map(testRuns.map(test => [
test[0].name,
new Map(aggregateWeeklyRuns(test, weekDates)
.map(runDate => [ weekDates.findRounded(runDate.date).getTime(), runDate ]))]));
const uniqueWeekDates = new Set();
for (const [_, runDateMap] of aggregatedWeeklyRuns) {
for (const [dateTime, _] of runDateMap) {
uniqueWeekDates.add(dateTime);
}
}
const orderedWeekDates = Array.from(uniqueWeekDates).sort();
const recentWeeklyTopFlakes = computeFlakePercentage(aggregatedWeeklyRuns, [orderedWeekDates[orderedWeekDates.length - 1]])
.sort((a, b) => b.flakeRate - a.flakeRate)
.slice(0, topFlakes)
.map(({testName}) => testName);
const data = new google.visualization.DataTable();
data.addColumn('date', 'Date');
for (const name of recentWeeklyTopFlakes) {
data.addColumn('number', `Flake Percentage - ${name}`);
data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
}
data.addRows(
orderedWeekDates.map(dateTime => [new Date(dateTime)].concat(recentTopFlakes.map(name => {
const data = aggregatedWeeklyRuns.get(name).get(dateTime);
return data !== undefined ? [
data.flakeRate,
`<div style="padding: 1rem; font-family: 'Arial'; font-size: 14">
<b style="display: block">${name}</b><br>
<b>Date:</b> ${data.date.toLocaleString([], {dateStyle: 'medium'})}<br>
<b>Flake Percentage:</b> ${data.flakeRate.toFixed(2)}%<br>
<b>Jobs:</b><br>
${data.jobs.map(({ id, status }) => ` - <a href="${testGopoghLink(id, environmentName, name)}">${id}</a> (${status})`).join("<br>")}
</div>`
] : [null, null];
})).flat())
);
const options = {
title: `Flake rate by week of top ${topFlakes} of recent test flakiness (past week) on ${environmentName}`,
width: window.innerWidth,
height: window.innerHeight,
pointSize: 10,
pointShape: "circle",
vAxes: {
0: { title: "Flake rate", minValue: 0, maxValue: 100 },
},
tooltip: { trigger: "selection", isHtml: true }
};
const flakeRateContainer = document.createElement("div");
flakeRateContainer.style.width = "100vw";
flakeRateContainer.style.height = "100vh";
chartsContainer.appendChild(flakeRateContainer);
const chart = new google.visualization.LineChart(flakeRateContainer);
chart.draw(data, options);
}
{
const jobData =
testData
.groupBy(run => run.date.getTime())
.map(runDate => ({
date: runDate[0].date,
runInfo: runDate
.oneOfEach(run => run.rootJob)
.map(run => ({
commit: run.commit,
rootJob: run.rootJob,
testCount: run.testCount,
totalDuration: run.totalDuration
}))
}))
.sort((a, b) => a.date - b.date)
.map(({date, runInfo}) => ({
date,
runInfo,
testCount: runInfo.map(job => job.testCount).average(),
totalDuration: runInfo.map(job => job.totalDuration).average(),
}));
const data = new google.visualization.DataTable();
data.addColumn('date', 'Date');
data.addColumn('number', 'Test Count');
data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
data.addColumn('number', 'Duration');
data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
data.addRows(
jobData.map(dateInfo => [
dateInfo.date,
dateInfo.testCount,
`<div style="padding: 1rem; font-family: 'Arial'; font-size: 14">
<b>Date:</b> ${dateInfo.date.toLocaleString([], {dateStyle: 'medium'})}<br>
<b>Test Count (averaged): </b> ${+dateInfo.testCount.toFixed(2)}<br>
<b>Jobs:</b><br>
${dateInfo.runInfo.map(job => ` - <a href="${testGopoghLink(job.rootJob, environmentName)}">${job.rootJob}</a> Test count: ${job.testCount}`).join("<br>")}
</div>`,
dateInfo.totalDuration,
`<div style="padding: 1rem; font-family: 'Arial'; font-size: 14">
<b>Date:</b> ${dateInfo.date.toLocaleString([], {dateStyle: 'medium'})}<br>
<b>Total Duration (averaged): </b> ${+dateInfo.totalDuration.toFixed(2)}<br>
<b>Jobs:</b><br>
${dateInfo.runInfo.map(job => ` - <a href="${testGopoghLink(job.rootJob, environmentName)}">${job.rootJob}</a> Total Duration: ${+job.totalDuration.toFixed(2)}s`).join("<br>")}
</div>`,
]));
const options = {
title: `Test count and total duration by day on ${environmentName}`,
width: window.innerWidth,
height: window.innerHeight,
pointSize: 10,
pointShape: "circle",
series: {
0: { targetAxisIndex: 0 },
1: { targetAxisIndex: 1 },
},
vAxes: {
0: { title: "Test Count", minValue: 0 },
1: { title: "Duration (seconds)", minValue: 0 },
},
tooltip: { trigger: "selection", isHtml: true }
};
const testCountContainer = document.createElement("div");
testCountContainer.style.width = "100vw";
testCountContainer.style.height = "100vh";
chartsContainer.appendChild(testCountContainer);
const chart = new google.visualization.LineChart(testCountContainer);
chart.draw(data, options);
}
chartsContainer.appendChild(
createRecentFlakePercentageTable(
recentFlakePercentage,
previousFlakePercentageMap,
environmentName));
}