stab-crashes/correlations.js

529 строки
16 KiB
JavaScript

var correlations = (() => {
let correlationData = {};
function sha1(str) {
return crypto.subtle.digest('SHA-1', new TextEncoder('utf-8').encode(str))
.then(hash => hex(hash));
}
function hex(buffer) {
let hexCodes = [];
let view = new DataView(buffer);
for (let i = 0; i < view.byteLength; i += 4) {
// Using getUint32 reduces the number of iterations needed (we process 4 bytes each time).
let value = view.getUint32(i);
// toString(16) will give the hex representation of the number without padding.
let stringValue = value.toString(16);
// We use concatenation and slice for padding.
let padding = '00000000';
let paddedValue = (padding + stringValue).slice(-padding.length);
hexCodes.push(paddedValue);
}
// Join all the hex strings into one
return hexCodes.join('');
}
function getDataURL(product) {
if (product === 'Firefox') {
return 'https://analysis-output.telemetry.mozilla.org/top-signatures-correlations/data/';
} else if (product === 'FennecAndroid') {
return 'https://analysis-output.telemetry.mozilla.org/top-fennec-signatures-correlations/data/';
} else {
throw new Error('Unknown product: ' + product);
}
}
function loadChannelsData(product) {
if (correlationData[product]) {
return Promise.resolve();
}
return fetch(getDataURL(product) + 'all.json.gz')
.then(response => response.json())
.then(totals => {
correlationData[product] = {
'date': totals['date'],
};
let channels = ['release', 'beta', 'aurora', 'nightly'];
if (product === 'Firefox') {
channels.push('esr');
}
for (let ch of channels) {
correlationData[product][ch] = {
'total': totals[ch],
'signatures': {},
}
}
});
}
function loadCorrelationData(signature, channel, product) {
return loadChannelsData(product)
.then(() => {
if (signature in correlationData[product][channel]['signatures']) {
return;
}
return sha1(signature)
.then(sha1signature => fetch(getDataURL(product) + channel + '/' + sha1signature + '.json.gz'))
.then(response => response.json())
.then(data => {
correlationData[product][channel]['signatures'][signature] = data;
});
})
.catch(() => {})
.then(() => correlationData);
}
function getAnalysisDate(product) {
return loadChannelsData(product)
.then(() => correlationData[product]['date'])
.catch(() => '');
}
function itemToLabel(item) {
return Object.getOwnPropertyNames(item)
.map(key => key + ' = ' + item[key])
.join(' ∧ ');
}
function toPercentage(num) {
let result = (num * 100).toFixed(2);
if (result == '100.00') {
return '100.0';
}
if (result.substring(0, result.indexOf('.')).length == 1) {
return '0' + result;
}
return result;
}
function confidenceInterval(count1, total1, count2, total2) {
let prop1 = count1 / total1;
let prop2 = count2 / total2;
let diff = prop1 - prop2;
// Wald 95% confidence interval for the difference between the proportions.
let standard_error = Math.sqrt(prop1 * (1 - prop1) / total1 + prop2 * (1 - prop2) / total2);
let ci = [diff - 1.96 * standard_error, diff + 1.96 * standard_error];
// Yates continuity correction for the confidence interval.
let correction = 0.5 * (1.0 / total1 + 1.0 / total2);
return [ci[0] - correction, ci[1] + correction];
}
function sortCorrelationData(correlationData, total_reference, total_group) {
return correlationData
.sort((a, b) => {
let rule_a_len = Object.keys(a.item).length;
let rule_b_len = Object.keys(b.item).length;
if (rule_a_len < rule_b_len) {
return -1;
}
if (rule_a_len > rule_b_len) {
return 1;
}
// Then, sort by percentage difference between signature and
// overall (using the lower endpoint of the confidence interval
// of the difference).
let ciA = null;
if (a.prior) {
// If one of the two elements has a prior that alters a rule's
// distribution significantly, sort by the percentage of the rule
// given the prior.
ciA = confidenceInterval(a.prior.count_group, a.prior.total_group, a.prior.count_reference, a.prior.total_reference);
}
else {
ciA = confidenceInterval(a.count_group, total_group, a.count_reference, total_reference);
}
let ciB = null;
if (b.prior) {
ciB = confidenceInterval( b.prior.count_group, b.prior.total_group, b.prior.count_reference, b.prior.total_reference);
}
else {
ciB = confidenceInterval(b.count_group, total_group, b.count_reference, total_reference);
}
return Math.min(Math.abs(ciB[0]), Math.abs(ciB[1])) - Math.min(Math.abs(ciA[0]), Math.abs(ciA[1]));
});
}
function itemEqual(item1, item2) {
let keys1 = Object.keys(item1);
let keys2 = Object.keys(item2);
if (keys1.length !== keys2.length) {
return false;
}
for (let prop of keys1.concat(keys2)) {
let val1 = item1[prop];
let val2 = item2[prop];
if (typeof val1 === 'string') {
val1 = val1.toLowerCase();
}
if (typeof val2 === 'string') {
val2 = val2.toLowerCase();
}
if (item1[prop] !== item2[prop]) {
return false;
}
}
return true;
}
let channelsData = {};
function loadChannelsDifferencesData(product) {
return fetch('https://analysis-output.telemetry.mozilla.org/channels-differences/data/differences.json.gz')
.then(response => response.json())
.then(data => channelsData = data);
}
function socorroToTelemetry(socorroKey, socorroValue) {
let valueMapping = {
'cpu_arch': {
values: {
'amd64': 'x86-64',
},
},
'os_arch': {
values: {
'amd64': 'x86-64',
},
},
'platform': {
key: 'os_name',
values: {
'Mac OS X': 'Darwin',
'Windows NT': 'Windows_NT',
}
},
'platform_version': {
key: 'os_version',
},
'platform_pretty_version': {
key: 'os_pretty_version',
values: {
'Windows 10': '10.0',
'Windows 8.1': '6.3',
'Windows 8': '6.2',
'Windows 7': '6.1',
'Windows Server 2003': '5.2',
'Windows XP': '5.1',
'Windows 2000': '5.0',
'Windows NT': '4.0',
}
},
'e10s_enabled': {
key: 'e10s_enabled',
values: {
'1': true,
}
},
'dom_ipc_enabled': {
key: 'e10s_enabled',
values: {
'1': true,
}
},
'"D2D1.1+" in app_notes': {
key: 'd2d_enabled',
},
'"D2D1.1-" in app_notes': {
key: 'd2d_enabled',
values: v => !v,
},
'"DWrite+" in app_notes': {
key: 'd_write_enabled',
},
'"DWrite-" in app_notes': {
key: 'd_write_enabled',
values: v => !v,
},
'adapter_vendor_id': {
values: {
'NVIDIA Corporation': '0x10de',
'Intel Corporation': '0x8086',
},
},
'CPU Info': {
key: 'cpu_info',
}
};
let key, value;
if (socorroKey in valueMapping) {
let mapping = valueMapping[socorroKey];
key = mapping['key'] || socorroKey;
if (mapping['values']) {
if (typeof mapping['values'] === 'function') {
value = mapping['values'](socorroValue);
} else {
value = mapping['values'][socorroValue];
}
}
}
if (typeof key === 'undefined') {
key = socorroKey;
}
if (typeof value === 'undefined') {
value = socorroValue;
}
return [key, value];
}
function getChannelPercentage(channel, socorroItem) {
// console.log('socorro')
// console.log(socorroItem)
let translatedItem = {};
for (let prop of Object.keys(socorroItem)) {
let [telemetryProp, telemetryValue] = socorroToTelemetry(prop, socorroItem[prop]);
//console.log(telemetryProp + ' - ' + telemetryValue)
translatedItem[telemetryProp] = telemetryValue;
}
// console.log('translated')
// console.log(translatedItem)
let found = channelsData[channel].find(longitudinalElem => itemEqual(longitudinalElem.item, translatedItem));
if (!found) {
return 0;
}
// console.log('telemetry ' + channel)
// console.log(found)
/*console.log('cpu_info');
console.log(channelsData[channel].filter(longitudinalElem => Object.keys(longitudinalElem.item).indexOf('cpu_info') != -1));*/
return found.p;
}
function rerank(textElem, signature, channel, channel_target, product) {
textElem.textContent = '';
return loadChannelsDifferencesData(product)
.then(() => loadCorrelationData(signature, channel, product))
.then(data => {
if (!(product in data)) {
textElem.textContent = 'No correlation data was generated for the \'' + product + '\' product.';
return [];
}
if (!(signature in data[product][channel]['signatures']) || !data[product][channel]['signatures'][signature]['results']) {
textElem.textContent = 'No correlation data was generated for the signature "' + signature + '" on the ' + channel + ' channel, for the \'' + product + '\' product.';
return [];
}
let correlationData = data[product][channel]['signatures'][signature]['results'];
let total_reference = data[product][channel].total;
let total_group = data[product][channel]['signatures'][signature].total;
return correlationData
.filter(socorroElem => Object.keys(socorroElem.item).length == 1)
.filter(socorroElem => getChannelPercentage(channel, socorroElem.item) != 0)
.sort((a, b) => {
//return getChannelPercentage(channel_target, b.item) / getChannelPercentage(channel, b.item) - getChannelPercentage(channel_target, a.item) / getChannelPercentage(channel, a.item);
return b.count_group / total_group - a.count_group / total_group;
})
.map(elem => {
return {
property: itemToLabel(elem.item),
in_signature: toPercentage(elem.count_group / total_group),
in_channel_target: getChannelPercentage(channel_target, elem.item),
in_channel: getChannelPercentage(channel, elem.item),
};
});
});
}
function text(textElem, signature, channel, product, show_ci=false) {
loadCorrelationData(signature, channel, product)
.then(data => {
textElem.textContent = '';
if (!(product in data)) {
textElem.textContent = 'No correlation data was generated for the \'' + product + '\' product.';
return;
}
if (!(signature in data[product][channel]['signatures']) || !data[product][channel]['signatures'][signature]['results']) {
textElem.textContent = 'No correlation data was generated for the signature "' + signature + '" on the ' + channel + ' channel, for the \'' + product + '\' product.';
return;
}
let correlationData = data[product][channel]['signatures'][signature]['results'];
let total_reference = data[product][channel].total;
let total_group = data[product][channel]['signatures'][signature].total;
textElem.textContent = sortCorrelationData(correlationData, total_reference, total_group)
.reduce((prev, cur) => {
let support_group = toPercentage(cur.count_group / total_group);
let support_reference = toPercentage(cur.count_reference / total_reference);
let support_diff = toPercentage(Math.abs(cur.count_group / total_group - cur.count_reference / total_reference));
let ci = confidenceInterval(cur.count_group, total_group, cur.count_reference, total_reference);
let support_diff_incertezza = toPercentage(Math.abs(Math.abs(ci[0]) - Math.abs(cur.count_group / total_group - cur.count_reference / total_reference)));
let res = prev + '(' + support_group + '% in signature vs ' + support_reference + '% overall, difference ' + support_diff + '±' + support_diff_incertezza + '%) ' + itemToLabel(cur.item)
if (cur.prior) {
let support_group_given_prior = toPercentage(cur.prior.count_group / cur.prior.total_group);
let support_reference_given_prior = toPercentage(cur.prior.count_reference / cur.prior.total_reference);
res += ' [' + support_group_given_prior + '% vs ' + support_reference_given_prior + '% if ' + itemToLabel(cur.prior.item) + ']'
}
return res + '\n';
}, '');
textElem.textContent += '\n\nTop Words: ' + data[product][channel]['signatures'][signature]['top_words'].join(', ');
});
}
function graph(svgElem, signature, channel, product) {
loadCorrelationData(signature, channel, product)
.then(data => {
d3.select(svgElem).selectAll('*').remove();
if (!(product in data) || !(signature in data[product][channel]['signatures']) || !data[product][channel]['signatures'][signature]['results']) {
return;
}
let total_reference = data[product][channel].total;
let total_group = data[product][channel]['signatures'][signature].total;
let correlationData = data[product][channel]['signatures'][signature]['results']
.filter(elem => Object.keys(elem.item).length <= 1);
correlationData = sortCorrelationData(correlationData, total_reference, total_group);
correlationData.reverse();
let margin = { top: 20, right: 300, bottom: 30, left: 300 };
let width = svgElem.getAttribute('width') - margin.left - margin.right;
let height = svgElem.getAttribute('height') - margin.top - margin.bottom;
let y0 = d3.scale.ordinal()
.rangeRoundBands([height, 0], .2, 0.5);
let y1 = d3.scale.ordinal();
let x = d3.scale.linear()
.range([0, width]);
let color = d3.scale.ordinal()
.range(['blue', 'red']);
let xAxis = d3.svg.axis()
.scale(x)
.tickSize(-height)
.orient('bottom');
let yAxis = d3.svg.axis()
.scale(y0)
.orient('left');
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 + ')');
let options = [signature, 'Overall'];
correlationData.forEach(d => {
d.values = [
{ name: 'Overall', value: d.count_reference / total_reference },
{ name: signature, value: d.count_group / total_group },
]
});
y0.domain(correlationData.map(d => itemToLabel(d.item)));
y1.domain(options).rangeRoundBands([0, y0.rangeBand()]);
x.domain([0, 100]);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
let bar = svg.selectAll('.bar')
.data(correlationData)
.enter().append('g')
.attr('class', 'rect')
.attr('transform', d => 'translate( 0,'+ y0(itemToLabel(d.item)) +')');
let bar_enter = bar.selectAll('rect')
.data(d => d.values)
.enter()
bar_enter.append('rect')
.attr('height', y1.rangeBand())
.attr('y', d => y1(d.name))
.attr('x', d => 0)
.attr('value', d => d.name)
.attr('width', d => x((d.value * 100).toFixed(2)))
.style('fill', d => color(d.name));
bar_enter.append('text')
.attr('x', d => x((d.value * 100).toFixed(2)) + 5)
.attr('y', d => y1(d.name) + (y1.rangeBand()/2))
.attr('dy', '.35em')
.text(d => (d.value * 100).toFixed(2));
let legend = svg.selectAll('.legend')
.data(options.slice())
.enter().append('g')
.attr('class', 'legend')
.attr('transform', (d, i) => 'translate(' + margin.right + ',' + i * 20 + ')');
legend.append('rect')
.attr('x', width - 18)
.attr('width', 18)
.attr('height', 18)
.style('fill', color);
legend.append('text')
.attr('x', width - 24)
.attr('y', 9)
.attr('dy', '.35em')
.style('text-anchor', 'end')
.text(d => d);
});
}
return {
getAnalysisDate: getAnalysisDate,
text: text,
rerank: rerank,
toPercentage: toPercentage,
graph: graph,
};
})();