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/iteration_helpers.html"> | |
| 9 <link rel="import" href="/tracing/base/statistics.html"> | |
| 10 <link rel="import" href="/tracing/base/xhr.html"> | |
| 11 <link rel="import" href="/tracing/value/value_set.html"> | |
| 12 | |
| 13 <script> | |
| 14 'use strict'; | |
| 15 | |
| 16 tr.exportTo('tr.metrics', function() { | |
|
nednguyen
2016/09/12 17:13:32
You also don't need this export.
RobertoCN
2016/09/19 20:07:43
Done.
nednguyen
2016/09/19 20:14:41
Did you forget to upload your new patch?
| |
| 17 var escapeChars = s => s.replace(/[\:|=\/#&,]/g, '_'); | |
| 18 | |
| 19 function findUnescapedKey(escaped, d) { | |
| 20 for (var k of tr.b.dictionaryKeys(d)) | |
| 21 if (escapeChars(k) === escaped) | |
| 22 return k; | |
| 23 throw new Error('did not find key ' + escaped + ' in ' + | |
| 24 tr.b.dictionaryKeys(d)); | |
| 25 } | |
| 26 | |
| 27 function geoMeanFromHistogram(h) { | |
| 28 if (!h.hasOwnProperty('buckets')) | |
| 29 return 0.0; | |
| 30 var count = 0; | |
| 31 var sumOfLogs = 0; | |
| 32 for (var bucket of h.buckets) { | |
| 33 if (bucket.hasOwnProperty('high')) | |
| 34 bucket.mean = (bucket.low + bucket.high) / 2.0; | |
| 35 else | |
| 36 bucket.mean = bucket.low; | |
| 37 | |
| 38 if (bucket.mean > 0) { | |
| 39 sumOfLogs += Math.log(bucket.mean) * bucket.count; | |
| 40 count += bucket.count; | |
| 41 } | |
| 42 } | |
| 43 if (count === 0) | |
| 44 return 0.0; | |
| 45 return Math.exp(sumOfLogs / count); | |
| 46 } | |
| 47 | |
| 48 function splitMetric(metricName) { | |
| 49 var parts = metricName.split('/'); | |
| 50 var interactionName; | |
| 51 var traceName = 'summary'; | |
| 52 var chartName = parts[0]; | |
| 53 if (parts.length === 3) { | |
| 54 // parts[1] is the interactionName | |
| 55 if (parts[1]) | |
| 56 chartName = parts[1] + '@@' + chartName; | |
| 57 traceName = parts[2]; | |
| 58 } else if (parts.length === 2) { | |
| 59 if (chartName !== parts[1]) | |
| 60 traceName = parts[1]; | |
| 61 } else | |
| 62 throw new Error('Could not parse metric name.'); | |
| 63 return [chartName, traceName]; | |
| 64 } | |
| 65 | |
| 66 function valuesFromCharts(listOfCharts, metricName) { | |
| 67 var all_values = []; | |
| 68 var chartAndTrace = splitMetric(metricName); | |
| 69 for (var charts of listOfCharts) { | |
| 70 var chartName = findUnescapedKey(chartAndTrace[0], charts.charts); | |
| 71 if (chartName) { | |
| 72 var traceName = findUnescapedKey( | |
| 73 chartAndTrace[1], charts.charts[chartName]); | |
| 74 if (traceName) { | |
| 75 if (charts.charts[chartName][traceName].type === | |
| 76 'list_of_scalar_values') | |
| 77 all_values.push(...charts.charts[chartName][traceName].values); | |
| 78 if (charts.charts[chartName][traceName].type === 'histogram') | |
| 79 all_values.push( | |
| 80 geoMeanFromHistogram(charts.charts[chartName][traceName])); | |
| 81 } | |
| 82 } | |
| 83 } | |
| 84 return all_values; | |
| 85 } | |
| 86 | |
| 87 function rawValuesByMetricName(valueSet, metricName) { | |
| 88 var interactionRecord, valueName, story; | |
| 89 var metricNameParts = metricName.split('/'); | |
| 90 if (metricNameParts[0] === metricNameParts[1]) | |
| 91 story = 'summary'; | |
| 92 else | |
| 93 story = metricNameParts[1]; | |
| 94 var chartNameParts = metricNameParts[0].split('-'); | |
| 95 valueName = chartNameParts[1]; | |
| 96 if (chartNameParts.length === 2) | |
| 97 interactionRecord = chartNameParts[0]; | |
| 98 var values = valueSet.getValuesWithName(valueName); | |
| 99 if (!values || values.length === 0) { | |
| 100 // If there was a dash in the chart name, but it wasn't an | |
| 101 // interaction record. | |
| 102 valueName = metricNameParts[0]; | |
| 103 values = valueSet.getValuesWithName(valueName); | |
| 104 interactionRecord = undefined; | |
| 105 if (!values || values.length === 0) | |
| 106 throw new Error('No values with name ' + valueName); | |
| 107 } | |
| 108 var filtered = []; | |
| 109 for (var value of values) { | |
| 110 if (value.name !== valueName) | |
| 111 continue; | |
| 112 var ii = tr.v.d.IterationInfo.getFromValue(value); | |
| 113 if (interactionRecord) { | |
| 114 var IRParts = []; | |
| 115 var keys = Object.keys(ii.storyGroupingKeys); | |
| 116 keys.sort(); | |
| 117 for (var key of keys) | |
| 118 IRParts.push(ii.storyGroupingKeys[key]); | |
| 119 if (interactionRecord === IRParts.join('_') && | |
| 120 escapeChars(ii.storyDisplayName) === | |
| 121 escapeChars(story)) | |
| 122 filtered.push(value); | |
| 123 } else if (escapeChars(ii.storyDisplayName) === | |
| 124 escapeChars(story)) | |
| 125 filtered.push(value); | |
| 126 } | |
| 127 | |
| 128 var rawValues = []; | |
| 129 for (var val of filtered) { | |
| 130 if (val.numeric instanceof tr.v.Numeric) | |
| 131 rawValues = rawValues.concat(val.numeric.sampleValues); | |
| 132 else if (val.numeric instanceof tr.v.ScalarNumeric) | |
| 133 rawValues.push(val.numeric.value); | |
| 134 } | |
| 135 return rawValues; | |
| 136 } | |
| 137 | |
| 138 function parseFiles(files) { | |
| 139 var results = []; | |
| 140 for (var path of files) { | |
| 141 try { | |
| 142 var current = tr.b.getSync('file://' + path); | |
| 143 } catch (ex) { | |
| 144 var err = new Error('Could not open' + path); | |
| 145 err.name = 'File loading error'; | |
| 146 throw err; | |
| 147 } | |
| 148 results.push(JSON.parse(current)); | |
| 149 } | |
| 150 return results; | |
| 151 } | |
| 152 | |
| 153 var escapeForRegExp = s => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); | |
| 154 | |
| 155 var strFromRE = re => re.toString().split('/')[1]; | |
| 156 | |
| 157 function valuesFromBuildbotOutput(out, metric) { | |
| 158 if (!out) | |
| 159 return []; | |
| 160 var stringVals = []; | |
| 161 var floatVals = []; | |
| 162 var chartAndTrace = splitMetric(metric); | |
| 163 var metricRE = escapeForRegExp( | |
| 164 'RESULT ' + chartAndTrace[0] + ': ' + chartAndTrace[1] + '='); | |
| 165 var singleResultRE = new RegExp(metricRE + | |
| 166 strFromRE(/\s*([-]?[\d\.]+)/), 'g'); | |
| 167 var multiResultsRE = new RegExp(metricRE + | |
| 168 strFromRE(/\s*\[\s*([\d\., -]+)\s*\]/), 'g'); | |
| 169 var meanStdDevRE = new RegExp(metricRE + | |
| 170 strFromRE(/\s*\{\s*([-]?\d*(?:\.\d*)?),\s*([-]?\d*(?:\.\d*)?)\}/), 'g'); | |
| 171 for (var line of out.split(/\r?\n/)) { | |
| 172 var singleResultMatch = singleResultRE.exec(line); | |
| 173 var multiResultsMatch = multiResultsRE.exec(line); | |
| 174 var meanStdDevMatch = meanStdDevRE.exec(line); | |
| 175 if (singleResultMatch && singleResultMatch.length > 1) | |
| 176 stringVals.push(singleResultMatch[1]); | |
| 177 else if (multiResultsMatch && multiResultsMatch.length > 1) { | |
| 178 var values = multiResultsMatch[1].split(','); | |
| 179 stringVals = stringVals.concat(values); | |
| 180 } else if (meanStdDevMatch && meanStdDevMatch.length > 1) | |
| 181 stringVals.push(meanStdDevMatch[1]); | |
| 182 } | |
| 183 for (var val of stringVals) { | |
| 184 var f = parseFloat(val); | |
| 185 if (!isNaN(f)) | |
| 186 floatVals.push(f); | |
| 187 } | |
| 188 return floatVals; | |
| 189 } | |
| 190 | |
| 191 function parseMultipleBuildbotStreams(files, metric) { | |
| 192 var allValues = []; | |
| 193 for (var path of files) { | |
| 194 try { | |
| 195 var contents = tr.b.getSync('file://' + path); | |
| 196 } | |
| 197 catch (ex) { | |
| 198 var err = new Error('Could not open' + path); | |
| 199 err.name = 'File loading error'; | |
| 200 throw err; | |
| 201 } | |
| 202 allValues = allValues.concat(valuesFromBuildbotOutput(contents, metric)); | |
| 203 } | |
| 204 return allValues; | |
| 205 } | |
| 206 | |
| 207 var BisectComparison = { | |
|
dtu
2016/09/15 21:31:56
The word "bisect" isn't used anywhere else. Probab
RobertoCN
2016/09/19 20:07:42
Done.
| |
| 208 ENOUGH_SAMPLES: 18, | |
| 209 SIGNIFICANCE_LEVEL: 0.05, | |
|
dtu
2016/09/15 21:31:56
Seems like bisect is currently using 0.01, so I gu
RobertoCN
2016/09/19 20:07:43
Done.
| |
| 210 | |
| 211 compareBuildbotOutputs: function( | |
|
dtu
2016/09/15 21:31:56
I like these composed functions. Very easy to read
RobertoCN
2016/09/19 20:07:43
Acknowledged.
| |
| 212 buildbotOutputAPathList, buildbotOutputBPathList, metric) { | |
| 213 var aPaths = buildbotOutputAPathList.split(','); | |
| 214 var bPaths = buildbotOutputBPathList.split(','); | |
| 215 var sampleA = parseMultipleBuildbotStreams(aPaths, metric); | |
| 216 var sampleB = parseMultipleBuildbotStreams(bPaths, metric); | |
| 217 return this.compareSamples(sampleA, sampleB); | |
| 218 }, | |
| 219 | |
| 220 compareValuesets: function(valueSetAPathList, valueSetBPathList, metric) { | |
| 221 var aPaths = valueSetAPathList.split(','); | |
| 222 var bPaths = valueSetBPathList.split(','); | |
| 223 var valueSetA = new tr.v.ValueSet(); | |
| 224 var valueSetB = new tr.v.ValueSet(); | |
| 225 var dictsA = parseFiles(aPaths); | |
| 226 var dictsB = parseFiles(bPaths); | |
| 227 for (var d of dictsA) | |
| 228 valueSetA.addValuesFromDicts(d); | |
| 229 for (var d of dictsB) | |
| 230 valueSetB.addValuesFromDicts(d); | |
| 231 | |
| 232 var sampleA = rawValuesByMetricName(valueSetA, metric); | |
| 233 var sampleB = rawValuesByMetricName(valueSetB, metric); | |
| 234 return this.compareSamples(sampleA, sampleB); | |
| 235 }, | |
| 236 | |
| 237 compareCharts: function(chartPathListA, chartPathListB, metric) { | |
| 238 var aPaths = chartPathListA.split(','); | |
| 239 var bPaths = chartPathListB.split(','); | |
| 240 var chartsA = parseFiles(aPaths); | |
| 241 var chartsB = parseFiles(bPaths); | |
| 242 var sampleA = valuesFromCharts(chartsA, metric); | |
| 243 var sampleB = valuesFromCharts(chartsB, metric); | |
| 244 return this.compareSamples(sampleA, sampleB); | |
| 245 }, | |
| 246 | |
| 247 compareSamples: function(sampleA, sampleB) { | |
| 248 var pValue = tr.b.Statistics.mwu.test(sampleA, sampleB); | |
| 249 // Diagnostics | |
| 250 var summaryStats = sample => ({ | |
| 251 std_dev: tr.b.Statistics.stddev(sample), | |
| 252 mean: tr.b.Statistics.mean(sample), | |
|
dtu
2016/09/15 21:35:41
Question for Ben and Ethan: are you going to be us
benjhayden
2016/09/21 06:33:25
No, I don't think that results2.html will be using
| |
| 253 debug_values: sample | |
| 254 }); | |
| 255 var result = { | |
| 256 sample_a: summaryStats(sampleA), | |
| 257 sample_b: summaryStats(sampleB), | |
| 258 pValue: pValue.p, | |
| 259 UStatistic: pValue.U, | |
|
dtu
2016/09/15 21:31:56
Why is the U-statistic useful?
RobertoCN
2016/09/19 20:07:42
Maybe it's not. I decided to surface it because it
| |
| 260 result: 'needMoreData', | |
| 261 }; | |
| 262 if (pValue.p < this.SIGNIFICANCE_LEVEL) | |
| 263 result.result = true; // Reject the null | |
|
dtu
2016/09/15 21:31:56
Don't mix types. Make them all string constants, I
RobertoCN
2016/09/19 20:07:43
Done.
| |
| 264 else if (sampleA.length > this.ENOUGH_SAMPLES && | |
| 265 sampleB.length > this.ENOUGH_SAMPLES) | |
| 266 result.result = false; // Fail to reject the null. | |
| 267 return result; | |
| 268 } | |
| 269 }; | |
| 270 | |
| 271 return { | |
| 272 BisectComparison: BisectComparison | |
| 273 }; | |
| 274 }); | |
| 275 </script> | |
| OLD | NEW |