1<!DOCTYPE html> 2<!-- 3Copyright 2016 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/base/range.html"> 9<link rel="import" href="/tracing/base/statistics.html"> 10<link rel="import" href="/tracing/value/unit.html"> 11 12<script> 13'use strict'; 14 15tr.exportTo('tr.v', function() { 16 var Range = tr.b.Range; 17 18 var MAX_SOURCE_INFOS = 16; 19 20 function NumericBase(unit) { 21 if (!(unit instanceof tr.v.Unit)) 22 throw new Error('Expected provided unit to be instance of Unit'); 23 24 this.unit = unit; 25 } 26 27 NumericBase.prototype = { 28 asDict: function() { 29 var d = { 30 unit: this.unit.asJSON() 31 }; 32 33 this.asDictInto_(d); 34 return d; 35 } 36 }; 37 38 NumericBase.fromDict = function(d) { 39 if (d.type === 'scalar') 40 return ScalarNumeric.fromDict(d); 41 42 throw new Error('Not implemented'); 43 }; 44 45 function NumericBin(parentNumeric, opt_range) { 46 this.parentNumeric = parentNumeric; 47 this.range = opt_range || (new tr.b.Range()); 48 this.count = 0; 49 this.sourceInfos = []; 50 } 51 52 NumericBin.fromDict = function(parentNumeric, d) { 53 var n = new NumericBin(parentNumeric); 54 n.range.min = d.min; 55 n.range.max = d.max; 56 n.count = d.count; 57 n.sourceInfos = d.sourceInfos; 58 return n; 59 }; 60 61 NumericBin.prototype = { 62 add: function(value, sourceInfo) { 63 this.count += 1; 64 tr.b.Statistics.uniformlySampleStream(this.sourceInfos, this.count, 65 sourceInfo, MAX_SOURCE_INFOS); 66 }, 67 68 addBin: function(other) { 69 if (!this.range.equals(other.range)) 70 throw new Error('Merging incompatible Numeric bins.'); 71 tr.b.Statistics.mergeSampledStreams(this.sourceInfos, this.count, 72 other.sourceInfos, other.count, MAX_SOURCE_INFOS); 73 this.count += other.count; 74 }, 75 76 asDict: function() { 77 return { 78 min: this.range.min, 79 max: this.range.max, 80 count: this.count, 81 sourceInfos: this.sourceInfos.slice(0) 82 }; 83 }, 84 85 asJSON: function() { 86 return this.asDict(); 87 } 88 }; 89 90 function Numeric(unit, range, binInfo) { 91 NumericBase.call(this, unit); 92 93 this.range = range; 94 95 this.numNans = 0; 96 this.nanSourceInfos = []; 97 98 this.runningSum = 0; 99 this.maxCount_ = 0; 100 101 this.underflowBin = binInfo.underflowBin; 102 this.centralBins = binInfo.centralBins; 103 this.centralBinWidth = binInfo.centralBinWidth; 104 this.overflowBin = binInfo.overflowBin; 105 106 this.allBins = []; 107 this.allBins.push(this.underflowBin); 108 this.allBins.push.apply(this.allBins, this.centralBins); 109 this.allBins.push(this.overflowBin); 110 111 this.allBins.forEach(function(bin) { 112 if (bin.count > this.maxCount_) 113 this.maxCount_ = bin.count; 114 }, this); 115 } 116 117 Numeric.fromDict = function(d) { 118 var range = Range.fromExplicitRange(d.min, d.max); 119 var binInfo = {}; 120 binInfo.underflowBin = NumericBin.fromDict(undefined, d.underflowBin); 121 binInfo.centralBins = d.centralBins.map(function(binAsDict) { 122 return NumericBin.fromDict(undefined, binAsDict); 123 }); 124 binInfo.centralBinWidth = d.centralBinWidth; 125 binInfo.overflowBin = NumericBin.fromDict(undefined, d.overflowBin); 126 var n = new Numeric(tr.v.Unit.fromJSON(d.unit), range, binInfo); 127 n.allBins.forEach(function(bin) { 128 bin.parentNumeric = n; 129 }); 130 n.runningSum = d.runningSum; 131 n.numNans = d.numNans; 132 n.nanSourceInfos = d.nanSourceInfos; 133 return n; 134 }; 135 136 Numeric.createLinear = function(unit, range, numBins) { 137 if (range.isEmpty) 138 throw new Error('Nope'); 139 140 var binInfo = {}; 141 binInfo.underflowBin = new NumericBin( 142 this, Range.fromExplicitRange(-Number.MAX_VALUE, range.min)); 143 binInfo.overflowBin = new NumericBin( 144 this, Range.fromExplicitRange(range.max, Number.MAX_VALUE)); 145 binInfo.centralBins = []; 146 binInfo.centralBinWidth = range.range / numBins; 147 148 for (var i = 0; i < numBins; i++) { 149 var lo = range.min + (binInfo.centralBinWidth * i); 150 var hi = lo + binInfo.centralBinWidth; 151 binInfo.centralBins.push( 152 new NumericBin(undefined, Range.fromExplicitRange(lo, hi))); 153 } 154 155 var n = new Numeric(unit, range, binInfo); 156 n.allBins.forEach(function(bin) { 157 bin.parentNumeric = n; 158 }); 159 return n; 160 }; 161 162 Numeric.prototype = { 163 __proto__: NumericBase.prototype, 164 165 get numValues() { 166 return tr.b.Statistics.sum(this.allBins, function(e) { 167 return e.count; 168 }); 169 }, 170 171 get average() { 172 return this.runningSum / this.numValues; 173 }, 174 175 get maxCount() { 176 return this.maxCount_; 177 }, 178 179 getInterpolatedCountAt: function(value) { 180 var bin = this.getBinForValue(value); 181 var idx = this.centralBins.indexOf(bin); 182 if (idx < 0) { 183 // |value| is in either the underflowBin or the overflowBin. 184 // We can't interpolate between infinities. 185 return bin.count; 186 } 187 188 // |value| must fall between the centers of two bins. 189 // The bin whose center is less than |value| will be this: 190 var lesserBin = bin; 191 192 // The bin whose center is greater than |value| will be this: 193 var greaterBin = bin; 194 195 // One of those bins could be an under/overflow bin. 196 // Avoid dealing with Infinities by arbitrarily saying that center of the 197 // underflow bin is its range.max, and the center of the overflow bin is 198 // its range.min. 199 // The centers of bins in |this.centralBins| will default to their 200 // |range.center|. 201 202 var lesserBinCenter = undefined; 203 var greaterBinCenter = undefined; 204 205 if (value < greaterBin.range.center) { 206 if (idx > 0) { 207 lesserBin = this.centralBins[idx - 1]; 208 } else { 209 lesserBin = this.underflowBin; 210 lesserBinCenter = lesserBin.range.max; 211 } 212 } else { 213 if (idx < (this.centralBins.length - 1)) { 214 greaterBin = this.centralBins[idx + 1]; 215 } else { 216 greaterBin = this.overflowBin; 217 greaterBinCenter = greaterBin.range.min; 218 } 219 } 220 221 if (greaterBinCenter === undefined) 222 greaterBinCenter = greaterBin.range.center; 223 224 if (lesserBinCenter === undefined) 225 lesserBinCenter = lesserBin.range.center; 226 227 value = tr.b.normalize(value, lesserBinCenter, greaterBinCenter); 228 229 return tr.b.lerp(value, lesserBin.count, greaterBin.count); 230 }, 231 232 getBinForValue: function(value) { 233 if (value < this.range.min) 234 return this.underflowBin; 235 if (value >= this.range.max) 236 return this.overflowBin; 237 var binIdx = Math.floor((value - this.range.min) / this.centralBinWidth); 238 return this.centralBins[binIdx]; 239 }, 240 241 add: function(value, sourceInfo) { 242 if (typeof(value) !== 'number' || isNaN(value)) { 243 this.numNans++; 244 tr.b.Statistics.uniformlySampleStream(this.nanSourceInfos, this.numNans, 245 sourceInfo, MAX_SOURCE_INFOS); 246 return; 247 } 248 249 var bin = this.getBinForValue(value); 250 bin.add(value, sourceInfo); 251 this.runningSum += value; 252 if (bin.count > this.maxCount_) 253 this.maxCount_ = bin.count; 254 }, 255 256 addNumeric: function(other) { 257 if (!this.range.equals(other.range) || 258 !this.unit === other.unit || 259 this.allBins.length !== other.allBins.length) { 260 throw new Error('Merging incompatible Numerics.'); 261 } 262 tr.b.Statistics.mergeSampledStreams(this.nanSourceInfos, this.numNans, 263 other.nanSourceInfos, other.numNans, MAX_SOURCE_INFOS); 264 this.numNans += other.numNans; 265 this.runningSum += other.runningSum; 266 for (var i = 0; i < this.allBins.length; ++i) { 267 this.allBins[i].addBin(other.allBins[i]); 268 } 269 }, 270 271 clone: function() { 272 return Numeric.fromDict(this.asDict()); 273 }, 274 275 asDict: function() { 276 var d = { 277 unit: this.unit.asJSON(), 278 279 min: this.range.min, 280 max: this.range.max, 281 282 numNans: this.numNans, 283 nanSourceInfos: this.nanSourceInfos, 284 285 runningSum: this.runningSum, 286 287 underflowBin: this.underflowBin.asDict(), 288 centralBins: this.centralBins.map(function(bin) { 289 return bin.asDict(); 290 }), 291 centralBinWidth: this.centralBinWidth, 292 overflowBin: this.overflowBin.asDict() 293 }; 294 return d; 295 }, 296 297 asJSON: function() { 298 return this.asDict(); 299 } 300 }; 301 302 function ScalarNumeric(unit, value) { 303 if (!(typeof(value) == 'number')) 304 throw new Error('Expected value to be number'); 305 306 NumericBase.call(this, unit); 307 this.value = value; 308 } 309 310 ScalarNumeric.prototype = { 311 __proto__: NumericBase.prototype, 312 313 asDictInto_: function(d) { 314 d.type = 'scalar'; 315 d.value = this.value; 316 }, 317 318 toString: function() { 319 return this.unit.format(this.value); 320 } 321 }; 322 323 ScalarNumeric.fromDict = function(d) { 324 return new ScalarNumeric(tr.v.Unit.fromJSON(d.unit), d.value); 325 }; 326 327 return { 328 NumericBase: NumericBase, 329 NumericBin: NumericBin, 330 Numeric: Numeric, 331 ScalarNumeric: ScalarNumeric 332 }; 333}); 334</script> 335