| OLD | NEW |
| 1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
| 2 <!-- | 2 <!-- |
| 3 Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 4 Use of this source code is governed by a BSD-style license that can be |
| 5 found in the LICENSE file. | 5 found in the LICENSE file. |
| 6 --> | 6 --> |
| 7 | 7 |
| 8 <link rel="import" href="/tracing/base/iteration_helpers.html"> | 8 <link rel="import" href="/tracing/base/iteration_helpers.html"> |
| 9 <link rel="import" href="/tracing/base/range.html"> | 9 <link rel="import" href="/tracing/base/range.html"> |
| 10 <link rel="import" href="/tracing/base/running_statistics.html"> | 10 <link rel="import" href="/tracing/base/running_statistics.html"> |
| 11 <link rel="import" href="/tracing/base/sorted_array_utils.html"> | 11 <link rel="import" href="/tracing/base/sorted_array_utils.html"> |
| 12 <link rel="import" href="/tracing/base/statistics.html"> | 12 <link rel="import" href="/tracing/base/statistics.html"> |
| 13 <link rel="import" href="/tracing/value/unit.html"> | 13 <link rel="import" href="/tracing/value/unit.html"> |
| 14 | 14 |
| 15 <script> | 15 <script> |
| 16 'use strict'; | 16 'use strict'; |
| 17 | 17 |
| 18 tr.exportTo('tr.v', function() { | 18 tr.exportTo('tr.v', function() { |
| 19 var Range = tr.b.Range; | 19 var Range = tr.b.Range; |
| 20 | 20 |
| 21 var MAX_SOURCE_INFOS = 16; | 21 var MAX_DIAGNOSTICS = 16; |
| 22 |
| 23 // p-values less than this indicate statistical significance. |
| 24 var DEFAULT_ALPHA = 0.05; |
| 25 |
| 26 /** @enum */ |
| 27 var Significance = { |
| 28 DONT_CARE: -1, |
| 29 INSIGNIFICANT: 0, |
| 30 SIGNIFICANT: 1 |
| 31 }; |
| 22 | 32 |
| 23 function NumericBase(unit) { | 33 function NumericBase(unit) { |
| 24 if (!(unit instanceof tr.v.Unit)) | 34 if (!(unit instanceof tr.v.Unit)) |
| 25 throw new Error('Expected provided unit to be instance of Unit'); | 35 throw new Error('Expected provided unit to be instance of Unit'); |
| 26 | 36 |
| 27 this.unit = unit; | 37 this.unit = unit; |
| 28 } | 38 } |
| 29 | 39 |
| 30 NumericBase.prototype = { | 40 NumericBase.prototype = { |
| 41 merge: function(other) { |
| 42 if (this.unit !== other.unit) |
| 43 throw new Error('Merging Numerics with different units'); |
| 44 |
| 45 // Two Numerics that were built using the same NumericBuilder |
| 46 // can be merged using addNumeric(). |
| 47 if (this instanceof Numeric && other instanceof Numeric && |
| 48 this.canAddNumeric(other)) { |
| 49 var result = this.clone(); |
| 50 result.addNumeric(other.clone()); |
| 51 return result; |
| 52 } |
| 53 |
| 54 // Either a Scalar and a Numeric, or two Scalars... |
| 55 // or two Numerics that were not built using the same NumericBuilder, |
| 56 // should be built from their raw samples. |
| 57 var samples = []; |
| 58 this.sampleValuesInto(samples); |
| 59 other.sampleValuesInto(samples); |
| 60 return Numeric.buildFromSamples(this.unit, samples); |
| 61 }, |
| 62 |
| 63 sampleValuesInto: function(samples) { |
| 64 throw new Error('Not implemented'); |
| 65 }, |
| 66 |
| 31 asDict: function() { | 67 asDict: function() { |
| 32 var d = { | 68 var d = { |
| 33 unit: this.unit.asJSON() | 69 unit: this.unit.asJSON() |
| 34 }; | 70 }; |
| 35 | 71 |
| 36 this.asDictInto_(d); | 72 this.asDictInto_(d); |
| 37 return d; | 73 return d; |
| 38 } | 74 } |
| 39 }; | 75 }; |
| 40 | 76 |
| 41 NumericBase.fromDict = function(d) { | 77 NumericBase.fromDict = function(d) { |
| 42 if (d.type === 'scalar') | 78 if (d.type === 'scalar') |
| 43 return ScalarNumeric.fromDict(d); | 79 return ScalarNumeric.fromDict(d); |
| 44 | 80 |
| 45 if (d.type === 'numeric') | 81 if (d.type === 'numeric') |
| 46 return Numeric.fromDict(d); | 82 return Numeric.fromDict(d); |
| 47 | 83 |
| 48 throw new Error('Not implemented'); | 84 throw new Error('Not implemented'); |
| 49 }; | 85 }; |
| 50 | 86 |
| 51 function NumericBin(parentNumeric, opt_range) { | 87 function NumericBin(parentNumeric, opt_range) { |
| 52 this.parentNumeric = parentNumeric; | 88 this.parentNumeric = parentNumeric; |
| 53 this.range = opt_range || (new tr.b.Range()); | 89 this.range = opt_range || (new tr.b.Range()); |
| 54 this.count = 0; | 90 this.count = 0; |
| 55 this.sourceInfos = []; | 91 this.diagnostics = []; |
| 56 } | 92 } |
| 57 | 93 |
| 58 NumericBin.fromDict = function(parentNumeric, d) { | 94 NumericBin.fromDict = function(parentNumeric, d) { |
| 59 var n = new NumericBin(parentNumeric); | 95 var n = new NumericBin(parentNumeric); |
| 60 n.range.min = d.min; | 96 n.range.min = d.min; |
| 61 n.range.max = d.max; | 97 n.range.max = d.max; |
| 62 n.count = d.count; | 98 n.count = d.count; |
| 63 n.sourceInfos = d.sourceInfos; | 99 if (d.diagnostics) |
| 100 n.diagnostics = d.diagnostics.map(dd => tr.v.d.Diagnostic.fromDict(dd)); |
| 64 return n; | 101 return n; |
| 65 }; | 102 }; |
| 66 | 103 |
| 67 NumericBin.prototype = { | 104 NumericBin.prototype = { |
| 68 add: function(value, sourceInfo) { | 105 /** |
| 106 * @param {*} value |
| 107 * @param {!tr.v.d.Diagnostic=} opt_diagnostic |
| 108 */ |
| 109 add: function(value, opt_diagnostic) { |
| 69 this.count += 1; | 110 this.count += 1; |
| 70 tr.b.Statistics.uniformlySampleStream(this.sourceInfos, this.count, | 111 if (opt_diagnostic) { |
| 71 sourceInfo, MAX_SOURCE_INFOS); | 112 tr.b.Statistics.uniformlySampleStream( |
| 113 this.diagnostics, this.count, opt_diagnostic, MAX_DIAGNOSTICS); |
| 114 } |
| 72 }, | 115 }, |
| 73 | 116 |
| 74 addBin: function(other) { | 117 addBin: function(other) { |
| 75 if (!this.range.equals(other.range)) | 118 if (!this.range.equals(other.range)) |
| 76 throw new Error('Merging incompatible Numeric bins.'); | 119 throw new Error('Merging incompatible Numeric bins.'); |
| 77 tr.b.Statistics.mergeSampledStreams(this.sourceInfos, this.count, | 120 tr.b.Statistics.mergeSampledStreams(this.diagnostics, this.count, |
| 78 other.sourceInfos, other.count, MAX_SOURCE_INFOS); | 121 other.diagnostics, other.count, MAX_DIAGNOSTICS); |
| 79 this.count += other.count; | 122 this.count += other.count; |
| 80 }, | 123 }, |
| 81 | 124 |
| 82 asDict: function() { | 125 asDict: function() { |
| 83 return { | 126 return { |
| 84 min: this.range.min, | 127 min: this.range.min, |
| 85 max: this.range.max, | 128 max: this.range.max, |
| 86 count: this.count, | 129 count: this.count, |
| 87 sourceInfos: this.sourceInfos.slice(0) | 130 diagnostics: this.diagnostics.map(d => d.asDict()) |
| 88 }; | 131 }; |
| 89 }, | 132 }, |
| 90 | 133 |
| 91 asJSON: function() { | 134 asJSON: function() { |
| 92 return this.asDict(); | 135 return this.asDict(); |
| 93 } | 136 } |
| 94 }; | 137 }; |
| 95 | 138 |
| 96 function Numeric(unit, range, binInfo) { | 139 function Numeric(unit, range, binInfo) { |
| 97 NumericBase.call(this, unit); | 140 NumericBase.call(this, unit); |
| 98 | 141 |
| 99 this.range = range; | 142 this.range = range; |
| 100 | 143 |
| 101 this.numNans = 0; | 144 this.numNans = 0; |
| 102 this.nanSourceInfos = []; | 145 this.nanDiagnostics = []; |
| 103 | 146 |
| 104 this.running = new tr.b.RunningStatistics(); | 147 this.running = new tr.b.RunningStatistics(); |
| 105 this.maxCount_ = 0; | 148 this.maxCount_ = 0; |
| 106 | 149 |
| 107 this.underflowBin = binInfo.underflowBin; | 150 this.underflowBin = binInfo.underflowBin; |
| 108 this.centralBins = binInfo.centralBins; | 151 this.centralBins = binInfo.centralBins; |
| 109 this.overflowBin = binInfo.overflowBin; | 152 this.overflowBin = binInfo.overflowBin; |
| 110 | 153 |
| 111 this.allBins = []; | 154 this.allBins = []; |
| 112 this.allBins.push(this.underflowBin); | 155 this.allBins.push(this.underflowBin); |
| 113 this.allBins.push.apply(this.allBins, this.centralBins); | 156 this.allBins.push.apply(this.allBins, this.centralBins); |
| 114 this.allBins.push(this.overflowBin); | 157 this.allBins.push(this.overflowBin); |
| 115 | 158 |
| 116 this.allBins.forEach(function(bin) { | 159 this.allBins.forEach(function(bin) { |
| 117 if (bin.count > this.maxCount_) | 160 if (bin.count > this.maxCount_) |
| 118 this.maxCount_ = bin.count; | 161 this.maxCount_ = bin.count; |
| 119 }, this); | 162 }, this); |
| 120 | 163 |
| 164 this.sampleValues_ = []; |
| 165 this.maxNumSampleValues = this.allBins.length * 10; |
| 166 |
| 121 this.summaryOptions = this.defaultSummaryOptions(); | 167 this.summaryOptions = this.defaultSummaryOptions(); |
| 122 } | 168 } |
| 123 | 169 |
| 124 Numeric.fromDict = function(d) { | 170 Numeric.fromDict = function(d) { |
| 125 var range = Range.fromExplicitRange(d.min, d.max); | 171 var range = Range.fromExplicitRange(d.min, d.max); |
| 126 var binInfo = {}; | 172 var binInfo = {}; |
| 127 binInfo.underflowBin = NumericBin.fromDict(undefined, d.underflowBin); | 173 binInfo.underflowBin = NumericBin.fromDict(undefined, d.underflowBin); |
| 128 binInfo.centralBins = d.centralBins.map(function(binAsDict) { | 174 binInfo.centralBins = d.centralBins.map(function(binAsDict) { |
| 129 return NumericBin.fromDict(undefined, binAsDict); | 175 return NumericBin.fromDict(undefined, binAsDict); |
| 130 }); | 176 }); |
| 131 binInfo.overflowBin = NumericBin.fromDict(undefined, d.overflowBin); | 177 binInfo.overflowBin = NumericBin.fromDict(undefined, d.overflowBin); |
| 132 var n = new Numeric(tr.v.Unit.fromJSON(d.unit), range, binInfo); | 178 var n = new Numeric(tr.v.Unit.fromJSON(d.unit), range, binInfo); |
| 133 n.allBins.forEach(function(bin) { | 179 n.allBins.forEach(function(bin) { |
| 134 bin.parentNumeric = n; | 180 bin.parentNumeric = n; |
| 135 }); | 181 }); |
| 136 if (d.running) | 182 if (d.running) |
| 137 n.running = tr.b.RunningStatistics.fromDict(d.running); | 183 n.running = tr.b.RunningStatistics.fromDict(d.running); |
| 138 if (d.summaryOptions) | 184 if (d.summaryOptions) |
| 139 n.customizeSummaryOptions(d.summaryOptions); | 185 n.customizeSummaryOptions(d.summaryOptions); |
| 140 n.numNans = d.numNans; | 186 n.numNans = d.numNans; |
| 141 n.nanSourceInfos = d.nanSourceInfos; | 187 if (d.nanDiagnostics) { |
| 188 n.nanDiagnostics = d.nanDiagnostics.map( |
| 189 dd => tr.v.d.Diagnostic.fromDict(dd)); |
| 190 } |
| 191 n.maxNumSampleValues = d.maxNumSampleValues; |
| 192 n.sampleValues_ = d.sampleValues; |
| 142 return n; | 193 return n; |
| 143 }; | 194 }; |
| 144 | 195 |
| 196 /** |
| 197 * @param {!tr.v.Unit} unit |
| 198 * @param {!Array.<number>} samples |
| 199 * @return {!Numeric} |
| 200 */ |
| 201 Numeric.buildFromSamples = function(unit, samples) { |
| 202 var range = new tr.b.Range(); |
| 203 // Prevent non-numeric samples from introducing NaNs into the range. |
| 204 for (var sample of samples) |
| 205 if (!isNaN(Math.max(sample))) |
| 206 range.addValue(sample); |
| 207 |
| 208 // NumericBuilder.addLinearBins() requires this. |
| 209 if (range.isEmpty) |
| 210 range.addValue(1); |
| 211 if (range.min === range.max) |
| 212 range.addValue(range.min - 1); |
| 213 |
| 214 // This optimizes the resolution when samples are uniformly distributed |
| 215 // (which is almost never the case). |
| 216 var numBins = Math.ceil(Math.sqrt(samples.length)); |
| 217 var builder = new NumericBuilder(unit, range.min); |
| 218 builder.addLinearBins(range.max, numBins); |
| 219 |
| 220 var result = builder.build(); |
| 221 result.maxNumSampleValues = 1000; |
| 222 |
| 223 // TODO(eakuefner): Propagate diagnostics? |
| 224 for (var sample of samples) |
| 225 result.add(sample); |
| 226 |
| 227 return result; |
| 228 }; |
| 229 |
| 145 Numeric.prototype = { | 230 Numeric.prototype = { |
| 146 __proto__: NumericBase.prototype, | 231 __proto__: NumericBase.prototype, |
| 147 | 232 |
| 148 get numValues() { | 233 get numValues() { |
| 149 return tr.b.Statistics.sum(this.allBins, function(e) { | 234 return tr.b.Statistics.sum(this.allBins, function(e) { |
| 150 return e.count; | 235 return e.count; |
| 151 }); | 236 }); |
| 152 }, | 237 }, |
| 153 | 238 |
| 154 get average() { | 239 get average() { |
| 155 return this.running.mean; | 240 return this.running.mean; |
| 156 }, | 241 }, |
| 157 | 242 |
| 158 get sum() { | 243 get sum() { |
| 159 return this.running.sum; | 244 return this.running.sum; |
| 160 }, | 245 }, |
| 161 | 246 |
| 162 get maxCount() { | 247 get maxCount() { |
| 163 return this.maxCount_; | 248 return this.maxCount_; |
| 164 }, | 249 }, |
| 165 | 250 |
| 251 /** |
| 252 * Requires that units agree. |
| 253 * Returns DONT_CARE if that is the units' improvementDirection. |
| 254 * Returns SIGNIFICANT if the Mann-Whitney U test returns a |
| 255 * p-value less than opt_alpha or DEFAULT_ALPHA. Returns INSIGNIFICANT if |
| 256 * the p-value is greater than alpha. |
| 257 * |
| 258 * @param {!tr.v.Numeric} other |
| 259 * @param {number=} opt_alpha |
| 260 * @return {!tr.v.Significance} |
| 261 */ |
| 262 getDifferenceSignificance: function(other, opt_alpha) { |
| 263 if (this.unit !== other.unit) |
| 264 throw new Error('Cannot compare Numerics with different units'); |
| 265 |
| 266 if (this.unit.improvementDirection === |
| 267 tr.v.ImprovementDirection.DONT_CARE) { |
| 268 return tr.v.Significance.DONT_CARE; |
| 269 } |
| 270 |
| 271 if (!(other instanceof Numeric)) |
| 272 throw new Error('Unable to compute a p-value'); |
| 273 |
| 274 var mwu = tr.b.Statistics.mwu.test(this.sampleValues, other.sampleValues); |
| 275 if (mwu.p < (opt_alpha || DEFAULT_ALPHA)) |
| 276 return tr.v.Significance.SIGNIFICANT; |
| 277 return tr.v.Significance.INSIGNIFICANT; |
| 278 }, |
| 279 |
| 166 /* | 280 /* |
| 167 * Compute an approximation of percentile based on the counts in the bins. | 281 * Compute an approximation of percentile based on the counts in the bins. |
| 168 * If the real percentile lies within |this.range| then the result of | 282 * If the real percentile lies within |this.range| then the result of |
| 169 * the function will deviate from the real percentile by at most | 283 * the function will deviate from the real percentile by at most |
| 170 * the maximum width of the bin(s) within which the point(s) | 284 * the maximum width of the bin(s) within which the point(s) |
| 171 * from which the real percentile would be calculated lie. | 285 * from which the real percentile would be calculated lie. |
| 172 * If the real percentile is outside |this.range| then the function | 286 * If the real percentile is outside |this.range| then the function |
| 173 * returns the closest range limit: |this.range.min| or |this.range.max|. | 287 * returns the closest range limit: |this.range.min| or |this.range.max|. |
| 174 * | 288 * |
| 175 * @param {number} percent The percent must be between 0.0 and 1.0. | 289 * @param {number} percent The percent must be between 0.0 and 1.0. |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 248 return tr.b.lerp(value, lesserBin.count, greaterBin.count); | 362 return tr.b.lerp(value, lesserBin.count, greaterBin.count); |
| 249 }, | 363 }, |
| 250 | 364 |
| 251 getBinForValue: function(value) { | 365 getBinForValue: function(value) { |
| 252 // Don't use subtraction to avoid arithmetic overflow. | 366 // Don't use subtraction to avoid arithmetic overflow. |
| 253 var binIndex = tr.b.findHighIndexInSortedArray( | 367 var binIndex = tr.b.findHighIndexInSortedArray( |
| 254 this.allBins, b => value < b.range.max ? -1 : 1); | 368 this.allBins, b => value < b.range.max ? -1 : 1); |
| 255 return this.allBins[binIndex] || this.overflowBin; | 369 return this.allBins[binIndex] || this.overflowBin; |
| 256 }, | 370 }, |
| 257 | 371 |
| 258 add: function(value, sourceInfo) { | 372 /** |
| 373 * @param {*} value |
| 374 * @param {!tr.v.d.Diagnostic=} opt_diagnostic |
| 375 */ |
| 376 add: function(value, opt_diagnostic) { |
| 259 if (typeof(value) !== 'number' || isNaN(value)) { | 377 if (typeof(value) !== 'number' || isNaN(value)) { |
| 260 this.numNans++; | 378 this.numNans++; |
| 261 tr.b.Statistics.uniformlySampleStream(this.nanSourceInfos, this.numNans, | 379 if (opt_diagnostic) { |
| 262 sourceInfo, MAX_SOURCE_INFOS); | 380 tr.b.Statistics.uniformlySampleStream(this.nanDiagnostics, |
| 263 return; | 381 this.numNans, opt_diagnostic, MAX_DIAGNOSTICS); |
| 382 } |
| 383 } else { |
| 384 var bin = this.getBinForValue(value); |
| 385 bin.add(value, opt_diagnostic); |
| 386 this.running.add(value); |
| 387 if (bin.count > this.maxCount_) |
| 388 this.maxCount_ = bin.count; |
| 264 } | 389 } |
| 265 | 390 |
| 266 var bin = this.getBinForValue(value); | 391 tr.b.Statistics.uniformlySampleStream(this.sampleValues_, |
| 267 bin.add(value, sourceInfo); | 392 this.numValues + this.numNans, value, this.maxNumSampleValues); |
| 268 this.running.add(value); | |
| 269 if (bin.count > this.maxCount_) | |
| 270 this.maxCount_ = bin.count; | |
| 271 }, | 393 }, |
| 272 | 394 |
| 395 sampleValuesInto: function(samples) { |
| 396 for (var sampleValue of this.sampleValues) |
| 397 samples.push(sampleValue); |
| 398 }, |
| 399 |
| 400 /** |
| 401 * Return true if this Numeric can be added to |other|. |
| 402 * |
| 403 * @param {!tr.v.Numeric} other |
| 404 * @return {boolean} |
| 405 */ |
| 406 canAddNumeric: function(other) { |
| 407 if (!this.range.equals(other.range)) |
| 408 return false; |
| 409 if (this.unit !== other.unit) |
| 410 return false; |
| 411 if (this.allBins.length !== other.allBins.length) |
| 412 return false; |
| 413 |
| 414 for (var i = 0; i < this.allBins.length; ++i) |
| 415 if (!this.allBins[i].range.equals(other.allBins[i].range)) |
| 416 return false; |
| 417 |
| 418 return true; |
| 419 }, |
| 420 |
| 421 /** |
| 422 * Add |other| to this Numeric in-place if they can be added. |
| 423 * |
| 424 * @param {!tr.v.Numeric} other |
| 425 */ |
| 273 addNumeric: function(other) { | 426 addNumeric: function(other) { |
| 274 if (!this.range.equals(other.range) || | 427 if (!this.canAddNumeric(other)) |
| 275 !this.unit === other.unit || | |
| 276 this.allBins.length !== other.allBins.length) { | |
| 277 throw new Error('Merging incompatible Numerics.'); | 428 throw new Error('Merging incompatible Numerics.'); |
| 278 } | 429 |
| 279 tr.b.Statistics.mergeSampledStreams(this.nanSourceInfos, this.numNans, | 430 tr.b.Statistics.mergeSampledStreams(this.nanDiagnostics, this.numNans, |
| 280 other.nanSourceInfos, other.numNans, MAX_SOURCE_INFOS); | 431 other.nanDiagnostics, other.numNans, MAX_DIAGNOSTICS); |
| 432 tr.b.Statistics.mergeSampledStreams( |
| 433 this.sampleValues, this.numValues, |
| 434 other.sampleValues, other.numValues, tr.b.Statistics.mean( |
| 435 [this.maxNumSampleValues, other.maxNumSampleValues])); |
| 281 this.numNans += other.numNans; | 436 this.numNans += other.numNans; |
| 282 this.running = this.running.merge(other.running); | 437 this.running = this.running.merge(other.running); |
| 283 for (var i = 0; i < this.allBins.length; ++i) { | 438 for (var i = 0; i < this.allBins.length; ++i) { |
| 284 this.allBins[i].addBin(other.allBins[i]); | 439 this.allBins[i].addBin(other.allBins[i]); |
| 285 } | 440 } |
| 286 }, | 441 }, |
| 287 | 442 |
| 288 /** | 443 /** |
| 289 * Controls which statistics are exported to dashboard for this numeric. | 444 * Controls which statistics are exported to dashboard for this numeric. |
| 290 * The |summaryOptions| parameter is a dictionary with optional boolean | 445 * The |summaryOptions| parameter is a dictionary with optional boolean |
| 291 * fields |count|, |sum|, |avg|, |std|, |min|, |max| and an optional | 446 * fields |count|, |sum|, |avg|, |std|, |min|, |max| and an optional |
| 292 * array field |percentile|. | 447 * array field |percentile|. |
| 293 * Each percentile should be a number between 0.0 and 1.0. | 448 * Each percentile should be a number between 0.0 and 1.0. |
| 294 * The options not included in the |summaryOptions| will not change. | 449 * The options not included in the |summaryOptions| will not change. |
| 295 */ | 450 */ |
| 296 customizeSummaryOptions: function(summaryOptions) { | 451 customizeSummaryOptions: function(summaryOptions) { |
| 297 tr.b.iterItems(summaryOptions, function(key, value) { | 452 tr.b.iterItems(summaryOptions, function(key, value) { |
| 298 this.summaryOptions[key] = value; | 453 this.summaryOptions[key] = value; |
| 299 }, this); | 454 }, this); |
| 300 }, | 455 }, |
| 301 | 456 |
| 302 defaultSummaryOptions: function() { | 457 defaultSummaryOptions: function() { |
| 303 return { | 458 return { |
| 304 count: true, | 459 count: true, |
| 305 sum: true, | 460 sum: true, |
| 306 avg: true, | 461 avg: true, |
| 307 std: true, | 462 std: true, |
| 308 min: true, | 463 min: true, |
| 309 max: true, | 464 max: true, |
| 465 nans: false, |
| 310 percentile: [] | 466 percentile: [] |
| 311 }; | 467 }; |
| 312 }, | 468 }, |
| 313 | 469 |
| 314 /** | 470 /** |
| 315 * Returns an array of {name: string, scalar: ScalarNumeric} values. | 471 * Returns an array of {name: string, scalar: ScalarNumeric} values. |
| 316 * Each enabled summary option produces the corresponding value: | 472 * Each enabled summary option produces the corresponding value: |
| 317 * min, max, count, sum, avg, or std. | 473 * min, max, count, sum, avg, or std. |
| 318 * Each percentile 0.x produces pct_0x0. | 474 * Each percentile 0.x produces pct_0x0. |
| 319 * Each percentile 0.xx produces pct_0xx. | 475 * Each percentile 0.xx produces pct_0xx. |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 358 if (!option) | 514 if (!option) |
| 359 return; | 515 return; |
| 360 if (stat === 'percentile') { | 516 if (stat === 'percentile') { |
| 361 option.forEach(function(percent) { | 517 option.forEach(function(percent) { |
| 362 var percentile = this.getApproximatePercentile(percent); | 518 var percentile = this.getApproximatePercentile(percent); |
| 363 results.push({ | 519 results.push({ |
| 364 name: 'pct_' + percentToString(percent), | 520 name: 'pct_' + percentToString(percent), |
| 365 scalar: new tr.v.ScalarNumeric(this.unit, percentile) | 521 scalar: new tr.v.ScalarNumeric(this.unit, percentile) |
| 366 }); | 522 }); |
| 367 }, this); | 523 }, this); |
| 524 } else if (stat === 'nans') { |
| 525 results.push({ |
| 526 name: 'nans', |
| 527 scalar: new tr.v.ScalarNumeric( |
| 528 tr.v.Unit.byName.count_smallerIsBetter, this.numNans) |
| 529 }); |
| 368 } else { | 530 } else { |
| 369 var statUnit = stat === 'count' ? | 531 var statUnit = stat === 'count' ? |
| 370 tr.v.Unit.byName.unitlessNumber_smallerIsBetter : this.unit; | 532 tr.v.Unit.byName.count_smallerIsBetter : this.unit; |
| 371 var key = statNameToKey(stat); | 533 var key = statNameToKey(stat); |
| 372 var statValue = this.running[key]; | 534 var statValue = this.running[key]; |
| 373 if (typeof(statValue) === 'number') { | 535 if (typeof(statValue) === 'number') { |
| 374 results.push({ | 536 results.push({ |
| 375 name: stat, | 537 name: stat, |
| 376 scalar: new tr.v.ScalarNumeric(statUnit, statValue) | 538 scalar: new tr.v.ScalarNumeric(statUnit, statValue) |
| 377 }); | 539 }); |
| 378 } | 540 } |
| 379 } | 541 } |
| 380 }, this); | 542 }, this); |
| 381 return results; | 543 return results; |
| 382 }, | 544 }, |
| 383 | 545 |
| 546 get sampleValues() { |
| 547 return this.sampleValues_; |
| 548 }, |
| 549 |
| 384 clone: function() { | 550 clone: function() { |
| 385 return Numeric.fromDict(this.asDict()); | 551 return Numeric.fromDict(this.asDict()); |
| 386 }, | 552 }, |
| 387 | 553 |
| 388 asDict: function() { | 554 asDict: function() { |
| 389 var d = { | 555 var d = { |
| 390 unit: this.unit.asJSON(), | 556 unit: this.unit.asJSON(), |
| 391 type: 'numeric', | 557 type: 'numeric', |
| 392 | 558 |
| 393 min: this.range.min, | 559 min: this.range.min, |
| 394 max: this.range.max, | 560 max: this.range.max, |
| 395 | 561 |
| 396 numNans: this.numNans, | 562 numNans: this.numNans, |
| 397 nanSourceInfos: this.nanSourceInfos, | 563 nanDiagnostics: this.nanDiagnostics.map(d => d.asDict()), |
| 398 | 564 |
| 399 running: this.running.asDict(), | 565 running: this.running.asDict(), |
| 400 summaryOptions: this.summaryOptions, | 566 summaryOptions: this.summaryOptions, |
| 401 | 567 |
| 568 sampleValues: this.sampleValues, |
| 569 maxNumSampleValues: this.maxNumSampleValues, |
| 402 underflowBin: this.underflowBin.asDict(), | 570 underflowBin: this.underflowBin.asDict(), |
| 403 centralBins: this.centralBins.map(function(bin) { | 571 centralBins: this.centralBins.map(function(bin) { |
| 404 return bin.asDict(); | 572 return bin.asDict(); |
| 405 }), | 573 }), |
| 406 overflowBin: this.overflowBin.asDict() | 574 overflowBin: this.overflowBin.asDict() |
| 407 }; | 575 }; |
| 408 return d; | 576 return d; |
| 409 }, | 577 }, |
| 410 | 578 |
| 411 asJSON: function() { | 579 asJSON: function() { |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 501 * @param {number} nextBinBoundary The last added bin boundary (must be | 669 * @param {number} nextBinBoundary The last added bin boundary (must be |
| 502 * greater than |this.maxMinBoundary|). | 670 * greater than |this.maxMinBoundary|). |
| 503 * @param {number} binCount Number of bins to be added (must be positive). | 671 * @param {number} binCount Number of bins to be added (must be positive). |
| 504 */ | 672 */ |
| 505 addLinearBins: function(nextMaxBinBoundary, binCount) { | 673 addLinearBins: function(nextMaxBinBoundary, binCount) { |
| 506 if (binCount <= 0) | 674 if (binCount <= 0) |
| 507 throw new Error('Bin count must be positive'); | 675 throw new Error('Bin count must be positive'); |
| 508 | 676 |
| 509 var curMaxBinBoundary = this.maxBinBoundary; | 677 var curMaxBinBoundary = this.maxBinBoundary; |
| 510 if (curMaxBinBoundary >= nextMaxBinBoundary) { | 678 if (curMaxBinBoundary >= nextMaxBinBoundary) { |
| 511 throw new Error('The last added max boundary must be greater than ' + | 679 throw new Error('The new max bin boundary must be greater than ' + |
| 512 'the current max boundary boundary'); | 680 'the previous max bin boundary'); |
| 513 } | 681 } |
| 514 | 682 |
| 515 var binWidth = (nextMaxBinBoundary - curMaxBinBoundary) / binCount; | 683 var binWidth = (nextMaxBinBoundary - curMaxBinBoundary) / binCount; |
| 516 for (var i = 1; i < binCount; i++) | 684 for (var i = 1; i < binCount; i++) |
| 517 this.addBinBoundary(curMaxBinBoundary + i * binWidth); | 685 this.addBinBoundary(curMaxBinBoundary + i * binWidth); |
| 518 this.addBinBoundary(nextMaxBinBoundary); | 686 this.addBinBoundary(nextMaxBinBoundary); |
| 519 | 687 |
| 520 return this; | 688 return this; |
| 521 }, | 689 }, |
| 522 | 690 |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 594 numeric.allBins.forEach(function(bin) { | 762 numeric.allBins.forEach(function(bin) { |
| 595 bin.parentNumeric = numeric; | 763 bin.parentNumeric = numeric; |
| 596 }); | 764 }); |
| 597 return numeric; | 765 return numeric; |
| 598 } | 766 } |
| 599 }; | 767 }; |
| 600 | 768 |
| 601 /** | 769 /** |
| 602 * Create a linearly scaled tr.v.NumericBuilder with |numBins| bins ranging | 770 * Create a linearly scaled tr.v.NumericBuilder with |numBins| bins ranging |
| 603 * from |range.min| to |range.max|. | 771 * from |range.min| to |range.max|. |
| 772 * |
| 773 * @param {!tr.v.Unit} unit |
| 774 * @param {!tr.b.Range} range |
| 775 * @param {number} numBins |
| 776 * @return {tr.v.NumericBuilder} |
| 604 */ | 777 */ |
| 605 NumericBuilder.createLinear = function(unit, range, numBins) { | 778 NumericBuilder.createLinear = function(unit, range, numBins) { |
| 606 if (range.isEmpty) | 779 if (range.isEmpty) |
| 607 throw new Error('Range must be non-empty'); | 780 throw new Error('Range must be non-empty'); |
| 608 return new NumericBuilder(unit, range.min).addLinearBins( | 781 return new NumericBuilder(unit, range.min).addLinearBins( |
| 609 range.max, numBins); | 782 range.max, numBins); |
| 610 }; | 783 }; |
| 611 | 784 |
| 785 /** |
| 786 * Create an exponentially scaled tr.v.NumericBuilder with |numBins| bins |
| 787 * ranging from |range.min| to |range.max|. |
| 788 * |
| 789 * @param {!tr.v.Unit} unit |
| 790 * @param {!tr.b.Range} range |
| 791 * @param {number} numBins |
| 792 * @return {tr.v.NumericBuilder} |
| 793 */ |
| 794 NumericBuilder.createExponential = function(unit, range, numBins) { |
| 795 if (range.isEmpty) |
| 796 throw new Error('Range must be non-empty'); |
| 797 return new NumericBuilder(unit, range.min).addExponentialBins( |
| 798 range.max, numBins); |
| 799 }; |
| 800 |
| 612 function ScalarNumeric(unit, value) { | 801 function ScalarNumeric(unit, value) { |
| 802 if (!(unit instanceof tr.v.Unit)) |
| 803 throw new Error('Expected Unit'); |
| 804 |
| 613 if (!(typeof(value) == 'number')) | 805 if (!(typeof(value) == 'number')) |
| 614 throw new Error('Expected value to be number'); | 806 throw new Error('Expected value to be number'); |
| 615 | 807 |
| 616 NumericBase.call(this, unit); | 808 NumericBase.call(this, unit); |
| 617 this.value = value; | 809 this.value = value; |
| 618 } | 810 } |
| 619 | 811 |
| 620 ScalarNumeric.prototype = { | 812 ScalarNumeric.prototype = { |
| 621 __proto__: NumericBase.prototype, | 813 __proto__: NumericBase.prototype, |
| 622 | 814 |
| 623 asDictInto_: function(d) { | 815 asDictInto_: function(d) { |
| 624 d.type = 'scalar'; | 816 d.type = 'scalar'; |
| 625 | 817 |
| 626 // Infinity and NaN are left out of JSON for security reasons that do not | 818 // Infinity and NaN are left out of JSON for security reasons that do not |
| 627 // apply to our use cases. | 819 // apply to our use cases. |
| 628 if (this.value === Infinity) | 820 if (this.value === Infinity) |
| 629 d.value = 'Infinity'; | 821 d.value = 'Infinity'; |
| 630 else if (this.value === -Infinity) | 822 else if (this.value === -Infinity) |
| 631 d.value = '-Infinity'; | 823 d.value = '-Infinity'; |
| 632 else if (isNaN(this.value)) | 824 else if (isNaN(this.value)) |
| 633 d.value = 'NaN'; | 825 d.value = 'NaN'; |
| 634 else | 826 else |
| 635 d.value = this.value; | 827 d.value = this.value; |
| 636 }, | 828 }, |
| 637 | 829 |
| 830 sampleValuesInto: function(samples) { |
| 831 samples.push(this.value); |
| 832 }, |
| 833 |
| 638 toString: function() { | 834 toString: function() { |
| 639 return this.unit.format(this.value); | 835 return this.unit.format(this.value); |
| 640 } | 836 } |
| 641 }; | 837 }; |
| 642 | 838 |
| 643 ScalarNumeric.fromDict = function(d) { | 839 ScalarNumeric.fromDict = function(d) { |
| 644 // Infinity and NaN are left out of JSON for security reasons that do not | 840 // Infinity and NaN are left out of JSON for security reasons that do not |
| 645 // apply to our use cases. | 841 // apply to our use cases. |
| 646 if (typeof(d.value) === 'string') { | 842 if (typeof(d.value) === 'string') { |
| 647 if (d.value === '-Infinity') { | 843 if (d.value === '-Infinity') { |
| 648 d.value = -Infinity; | 844 d.value = -Infinity; |
| 649 } else if (d.value === 'Infinity') { | 845 } else if (d.value === 'Infinity') { |
| 650 d.value = Infinity; | 846 d.value = Infinity; |
| 651 } else if (d.value === 'NaN') { | 847 } else if (d.value === 'NaN') { |
| 652 d.value = NaN; | 848 d.value = NaN; |
| 653 } | 849 } |
| 654 } | 850 } |
| 655 | 851 |
| 656 return new ScalarNumeric(tr.v.Unit.fromJSON(d.unit), d.value); | 852 return new ScalarNumeric(tr.v.Unit.fromJSON(d.unit), d.value); |
| 657 }; | 853 }; |
| 658 | 854 |
| 659 return { | 855 return { |
| 856 Significance: Significance, |
| 660 NumericBase: NumericBase, | 857 NumericBase: NumericBase, |
| 661 NumericBin: NumericBin, | 858 NumericBin: NumericBin, |
| 662 Numeric: Numeric, | 859 Numeric: Numeric, |
| 663 NumericBuilder: NumericBuilder, | 860 NumericBuilder: NumericBuilder, |
| 664 ScalarNumeric: ScalarNumeric | 861 ScalarNumeric: ScalarNumeric |
| 665 }; | 862 }; |
| 666 }); | 863 }); |
| 667 </script> | 864 </script> |
| OLD | NEW |