2016-08-28 21:17:04 +03:00
var correlations = ( ( ) => {
2016-09-30 15:52:31 +03:00
let correlationData = { } ;
2016-08-10 15:53:02 +03:00
2016-09-18 00:47:12 +03:00
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 ( '' ) ;
}
2016-09-30 15:52:31 +03:00
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 ] ) {
2016-09-24 20:06:48 +03:00
return Promise . resolve ( ) ;
2016-08-28 20:31:52 +03:00
}
2016-08-10 15:53:02 +03:00
2016-09-30 15:52:31 +03:00
return fetch ( getDataURL ( product ) + 'all.json.gz' )
2016-09-24 20:06:48 +03:00
. then ( response => response . json ( ) )
. then ( totals => {
2017-01-11 20:40:28 +03:00
correlationData [ product ] = {
'date' : totals [ 'date' ] ,
} ;
let channels = [ 'release' , 'beta' , 'aurora' , 'nightly' ] ;
if ( product === 'Firefox' ) {
channels . push ( 'esr' ) ;
}
2016-09-24 20:06:48 +03:00
2017-01-11 20:40:28 +03:00
for ( let ch of channels ) {
correlationData [ product ] [ ch ] = {
'total' : totals [ ch ] ,
'signatures' : { } ,
2016-09-24 20:06:48 +03:00
}
2017-01-11 20:40:28 +03:00
}
2016-09-24 20:06:48 +03:00
} ) ;
}
2016-09-30 15:52:31 +03:00
function loadCorrelationData ( signature , channel , product ) {
return loadChannelsData ( product )
2016-09-18 00:47:12 +03:00
. then ( ( ) => {
2016-09-30 15:52:31 +03:00
if ( signature in correlationData [ product ] [ channel ] [ 'signatures' ] ) {
2016-09-18 00:47:12 +03:00
return ;
}
return sha1 ( signature )
2016-09-30 15:52:31 +03:00
. then ( sha1signature => fetch ( getDataURL ( product ) + channel + '/' + sha1signature + '.json.gz' ) )
2016-09-18 00:47:12 +03:00
. then ( response => response . json ( ) )
. then ( data => {
2016-09-30 15:52:31 +03:00
correlationData [ product ] [ channel ] [ 'signatures' ] [ signature ] = data ;
} ) ;
2016-09-18 00:47:12 +03:00
} )
2016-09-30 15:52:31 +03:00
. catch ( ( ) => { } )
2016-09-18 00:47:12 +03:00
. then ( ( ) => correlationData ) ;
2016-08-28 20:31:52 +03:00
}
2016-08-10 15:53:02 +03:00
2016-09-30 15:52:31 +03:00
function getAnalysisDate ( product ) {
return loadChannelsData ( product )
. then ( ( ) => correlationData [ product ] [ 'date' ] )
. catch ( ( ) => '' ) ;
2016-09-24 20:06:48 +03:00
}
2016-08-28 20:31:52 +03:00
function itemToLabel ( item ) {
return Object . getOwnPropertyNames ( item )
2016-08-28 21:17:04 +03:00
. map ( key => key + ' = ' + item [ key ] )
2016-08-28 20:31:52 +03:00
. join ( ' ∧ ' ) ;
}
2016-08-10 15:53:02 +03:00
2016-08-31 21:17:26 +03:00
function toPercentage ( num ) {
let result = ( num * 100 ) . toFixed ( 2 ) ;
if ( result == '100.00' ) {
2016-10-01 22:19:45 +03:00
return '100.0' ;
2016-08-31 21:17:26 +03:00
}
if ( result . substring ( 0 , result . indexOf ( '.' ) ) . length == 1 ) {
return '0' + result ;
}
return result ;
}
2016-10-10 18:29:23 +03:00
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 ] ;
}
2016-09-26 23:47:30 +03:00
function sortCorrelationData ( correlationData , total _reference , total _group ) {
2016-09-05 13:54:51 +03:00
return correlationData
2016-09-12 15:37:42 +03:00
. 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 ;
}
2017-01-11 20:40:28 +03:00
// 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 ) ;
}
2016-10-10 18:29:23 +03:00
return Math . min ( Math . abs ( ciB [ 0 ] ) , Math . abs ( ciB [ 1 ] ) ) - Math . min ( Math . abs ( ciA [ 0 ] ) , Math . abs ( ciA [ 1 ] ) ) ;
2016-09-12 15:38:02 +03:00
} ) ;
2016-09-05 13:54:51 +03:00
}
2017-01-11 20:40:28 +03:00
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' ,
}
} ,
2017-01-23 14:39:44 +03:00
'e10s_enabled' : {
key : 'e10s_enabled' ,
values : {
'1' : true ,
}
} ,
2017-01-11 20:40:28 +03:00
'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' ,
2017-01-23 14:38:39 +03:00
values : v => ! v ,
2017-01-11 20:40:28 +03:00
} ,
'"DWrite+" in app_notes' : {
key : 'd_write_enabled' ,
} ,
'"DWrite-" in app_notes' : {
key : 'd_write_enabled' ,
2017-01-23 14:38:39 +03:00
values : v => ! v ,
2017-01-11 20:40:28 +03:00
} ,
'adapter_vendor_id' : {
values : {
'NVIDIA Corporation' : '0x10de' ,
'Intel Corporation' : '0x8086' ,
} ,
} ,
'CPU Info' : {
key : 'cpu_info' ,
}
} ;
2017-01-23 14:38:39 +03:00
let key , value ;
2017-01-11 20:40:28 +03:00
if ( socorroKey in valueMapping ) {
let mapping = valueMapping [ socorroKey ] ;
2017-01-23 14:38:39 +03:00
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 ;
2017-01-11 20:40:28 +03:00
}
2017-01-23 14:38:39 +03:00
return [ key , value ] ;
2017-01-11 20:40:28 +03:00
}
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)
/ * c o n s o l e . l o g ( ' c p u _ i n f o ' ) ;
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 ) {
2016-09-30 15:52:31 +03:00
loadCorrelationData ( signature , channel , product )
2016-08-28 21:17:04 +03:00
. then ( data => {
2016-08-29 03:19:21 +03:00
textElem . textContent = '' ;
2016-09-30 15:52:31 +03:00
if ( ! ( product in data ) ) {
2017-01-11 20:40:28 +03:00
textElem . textContent = 'No correlation data was generated for the \'' + product + '\' product.' ;
2016-09-30 15:52:31 +03:00
return ;
}
if ( ! ( signature in data [ product ] [ channel ] [ 'signatures' ] ) || ! data [ product ] [ channel ] [ 'signatures' ] [ signature ] [ 'results' ] ) {
2017-01-11 20:40:28 +03:00
textElem . textContent = 'No correlation data was generated for the signature "' + signature + '" on the ' + channel + ' channel, for the \'' + product + '\' product.' ;
2016-08-29 03:24:46 +03:00
return ;
}
2016-08-10 15:53:02 +03:00
2016-09-30 15:52:31 +03:00
let correlationData = data [ product ] [ channel ] [ 'signatures' ] [ signature ] [ 'results' ] ;
2016-09-18 00:47:12 +03:00
2016-09-30 15:52:31 +03:00
let total _reference = data [ product ] [ channel ] . total ;
let total _group = data [ product ] [ channel ] [ 'signatures' ] [ signature ] . total ;
2016-09-12 15:36:56 +03:00
2016-09-26 23:47:30 +03:00
textElem . textContent = sortCorrelationData ( correlationData , total _reference , total _group )
2017-01-11 20:40:28 +03:00
. 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 ( ', ' ) ;
2016-08-28 20:31:52 +03:00
} ) ;
2016-08-10 17:53:24 +03:00
}
2016-09-30 15:52:31 +03:00
function graph ( svgElem , signature , channel , product ) {
loadCorrelationData ( signature , channel , product )
2016-08-28 21:17:04 +03:00
. then ( data => {
2016-08-28 20:31:52 +03:00
d3 . select ( svgElem ) . selectAll ( '*' ) . remove ( ) ;
2016-09-30 15:52:31 +03:00
if ( ! ( product in data ) || ! ( signature in data [ product ] [ channel ] [ 'signatures' ] ) || ! data [ product ] [ channel ] [ 'signatures' ] [ signature ] [ 'results' ] ) {
2016-09-18 00:47:12 +03:00
return ;
}
2016-09-30 15:52:31 +03:00
let total _reference = data [ product ] [ channel ] . total ;
let total _group = data [ product ] [ channel ] [ 'signatures' ] [ signature ] . total ;
2016-09-12 15:50:22 +03:00
2016-09-30 15:52:31 +03:00
let correlationData = data [ product ] [ channel ] [ 'signatures' ] [ signature ] [ 'results' ]
2016-09-19 17:19:08 +03:00
. filter ( elem => Object . keys ( elem . item ) . length <= 1 ) ;
2016-09-26 23:47:30 +03:00
correlationData = sortCorrelationData ( correlationData , total _reference , total _group ) ;
2016-09-05 13:54:51 +03:00
correlationData . reverse ( ) ;
2016-08-28 20:31:52 +03:00
let margin = { top : 20 , right : 300 , bottom : 30 , left : 300 } ;
2016-09-12 15:55:46 +03:00
let width = svgElem . getAttribute ( 'width' ) - margin . left - margin . right ;
let height = svgElem . getAttribute ( 'height' ) - margin . top - margin . bottom ;
2016-08-28 20:31:52 +03:00
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 + ')' ) ;
2016-08-31 21:10:11 +03:00
let options = [ signature , 'Overall' ] ;
2016-08-28 20:31:52 +03:00
2016-08-28 21:17:04 +03:00
correlationData . forEach ( d => {
2016-08-28 20:31:52 +03:00
d . values = [
2016-09-26 23:47:30 +03:00
{ name : 'Overall' , value : d . count _reference / total _reference } ,
{ name : signature , value : d . count _group / total _group } ,
2016-08-28 20:31:52 +03:00
]
} ) ;
2016-08-28 21:17:04 +03:00
y0 . domain ( correlationData . map ( d => itemToLabel ( d . item ) ) ) ;
2016-08-28 20:31:52 +03:00
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' )
2016-08-28 21:17:04 +03:00
. attr ( 'transform' , d => 'translate( 0,' + y0 ( itemToLabel ( d . item ) ) + ')' ) ;
2016-08-28 20:31:52 +03:00
let bar _enter = bar . selectAll ( 'rect' )
2016-08-28 21:17:04 +03:00
. data ( d => d . values )
2016-08-28 20:31:52 +03:00
. enter ( )
bar _enter . append ( 'rect' )
. attr ( 'height' , y1 . rangeBand ( ) )
2016-08-28 21:17:04 +03:00
. 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 ) ) ;
2016-08-28 20:31:52 +03:00
bar _enter . append ( 'text' )
2016-08-28 21:17:04 +03:00
. attr ( 'x' , d => x ( ( d . value * 100 ) . toFixed ( 2 ) ) + 5 )
. attr ( 'y' , d => y1 ( d . name ) + ( y1 . rangeBand ( ) / 2 ) )
2016-08-28 20:31:52 +03:00
. attr ( 'dy' , '.35em' )
2016-08-28 21:17:04 +03:00
. text ( d => ( d . value * 100 ) . toFixed ( 2 ) ) ;
2016-08-28 20:31:52 +03:00
let legend = svg . selectAll ( '.legend' )
. data ( options . slice ( ) )
. enter ( ) . append ( 'g' )
. attr ( 'class' , 'legend' )
2016-08-28 21:17:04 +03:00
. attr ( 'transform' , ( d , i ) => 'translate(' + margin . right + ',' + i * 20 + ')' ) ;
2016-08-28 20:31:52 +03:00
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' )
2016-08-28 21:17:04 +03:00
. text ( d => d ) ;
2016-08-28 20:31:52 +03:00
} ) ;
}
2016-08-10 17:53:24 +03:00
2016-08-28 20:31:52 +03:00
return {
2016-09-24 20:06:48 +03:00
getAnalysisDate : getAnalysisDate ,
2016-08-28 20:31:52 +03:00
text : text ,
2017-01-11 20:40:28 +03:00
rerank : rerank ,
toPercentage : toPercentage ,
2016-08-28 20:31:52 +03:00
graph : graph ,
} ;
} ) ( ) ;