blob: 68dac339b6da24cad45634c60118193cc85d97a4 [file] [log] [blame]
<!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>