Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 <!DOCTYPE html> | |
| 2 <!-- | |
| 3 Copyright 2016 The Chromium Authors. All rights reserved. | |
| 4 Use of this source code is governed by a BSD-style license that can be | |
| 5 found in the LICENSE file. | |
| 6 --> | |
| 7 | |
| 8 <link rel="import" href="/tracing/base/statistics.html"> | |
| 9 <link rel="import" href="/tracing/base/xhr.html"> | |
| 10 <link rel="import" href="/tracing/value/value_set.html"> | |
| 11 | |
| 12 <script> | |
| 13 'use strict'; | |
| 14 | |
| 15 tr.exportTo('tr.metrics', function() { | |
| 16 var BisectComparison = { | |
| 17 ENOUGH_SAMPLES: 18, | |
| 18 SIGNIFICANCE_LEVEL: 0.05, | |
| 19 escapeChars: function(s) { | |
|
eakuefner
2016/07/21 19:35:34
write this as an arrow, that is:
s => s.replace..
RobertoCN
2016/09/02 21:19:30
Done.
| |
| 20 return s.replace(/[\:|=\/#&,]/g, '_'); | |
| 21 }, | |
| 22 valuesFromCharts: function(listOfCharts, metricName) { | |
| 23 function geoMeanFromHistogram(h) { | |
| 24 if (!h.hasOwnProperty('buckets')) { | |
| 25 return 0.0; | |
| 26 } | |
| 27 var count = 0; | |
| 28 var sumOfLogs = 0; | |
| 29 for (var bucket in h.buckets) { | |
|
eakuefner
2016/07/21 19:35:34
you probably want for..of not for..in. for..in is
RobertoCN
2016/09/02 21:19:30
Done.
| |
| 30 if (bucket.hasOwnProperty('high')) { | |
| 31 bucket.mean = (bucket.low + bucket.high) / 2.0; | |
| 32 } else { | |
| 33 bucket.mean = bucket.low; | |
| 34 } | |
| 35 | |
| 36 if (bucket.mean > 0) { | |
| 37 sumOfLogs += Math.log(bucket.mean) * bucket.count; | |
| 38 count += bucket.count; | |
| 39 } | |
| 40 } | |
| 41 if (count == 0) { | |
| 42 return 0.0; | |
| 43 } | |
| 44 return Math.exp(sumOfLogs / count); | |
| 45 } | |
| 46 var all_values = []; | |
| 47 var charts; | |
| 48 for (var i = 0; i < listOfCharts.length; i++) { | |
|
eakuefner
2016/07/21 19:35:34
again a place where you could just use a for..of l
RobertoCN
2016/09/02 21:19:30
Done.
| |
| 49 charts = listOfCharts[i]; | |
| 50 var chartName, interactionName, traceName; | |
| 51 var parts = metricName.split('/'); | |
| 52 if (parts.length == 3) { | |
| 53 chartName = parts[0]; | |
| 54 interactionName = parts[1]; | |
| 55 traceName = parts[2]; | |
| 56 } else if (parts.length == 2) { | |
| 57 chartName = parts[0]; | |
| 58 if (chartName != parts[1]) { | |
| 59 traceName = parts[1]; | |
| 60 } | |
| 61 } else { | |
| 62 throw new Error('Could not parse metric name.'); | |
| 63 } | |
| 64 | |
| 65 if (interactionName) { | |
| 66 chartName = interactionName + '@@' + chartName; | |
| 67 } | |
| 68 | |
| 69 if (!traceName) { | |
| 70 traceName = 'summary'; | |
| 71 } | |
| 72 | |
| 73 | |
| 74 var chart, trace; | |
| 75 | |
| 76 for (chart in charts.charts) { | |
| 77 if (charts.charts.hasOwnProperty(chart) && | |
| 78 this.escapeChars(chart) == chartName) { | |
| 79 chartName = chart; // Unescaping | |
| 80 break; | |
| 81 } | |
| 82 } | |
| 83 for (trace in charts.charts[chartName]) { | |
| 84 if (charts.charts[chartName].hasOwnProperty(trace) && | |
| 85 this.escapeChars(trace) == traceName) { | |
| 86 traceName = trace; // Unescaping | |
| 87 break; | |
| 88 } | |
| 89 } | |
| 90 if (charts.charts[chartName][traceName].type == | |
| 91 'list_of_scalar_values') { | |
| 92 all_values.push.apply( | |
| 93 all_values, charts.charts[chartName][traceName].values); | |
| 94 } | |
| 95 | |
| 96 if (charts.charts[chartName][traceName].type == 'histogram') { | |
| 97 all_values.push( | |
| 98 geoMeanFromHistogam(charts.charts[chartName][traceName])); | |
| 99 } | |
| 100 } | |
| 101 return all_values; | |
| 102 }, | |
| 103 | |
| 104 compareValuesets: function(valueSetAPathList, valueSetBPathList, metric) { | |
| 105 var aPaths = valueSetAPathList.split(','); | |
| 106 var bPaths = valueSetBPathList.split(','); | |
| 107 var valueSetA = new tr.v.ValueSet(); | |
| 108 var valueSetB = new tr.v.ValueSet(); | |
| 109 var current; | |
| 110 for (var i = 0; i < aPaths.length; i++) { | |
| 111 try { | |
| 112 current = tr.b.getSync('file://' + aPaths[i]); | |
| 113 } catch (ex) { | |
| 114 var err = new Error('Could not open' + aPaths[i]); | |
| 115 err.name = 'File loading error'; | |
| 116 throw err; | |
| 117 } | |
| 118 valueSetA.addValuesFromDicts(JSON.parse(current)); | |
| 119 } | |
| 120 for (var i = 0; i < bPaths.length; i++) { | |
| 121 try { | |
| 122 current = tr.b.getSync('file://' + bPaths[i]); | |
| 123 } catch (ex) { | |
| 124 var err = new Error('Could not open' + bPaths[i]); | |
| 125 err.name = 'File loading error'; | |
| 126 throw err; | |
| 127 } | |
| 128 valueSetB.addValuesFromDicts(JSON.parse(current)); | |
| 129 } | |
| 130 | |
| 131 function rawValuesByMetricName(valueSet, metricName, thisParam) { | |
| 132 var interactionRecord, valueName, story; | |
| 133 var metricNameParts = metricName.split('/'); | |
| 134 if (metricNameParts[0] === metricNameParts[1]) { | |
| 135 story = 'summary'; | |
| 136 } else { | |
| 137 story = metricNameParts[1]; | |
| 138 } | |
| 139 var chartNameParts = metricNameParts[0].split('-'); | |
| 140 valueName = chartNameParts[1]; | |
| 141 if (chartNameParts.length == 2) { | |
| 142 interactionRecord = chartNameParts[0]; | |
| 143 } | |
| 144 var values = valueSet.getValuesWithName(valueName); | |
| 145 if (!values || values.length == 0) { | |
| 146 // If there was a dash in the chart name, but it wasn't an | |
| 147 // interaction record. | |
| 148 valueName = metricNameParts[0]; | |
| 149 values = valueSet.getValuesWithName(valueName); | |
| 150 interactionRecord = undefined; | |
| 151 if (!values || values.length == 0) { | |
| 152 throw new Error('No values with name ' + valueName); | |
| 153 } | |
| 154 } | |
| 155 var filtered = values.filter(function(value) { | |
| 156 if (value.name != valueName) { | |
| 157 return false; | |
| 158 } | |
| 159 var ii = tr.v.d.IterationInfo.getFromValue(value); | |
| 160 if (interactionRecord) { | |
| 161 var values = []; | |
| 162 var keys = Object.keys(ii.storyGroupingKeys); | |
| 163 keys.sort(); | |
| 164 for (var i = 0; i < keys.length; i++) { | |
| 165 values.push(ii.storyGroupingKeys[keys[i]]); | |
| 166 } | |
| 167 if (interactionRecord == values.join('_')) { | |
| 168 return thisParam.escapeChars(ii.storyDisplayName) == | |
| 169 thisParam.escapeChars(story); | |
| 170 } | |
| 171 return false; | |
| 172 } | |
| 173 return thisParam.escapeChars(ii.storyDisplayName) == | |
| 174 thisParam.escapeChars(story); | |
| 175 }); | |
| 176 var rawValues = []; | |
| 177 for (var i = 0; i < filtered.length; i++) { | |
| 178 if (filtered[i].numeric instanceof tr.v.Numeric) { | |
| 179 rawValues = rawValues.concat(filtered[i].numeric.sampleValues); | |
| 180 } else if (filtered[i].numeric instanceof tr.v.ScalarNumeric) { | |
| 181 rawValues.push(filtered[i].numeric.value); | |
| 182 } | |
| 183 } | |
| 184 return rawValues; | |
| 185 } | |
| 186 var sampleA = rawValuesByMetricName(valueSetA, metric, this); | |
| 187 var sampleB = rawValuesByMetricName(valueSetB, metric, this); | |
| 188 return this.compareSamples(sampleA, sampleB); | |
| 189 }, | |
| 190 | |
| 191 compareCharts: function(chartPathListA, chartPathListB, metric) { | |
| 192 var aPaths = chartPathListA.split(','); | |
| 193 var bPaths = chartPathListB.split(','); | |
| 194 var chartsA = new Array(aPaths.length); | |
| 195 var chartsB = new Array(bPaths.length); | |
| 196 var current; | |
| 197 for (var i = 0; i < aPaths.length; i++) { | |
| 198 try { | |
| 199 current = tr.b.getSync('file://' + aPaths[i]); | |
| 200 } catch (ex) { | |
| 201 var err = new Error('Could not open' + aPaths[i]); | |
| 202 err.name = 'File loading error'; | |
| 203 throw err; | |
| 204 } | |
| 205 chartsA[i] = JSON.parse(current); | |
| 206 } | |
| 207 for (var i = 0; i < bPaths.length; i++) { | |
| 208 try { | |
| 209 current = tr.b.getSync('file://' + bPaths[i]); | |
| 210 } catch (ex) { | |
| 211 var err = new Error('Could not open' + bPaths[i]); | |
| 212 err.name = 'File loading error'; | |
| 213 throw err; | |
| 214 } | |
| 215 chartsB[i] = JSON.parse(current); | |
| 216 } | |
| 217 var sampleA = this.valuesFromCharts(chartsA, metric); | |
| 218 var sampleB = this.valuesFromCharts(chartsB, metric); | |
| 219 return this.compareSamples(sampleA, sampleB); | |
| 220 }, | |
| 221 | |
| 222 compareSamples: function(sampleA, sampleB) { | |
| 223 var pValue = tr.b.Statistics.mwu.test(sampleA, sampleB); | |
| 224 // Diagnostics | |
| 225 var result = { | |
| 226 sample_a: { | |
| 227 std_dev: tr.b.Statistics.stddev(sampleA), | |
| 228 mean: tr.b.Statistics.mean(sampleA), | |
| 229 debug_values: sampleA | |
| 230 }, | |
| 231 sample_b: { | |
| 232 std_dev: tr.b.Statistics.stddev(sampleB), | |
| 233 mean: tr.b.Statistics.mean(sampleB), | |
| 234 debug_values: sampleB | |
| 235 }, | |
| 236 pValue: pValue.p, | |
| 237 UStatistic: pValue.U, | |
| 238 result: 'needMoreData', | |
| 239 }; | |
| 240 if (pValue.p < this.SIGNIFICANCE_LEVEL) { | |
| 241 result.result = true; // Reject the null | |
| 242 } else if (sampleA.length > this.ENOUGH_SAMPLES && | |
| 243 sampleB.length > this.ENOUGH_SAMPLES) { | |
| 244 result.result = false; // Fail to reject the null. | |
| 245 } | |
| 246 return result; | |
| 247 } | |
| 248 }; | |
| 249 | |
| 250 return { | |
| 251 BisectComparison: BisectComparison | |
| 252 }; | |
| 253 }); | |
| 254 </script> | |
| OLD | NEW |