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 |