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