461 строка
13 KiB
JavaScript
461 строка
13 KiB
JavaScript
let crashesFile, crashes;
|
|
let tableOptions = {
|
|
'oom': {
|
|
value: true,
|
|
type: 'select',
|
|
},
|
|
'shutdownhang': {
|
|
value: false,
|
|
type: 'select',
|
|
},
|
|
'flash': {
|
|
value: false,
|
|
type: 'select',
|
|
},
|
|
'version': {
|
|
value: null,
|
|
type: 'option',
|
|
},
|
|
'sortBy': {
|
|
value: null,
|
|
type: 'option',
|
|
},
|
|
'crashesType': {
|
|
value: null,
|
|
type: 'option',
|
|
},
|
|
'graphType': {
|
|
value: null,
|
|
type: 'option',
|
|
}
|
|
};
|
|
|
|
function getOption(name) {
|
|
return tableOptions[name].value;
|
|
}
|
|
|
|
function getOptionType(name) {
|
|
return tableOptions[name].type;
|
|
}
|
|
|
|
function setOption(name, value) {
|
|
return tableOptions[name].value = value;
|
|
}
|
|
|
|
let onLoad = new Promise(function(resolve, reject) {
|
|
window.onload = resolve;
|
|
});
|
|
|
|
function crashesByKhours(rawCrashes) {
|
|
return rawCrashes.map(function(crashNum, i) {
|
|
return crashes.khours[i] ? (100 / crashes.throttle * crashNum * 1000 / crashes.khours[i]) : null;
|
|
});
|
|
}
|
|
|
|
function crashesByADI(rawCrashes) {
|
|
return rawCrashes.map(function(crashNum, i) {
|
|
return crashes.adi[i] ? (100 / crashes.throttle * crashNum * 1000000 / crashes.adi[i]) : null;
|
|
});
|
|
}
|
|
|
|
function crashesByTotalCrashes(rawCrashes) {
|
|
return rawCrashes.map(function(crashNum, i) {
|
|
return crashes.crash_by_day[i] ? 100 * crashNum / crashes.crash_by_day[i] : null;
|
|
});
|
|
}
|
|
|
|
function agoString(val, str) {
|
|
return val + ' ' + (val == 1 ? str : str + 's') + ' ago';
|
|
}
|
|
|
|
function prettyDate(date) {
|
|
date = new Date(date);
|
|
let today = new Date();
|
|
|
|
let hoursDiff = Math.round((today.getTime() - date.getTime()) / 3600000);
|
|
if (hoursDiff < 24) {
|
|
return agoString(hoursDiff, 'hour');
|
|
}
|
|
|
|
let daysDiff = Math.round((today.getTime() - date.getTime()) / 86400000);
|
|
if (daysDiff < 10) {
|
|
return agoString(daysDiff, 'day');
|
|
}
|
|
|
|
let weeksDiff = Math.round((today.getTime() - date.getTime()) / (7 * 86400000));
|
|
if (weeksDiff < 3) {
|
|
return agoString(weeksDiff, 'week');
|
|
}
|
|
|
|
let monthsDiff = (today.getMonth() + 12 * today.getFullYear()) - (date.getMonth() + 12 * date.getFullYear());
|
|
if (monthsDiff < 12) {
|
|
return agoString(monthsDiff, 'month');
|
|
}
|
|
|
|
return agoString(today.getFullYear() - date.getFullYear(), 'year');
|
|
}
|
|
|
|
function createGraph(svgElem, data, margin, totalWidth, totalHeight) {
|
|
let startDay = 1 + data.filter(d => d == null).length;
|
|
data = data.filter(d => d != null);
|
|
|
|
let width = totalWidth - margin.left - margin.right;
|
|
let height = totalHeight - margin.top - margin.bottom;
|
|
|
|
let x = d3.time.scale()
|
|
.range([0, width]);
|
|
|
|
let y = d3.scale.linear()
|
|
.range([height, 0]);
|
|
|
|
let xAxis = d3.svg.axis()
|
|
.scale(x)
|
|
.tickFormat(d3.time.format('%d'))
|
|
.ticks(data.length)
|
|
.orient('bottom');
|
|
|
|
let yAxis = d3.svg.axis()
|
|
.scale(y)
|
|
.orient('left');
|
|
|
|
let line = d3.svg.line()
|
|
.x(function(d, i) {
|
|
let date = new Date();
|
|
date.setHours(0, 0, 0, 0);
|
|
date.setDate(date.getDate() - startDay - i);
|
|
return x(date);
|
|
})
|
|
.y(function(d, i) { return y(d); });
|
|
|
|
let svg = d3.select(svgElem)
|
|
.attr('width', width + margin.left + margin.right)
|
|
.attr('height', height + margin.top + margin.bottom)
|
|
.append('g')
|
|
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
|
|
|
x.domain(d3.extent(data, function(d, i) {
|
|
let date = new Date();
|
|
date.setHours(0, 0, 0, 0);
|
|
date.setDate(date.getDate() - startDay - i);
|
|
return date;
|
|
}));
|
|
|
|
y.domain([0, d3.max(data, function(d) { return d; })]);
|
|
|
|
svg.append('g')
|
|
.attr('class', 'x axis')
|
|
.attr('transform', 'translate(0,' + height + ')')
|
|
.call(xAxis);
|
|
|
|
svg.append('g')
|
|
.attr('class', 'y axis')
|
|
.call(yAxis);
|
|
|
|
svg.append('path')
|
|
.attr('class', 'line')
|
|
.attr('d', line(data));
|
|
|
|
return svgElem;
|
|
}
|
|
|
|
function getVersion() {
|
|
return Number(crashes.versions[0].substring(0, crashes.versions[0].indexOf('.')));
|
|
}
|
|
|
|
function getFixedIn(bug) {
|
|
let version = getVersion();
|
|
|
|
if (bug['cf_status_firefox' + version] != '' &&
|
|
bug['cf_status_firefox' + version] != 'affected') {
|
|
return [];
|
|
}
|
|
|
|
let versionEnd = version;
|
|
if (getOption('version') == 'aurora') {
|
|
versionEnd += 1;
|
|
} else if (getOption('version') == 'beta') {
|
|
versionEnd += 2;
|
|
} else if (getOption('version') == 'release') {
|
|
versionEnd += 3;
|
|
}
|
|
|
|
let fixedIn = [];
|
|
for (version = version + 1; version <= versionEnd; version++) {
|
|
if (bug['cf_status_firefox' + version] === 'fixed' ||
|
|
bug['cf_status_firefox' + version] === 'verified') {
|
|
fixedIn.push(version);
|
|
}
|
|
}
|
|
|
|
return fixedIn;
|
|
}
|
|
|
|
function addRow(signature, obj) {
|
|
let table = document.getElementById('table');
|
|
|
|
let row = table.insertRow(table.rows.length);
|
|
|
|
let rank = row.insertCell(0);
|
|
rank.appendChild(document.createTextNode(obj.tc_rank));
|
|
|
|
let usersVolume = row.insertCell(1);
|
|
usersVolume.appendChild(document.createTextNode(obj.estimated_user_count));
|
|
|
|
let reportsVolume = row.insertCell(2);
|
|
reportsVolume.appendChild(document.createTextNode(obj.crash_count));
|
|
|
|
let key = row.insertCell(3);
|
|
|
|
let startupImage = document.createElement('img');
|
|
startupImage.title = (obj.startup_percent * 100).toFixed(2) + ' %';
|
|
startupImage.src = 'rocket_fly.png';
|
|
startupImage.width = 64 * obj.startup_percent;
|
|
startupImage.height = 64 * obj.startup_percent;
|
|
startupImage.style.paddingRight = 5;
|
|
key.appendChild(startupImage);
|
|
|
|
let signatureDiv = document.createElement('div');
|
|
signatureDiv.className = 'tooltip';
|
|
|
|
let signatureLink = document.createElement('a');
|
|
signatureLink.appendChild(document.createTextNode(signature.length > 50 ? signature.substr(0, 49) + '…' : signature));
|
|
signatureLink.href = 'https://crash-stats.mozilla.com/signature/?date=<%3D' + crashes.end_date + '&date=>%3D' + crashes.start_date + '&product=Firefox&' + crashes.versions.map(version => 'version=' + version).join('&') + '&signature=' + signature;
|
|
signatureDiv.appendChild(signatureLink);
|
|
|
|
signatureLink.onmouseover = function() {
|
|
let signatureTooltip = document.createElement('div');
|
|
signatureTooltip.className = 'tooltip-dialog';
|
|
let signatureImage = document.createElementNS(d3.ns.prefix.svg, 'svg');
|
|
signatureImage.setAttribute('width', 1200);
|
|
signatureImage.setAttribute('height', 900);
|
|
signatureTooltip.appendChild(signatureImage);
|
|
signatureDiv.appendChild(signatureTooltip);
|
|
correlations.graph(signatureImage, signature, getOption('version'), 'Firefox');
|
|
};
|
|
|
|
key.appendChild(signatureDiv);
|
|
|
|
let today = new Date();
|
|
let three_days_ago = new Date().setDate(today.getDate() - 3);
|
|
let ten_days_ago = new Date().setDate(today.getDate() - 10);
|
|
let bugs = row.insertCell(4);
|
|
obj.bugs
|
|
.sort((bug1, bug2) => new Date(bug2.last_change_time) - new Date(bug1.last_change_time))
|
|
.forEach(function(bug) {
|
|
let fixedIn = getFixedIn(bug);
|
|
|
|
let bugLink = document.createElement('a');
|
|
bugLink.appendChild(document.createTextNode(bug.id));
|
|
bugLink.title = (bug.resolution ? bug.resolution + ' - ' : '') +
|
|
'Last activity: ' + prettyDate(bug.last_change_time) +
|
|
((fixedIn.length > 0) ? (' - Fixed in ' + fixedIn.join(', ') + '.') : '');
|
|
bugLink.href = 'https://bugzilla.mozilla.org/show_bug.cgi?id=' + bug.id;
|
|
bugLink.className = bug.resolution != '' ? 'resolved' : '';
|
|
|
|
let bugDate = new Date(bug.last_change_time);
|
|
if (bugDate > three_days_ago) {
|
|
bugLink.style.color = 'green';
|
|
} else if (bugDate > ten_days_ago) {
|
|
bugLink.style.color = 'orange';
|
|
} else {
|
|
bugLink.style.color = 'red';
|
|
}
|
|
|
|
bugs.appendChild(bugLink);
|
|
|
|
if (fixedIn.length > 0) {
|
|
let exclamationMark = document.createElement('img');
|
|
exclamationMark.title = 'Fixed in ' + fixedIn.join(', ');
|
|
exclamationMark.src = 'exclamation_mark.svg';
|
|
exclamationMark.width = 16;
|
|
exclamationMark.height = 16;
|
|
|
|
bugs.appendChild(exclamationMark);
|
|
}
|
|
|
|
if (bug['cf_tracking_firefox' + getVersion()] !== '+' &&
|
|
(bug.resolution === '' || fixedIn.length > 0)) {
|
|
let questionMark = document.createElement('img');
|
|
questionMark.title = 'TRACK?';
|
|
questionMark.src = 'question_mark.svg';
|
|
questionMark.width = 16;
|
|
questionMark.height = 16;
|
|
|
|
bugs.appendChild(questionMark);
|
|
}
|
|
|
|
bugs.appendChild(document.createElement('br'));
|
|
});
|
|
|
|
let graph = row.insertCell(5);
|
|
|
|
let svgElem = document.createElementNS(d3.ns.prefix.svg, 'svg');
|
|
|
|
let margin = { top: 20, right: 20, bottom: 30, left: 50 };
|
|
let width = 500;
|
|
let height = 200;
|
|
|
|
if (getOption('graphType') === 'Crashes per usage hours') {
|
|
createGraph(svgElem, crashesByKhours(obj.crash_by_day), margin, width, height);
|
|
} else if (getOption('graphType') === 'Crashes per ADI') {
|
|
createGraph(svgElem, crashesByADI(obj.crash_by_day), margin, width, height);
|
|
} else if (getOption('graphType') === 'Crashes per total crashes') {
|
|
createGraph(svgElem, crashesByTotalCrashes(obj.crash_by_day), margin, width, height);
|
|
} else if (getOption('graphType') === 'Raw number of crashes') {
|
|
createGraph(svgElem, obj.crash_by_day, margin, width, height);
|
|
} else {
|
|
throw new Error('Unexpected graph type');
|
|
}
|
|
|
|
graph.appendChild(svgElem);
|
|
}
|
|
|
|
function buildTable() {
|
|
let file = getOption('version');
|
|
if (getOption('crashesType') === 'All crashes') {
|
|
file += '.json';
|
|
} else if (getOption('crashesType') === 'Startup crashes') {
|
|
file += '-startup.json'
|
|
}
|
|
|
|
let promise;
|
|
if (file === crashesFile) {
|
|
promise = Promise.resolve();
|
|
} else {
|
|
promise = fetch(file)
|
|
.then(function(response) {
|
|
return response.json();
|
|
})
|
|
.then(function(val) {
|
|
crashes = val;
|
|
});
|
|
|
|
crashesFile = file;
|
|
}
|
|
|
|
promise
|
|
.then(function() {
|
|
var khours_mean = crashes.khours.reduce((prev, cur) => prev + cur, 0) / crashes.khours.length;
|
|
|
|
crashes.khours = crashes.khours.map(function(crashNum, i) {
|
|
if ((i == 0 || i == 1) && crashes.khours[i] < khours_mean * 0.8) {
|
|
return 0;
|
|
}
|
|
|
|
return crashes.khours[i];
|
|
});
|
|
|
|
let svgElem = document.getElementById('overallGraph');
|
|
d3.select(svgElem).selectAll("*").remove();
|
|
|
|
let margin = { top: 20, right: 20, bottom: 30, left: 100 };
|
|
let width = 900;
|
|
let height = 300;
|
|
|
|
if (getOption('graphType') === 'Crashes per usage hours') {
|
|
createGraph(svgElem, crashesByKhours(crashes.crash_by_day), margin, width, height);
|
|
} else if (getOption('graphType') === 'Crashes per ADI') {
|
|
createGraph(svgElem, crashesByADI(crashes.crash_by_day), margin, width, height);
|
|
} else if (getOption('graphType') === 'Crashes per total crashes') {
|
|
createGraph(svgElem, crashesByTotalCrashes(crashes.crash_by_day), margin, width, height);
|
|
} else if (getOption('graphType') === 'Raw number of crashes') {
|
|
createGraph(svgElem, crashes.crash_by_day, margin, width, height);
|
|
} else {
|
|
throw new Error('Unexpected graph type');
|
|
}
|
|
|
|
// Order signatures by rank change or kairo's explosiveness.
|
|
Object.keys(crashes.signatures)
|
|
.sort((signature1, signature2) => {
|
|
let signatureObj1 = crashes.signatures[signature1];
|
|
let signatureObj2 = crashes.signatures[signature2];
|
|
|
|
if (getOption('sortBy') == 'Number of crash reports') {
|
|
if (signatureObj1.tc_rank > signatureObj2.tc_rank) {
|
|
return 1;
|
|
} else if (signatureObj1.tc_rank < signatureObj2.tc_rank) {
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (signatureObj1.estimated_user_count < signatureObj2.estimated_user_count) {
|
|
return 1;
|
|
} else if (signatureObj1.estimated_user_count > signatureObj2.estimated_user_count) {
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
})
|
|
.forEach(function(signature) {
|
|
if (!getOption('oom') && signature.toLowerCase().includes('oom')) {
|
|
return;
|
|
}
|
|
|
|
if (!getOption('shutdownhang') && signature.toLowerCase().includes('shutdownhang')) {
|
|
return;
|
|
}
|
|
|
|
if (!getOption('flash') && signature.match(/F_?[0-9]{10}_+/)) {
|
|
return;
|
|
}
|
|
|
|
addRow(signature, crashes.signatures[signature]);
|
|
});
|
|
})
|
|
.catch(function(err) {
|
|
console.error(err);
|
|
});
|
|
}
|
|
|
|
function rebuildTable() {
|
|
let table = document.getElementById('table');
|
|
|
|
while(table.rows.length > 1) {
|
|
table.deleteRow(table.rows.length - 1);
|
|
}
|
|
|
|
buildTable();
|
|
}
|
|
|
|
onLoad
|
|
.then(function() {
|
|
Object.keys(tableOptions)
|
|
.forEach(function(optionName) {
|
|
let optionType = getOptionType(optionName);
|
|
let elem = document.getElementById(optionName);
|
|
|
|
if (optionType === 'select') {
|
|
setOption(optionName, elem.checked);
|
|
|
|
elem.onchange = function() {
|
|
setOption(optionName, elem.checked);
|
|
rebuildTable();
|
|
};
|
|
} else if (optionType === 'option') {
|
|
setOption(optionName, elem.options[elem.selectedIndex].value);
|
|
|
|
elem.onchange = function() {
|
|
setOption(optionName, elem.options[elem.selectedIndex].value);
|
|
rebuildTable();
|
|
};
|
|
} else if (optionType === 'button') {
|
|
setOption(optionName, elem.value);
|
|
|
|
document.getElementById(optionName + 'Button').onclick = function() {
|
|
setOption(optionName, elem.value);
|
|
rebuildTable();
|
|
};
|
|
} else {
|
|
throw new Error('Unexpected option type.');
|
|
}
|
|
});
|
|
})
|
|
.then(function() {
|
|
buildTable();
|
|
})
|
|
.catch(function(err) {
|
|
console.error(err);
|
|
});
|