| <!DOCTYPE html> |
| <!-- |
| Copyright 2016 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="import" href="/tracing/base/range.html"> |
| <link rel="import" href="/tracing/base/statistics.html"> |
| <link rel="import" href="/tracing/value/unit.html"> |
| |
| <script> |
| 'use strict'; |
| |
| tr.exportTo('tr.v', function() { |
| var Range = tr.b.Range; |
| |
| var MAX_SOURCE_INFOS = 16; |
| |
| function NumericBase(unit) { |
| if (!(unit instanceof tr.v.Unit)) |
| throw new Error('Expected provided unit to be instance of Unit'); |
| |
| this.unit = unit; |
| } |
| |
| NumericBase.prototype = { |
| asDict: function() { |
| var d = { |
| unit: this.unit.asJSON() |
| }; |
| |
| this.asDictInto_(d); |
| return d; |
| } |
| }; |
| |
| NumericBase.fromDict = function(d) { |
| if (d.type === 'scalar') |
| return ScalarNumeric.fromDict(d); |
| |
| throw new Error('Not implemented'); |
| }; |
| |
| function NumericBin(parentNumeric, opt_range) { |
| this.parentNumeric = parentNumeric; |
| this.range = opt_range || (new tr.b.Range()); |
| this.count = 0; |
| this.sourceInfos = []; |
| } |
| |
| NumericBin.fromDict = function(parentNumeric, d) { |
| var n = new NumericBin(parentNumeric); |
| n.range.min = d.min; |
| n.range.max = d.max; |
| n.count = d.count; |
| n.sourceInfos = d.sourceInfos; |
| return n; |
| }; |
| |
| NumericBin.prototype = { |
| add: function(value, sourceInfo) { |
| this.count += 1; |
| tr.b.Statistics.uniformlySampleStream(this.sourceInfos, this.count, |
| sourceInfo, MAX_SOURCE_INFOS); |
| }, |
| |
| addBin: function(other) { |
| if (!this.range.equals(other.range)) |
| throw new Error('Merging incompatible Numeric bins.'); |
| tr.b.Statistics.mergeSampledStreams(this.sourceInfos, this.count, |
| other.sourceInfos, other.count, MAX_SOURCE_INFOS); |
| this.count += other.count; |
| }, |
| |
| asDict: function() { |
| return { |
| min: this.range.min, |
| max: this.range.max, |
| count: this.count, |
| sourceInfos: this.sourceInfos.slice(0) |
| }; |
| }, |
| |
| asJSON: function() { |
| return this.asDict(); |
| } |
| }; |
| |
| function Numeric(unit, range, binInfo) { |
| NumericBase.call(this, unit); |
| |
| this.range = range; |
| |
| this.numNans = 0; |
| this.nanSourceInfos = []; |
| |
| this.runningSum = 0; |
| this.maxCount_ = 0; |
| |
| this.underflowBin = binInfo.underflowBin; |
| this.centralBins = binInfo.centralBins; |
| this.centralBinWidth = binInfo.centralBinWidth; |
| this.overflowBin = binInfo.overflowBin; |
| |
| this.allBins = []; |
| this.allBins.push(this.underflowBin); |
| this.allBins.push.apply(this.allBins, this.centralBins); |
| this.allBins.push(this.overflowBin); |
| |
| this.allBins.forEach(function(bin) { |
| if (bin.count > this.maxCount_) |
| this.maxCount_ = bin.count; |
| }, this); |
| } |
| |
| Numeric.fromDict = function(d) { |
| var range = Range.fromExplicitRange(d.min, d.max); |
| var binInfo = {}; |
| binInfo.underflowBin = NumericBin.fromDict(undefined, d.underflowBin); |
| binInfo.centralBins = d.centralBins.map(function(binAsDict) { |
| return NumericBin.fromDict(undefined, binAsDict); |
| }); |
| binInfo.centralBinWidth = d.centralBinWidth; |
| binInfo.overflowBin = NumericBin.fromDict(undefined, d.overflowBin); |
| var n = new Numeric(tr.v.Unit.fromJSON(d.unit), range, binInfo); |
| n.allBins.forEach(function(bin) { |
| bin.parentNumeric = n; |
| }); |
| n.runningSum = d.runningSum; |
| n.numNans = d.numNans; |
| n.nanSourceInfos = d.nanSourceInfos; |
| return n; |
| }; |
| |
| Numeric.createLinear = function(unit, range, numBins) { |
| if (range.isEmpty) |
| throw new Error('Nope'); |
| |
| var binInfo = {}; |
| binInfo.underflowBin = new NumericBin( |
| this, Range.fromExplicitRange(-Number.MAX_VALUE, range.min)); |
| binInfo.overflowBin = new NumericBin( |
| this, Range.fromExplicitRange(range.max, Number.MAX_VALUE)); |
| binInfo.centralBins = []; |
| binInfo.centralBinWidth = range.range / numBins; |
| |
| for (var i = 0; i < numBins; i++) { |
| var lo = range.min + (binInfo.centralBinWidth * i); |
| var hi = lo + binInfo.centralBinWidth; |
| binInfo.centralBins.push( |
| new NumericBin(undefined, Range.fromExplicitRange(lo, hi))); |
| } |
| |
| var n = new Numeric(unit, range, binInfo); |
| n.allBins.forEach(function(bin) { |
| bin.parentNumeric = n; |
| }); |
| return n; |
| }; |
| |
| Numeric.prototype = { |
| __proto__: NumericBase.prototype, |
| |
| get numValues() { |
| return tr.b.Statistics.sum(this.allBins, function(e) { |
| return e.count; |
| }); |
| }, |
| |
| get average() { |
| return this.runningSum / this.numValues; |
| }, |
| |
| get maxCount() { |
| return this.maxCount_; |
| }, |
| |
| getInterpolatedCountAt: function(value) { |
| var bin = this.getBinForValue(value); |
| var idx = this.centralBins.indexOf(bin); |
| if (idx < 0) { |
| // |value| is in either the underflowBin or the overflowBin. |
| // We can't interpolate between infinities. |
| return bin.count; |
| } |
| |
| // |value| must fall between the centers of two bins. |
| // The bin whose center is less than |value| will be this: |
| var lesserBin = bin; |
| |
| // The bin whose center is greater than |value| will be this: |
| var greaterBin = bin; |
| |
| // One of those bins could be an under/overflow bin. |
| // Avoid dealing with Infinities by arbitrarily saying that center of the |
| // underflow bin is its range.max, and the center of the overflow bin is |
| // its range.min. |
| // The centers of bins in |this.centralBins| will default to their |
| // |range.center|. |
| |
| var lesserBinCenter = undefined; |
| var greaterBinCenter = undefined; |
| |
| if (value < greaterBin.range.center) { |
| if (idx > 0) { |
| lesserBin = this.centralBins[idx - 1]; |
| } else { |
| lesserBin = this.underflowBin; |
| lesserBinCenter = lesserBin.range.max; |
| } |
| } else { |
| if (idx < (this.centralBins.length - 1)) { |
| greaterBin = this.centralBins[idx + 1]; |
| } else { |
| greaterBin = this.overflowBin; |
| greaterBinCenter = greaterBin.range.min; |
| } |
| } |
| |
| if (greaterBinCenter === undefined) |
| greaterBinCenter = greaterBin.range.center; |
| |
| if (lesserBinCenter === undefined) |
| lesserBinCenter = lesserBin.range.center; |
| |
| value = tr.b.normalize(value, lesserBinCenter, greaterBinCenter); |
| |
| return tr.b.lerp(value, lesserBin.count, greaterBin.count); |
| }, |
| |
| getBinForValue: function(value) { |
| if (value < this.range.min) |
| return this.underflowBin; |
| if (value >= this.range.max) |
| return this.overflowBin; |
| var binIdx = Math.floor((value - this.range.min) / this.centralBinWidth); |
| return this.centralBins[binIdx]; |
| }, |
| |
| add: function(value, sourceInfo) { |
| if (typeof(value) !== 'number' || isNaN(value)) { |
| this.numNans++; |
| tr.b.Statistics.uniformlySampleStream(this.nanSourceInfos, this.numNans, |
| sourceInfo, MAX_SOURCE_INFOS); |
| return; |
| } |
| |
| var bin = this.getBinForValue(value); |
| bin.add(value, sourceInfo); |
| this.runningSum += value; |
| if (bin.count > this.maxCount_) |
| this.maxCount_ = bin.count; |
| }, |
| |
| addNumeric: function(other) { |
| if (!this.range.equals(other.range) || |
| !this.unit === other.unit || |
| this.allBins.length !== other.allBins.length) { |
| throw new Error('Merging incompatible Numerics.'); |
| } |
| tr.b.Statistics.mergeSampledStreams(this.nanSourceInfos, this.numNans, |
| other.nanSourceInfos, other.numNans, MAX_SOURCE_INFOS); |
| this.numNans += other.numNans; |
| this.runningSum += other.runningSum; |
| for (var i = 0; i < this.allBins.length; ++i) { |
| this.allBins[i].addBin(other.allBins[i]); |
| } |
| }, |
| |
| clone: function() { |
| return Numeric.fromDict(this.asDict()); |
| }, |
| |
| asDict: function() { |
| var d = { |
| unit: this.unit.asJSON(), |
| |
| min: this.range.min, |
| max: this.range.max, |
| |
| numNans: this.numNans, |
| nanSourceInfos: this.nanSourceInfos, |
| |
| runningSum: this.runningSum, |
| |
| underflowBin: this.underflowBin.asDict(), |
| centralBins: this.centralBins.map(function(bin) { |
| return bin.asDict(); |
| }), |
| centralBinWidth: this.centralBinWidth, |
| overflowBin: this.overflowBin.asDict() |
| }; |
| return d; |
| }, |
| |
| asJSON: function() { |
| return this.asDict(); |
| } |
| }; |
| |
| function ScalarNumeric(unit, value) { |
| if (!(typeof(value) == 'number')) |
| throw new Error('Expected value to be number'); |
| |
| NumericBase.call(this, unit); |
| this.value = value; |
| } |
| |
| ScalarNumeric.prototype = { |
| __proto__: NumericBase.prototype, |
| |
| asDictInto_: function(d) { |
| d.type = 'scalar'; |
| d.value = this.value; |
| }, |
| |
| toString: function() { |
| return this.unit.format(this.value); |
| } |
| }; |
| |
| ScalarNumeric.fromDict = function(d) { |
| return new ScalarNumeric(tr.v.Unit.fromJSON(d.unit), d.value); |
| }; |
| |
| return { |
| NumericBase: NumericBase, |
| NumericBin: NumericBin, |
| Numeric: Numeric, |
| ScalarNumeric: ScalarNumeric |
| }; |
| }); |
| </script> |