1<!DOCTYPE html>
2<!--
3Copyright 2015 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/event.html">
9<link rel="import" href="/tracing/base/event_target.html">
10<link rel="import" href="/tracing/value/time_display_mode.html">
11
12<script>
13'use strict';
14
15tr.exportTo('tr.v', function() {
16  var TimeDisplayModes = tr.v.TimeDisplayModes;
17
18  var BINARY_PREFIXES = ['', 'Ki', 'Mi', 'Gi', 'Ti'];
19
20  var PLUS_MINUS_SIGN = String.fromCharCode(177);
21
22  function max(a, b) {
23    if (a === undefined)
24      return b;
25    if (b === undefined)
26      return a;
27    return a.scale > b.scale ? a : b;
28  }
29
30  /** @enum */
31  var ImprovementDirection = {
32    DONT_CARE: 0,
33    BIGGER_IS_BETTER: 1,
34    SMALLER_IS_BETTER: 2
35  };
36
37  /** @constructor */
38  function Unit(unitName, jsonName, isDelta, improvementDirection,
39      formatValue) {
40    this.unitName = unitName;
41    this.jsonName = jsonName;
42    this.isDelta = isDelta;
43    this.improvementDirection = improvementDirection;
44    this.formatValue_ = formatValue;
45    this.correspondingDeltaUnit = undefined;
46  }
47
48  Unit.prototype = {
49    asJSON: function() {
50      return this.jsonName;
51    },
52
53    format: function(value) {
54      var formattedValue = this.formatValue_(value);
55      if (!this.isDelta || value < 0 /* already contains negative sign */)
56        return formattedValue;
57      if (value === 0)
58        return PLUS_MINUS_SIGN + formattedValue;
59      else
60        return '+' + formattedValue;
61    }
62  };
63
64  Unit.reset = function() {
65    Unit.currentTimeDisplayMode = TimeDisplayModes.ms;
66  };
67
68  Unit.timestampFromUs = function(us) {
69    return us / 1000;
70  };
71
72  Unit.maybeTimestampFromUs = function(us) {
73    return us === undefined ? undefined : us / 1000;
74  };
75
76  Object.defineProperty(Unit, 'currentTimeDisplayMode', {
77    get: function() {
78      return Unit.currentTimeDisplayMode_;
79    },
80    // Use tr-v-ui-preferred-display-unit element instead of directly setting.
81    set: function(value) {
82      if (Unit.currentTimeDisplayMode_ === value)
83        return;
84
85      Unit.currentTimeDisplayMode_ = value;
86      Unit.dispatchEvent(new tr.b.Event('display-mode-changed'));
87    }
88  });
89
90  Unit.didPreferredTimeDisplayUnitChange = function() {
91    var largest = undefined;
92    var els = tr.b.findDeepElementsMatching(document.body,
93        'tr-v-ui-preferred-display-unit');
94    els.forEach(function(el) {
95      largest = max(largest, el.preferredTimeDisplayMode);
96    });
97
98    Unit.currentDisplayUnit = largest === undefined ?
99        TimeDisplayModes.ms : largest;
100  };
101
102  Unit.byName = {};
103  Unit.byJSONName = {};
104
105  Unit.fromJSON = function(object) {
106    var u = Unit.byJSONName[object];
107    if (u) {
108      return u;
109    }
110    throw new Error('Unrecognized unit');
111  };
112
113  /**
114   * Define all combinations of a unit with isDelta and improvementDirection
115   * flags. For example, the following code:
116   *
117   *   Unit.define({
118   *     baseUnitName: 'powerInWatts'
119   *     baseJsonName: 'W'
120   *     formatValue: function(value) {
121   *       // Code for formatting the unit (independent of isDelta and
122   *       // improvementDirection flags).
123   *      }
124   *   });
125   *
126   * generates the following six units (JSON names shown in parentheses):
127   *
128   *   Unit.byName.powerInWatts (W)
129   *   Unit.byName.powerInWatts_smallerIsBetter (W_smallerIsBetter)
130   *   Unit.byName.powerInWatts_biggerIsBetter (W_biggerIsBetter)
131   *   Unit.byName.powerInWattsDelta (WDelta)
132   *   Unit.byName.powerInWattsDelta_smallerIsBetter (WDelta_smallerIsBetter)
133   *   Unit.byName.powerInWattsDelta_biggerIsBetter (WDelta_biggerIsBetter)
134   *
135   * with the appropriate flags and formatting code (including +/- prefixes
136   * for deltas).
137   */
138  Unit.define = function(params) {
139    tr.b.iterItems(ImprovementDirection, function(_, improvementDirection) {
140      var regularUnit =
141          Unit.defineUnitVariant_(params, false, improvementDirection);
142      var deltaUnit =
143          Unit.defineUnitVariant_(params, true, improvementDirection);
144
145      regularUnit.correspondingDeltaUnit = deltaUnit;
146      deltaUnit.correspondingDeltaUnit = deltaUnit;
147    });
148  };
149
150  Unit.defineUnitVariant_ = function(params, isDelta, improvementDirection) {
151    var nameSuffix = isDelta ? 'Delta' : '';
152    switch (improvementDirection) {
153      case ImprovementDirection.DONT_CARE:
154        break;
155      case ImprovementDirection.BIGGER_IS_BETTER:
156        nameSuffix += '_biggerIsBetter';
157        break;
158      case ImprovementDirection.SMALLER_IS_BETTER:
159        nameSuffix += '_smallerIsBetter';
160        break;
161      default:
162        throw new Error(
163            'Unknown improvement direction: ' + improvementDirection);
164    }
165
166    var unitName = params.baseUnitName + nameSuffix;
167    var jsonName = params.baseJsonName + nameSuffix;
168    if (Unit.byName[unitName] !== undefined)
169      throw new Error('Unit \'' + unitName + '\' already exists');
170    if (Unit.byJSONName[jsonName] !== undefined)
171      throw new Error('JSON unit \'' + jsonName + '\' alread exists');
172
173    var unit = new Unit(
174        unitName, jsonName, isDelta, improvementDirection, params.formatValue);
175    Unit.byName[unitName] = unit;
176    Unit.byJSONName[jsonName] = unit;
177
178    return unit;
179  };
180
181  tr.b.EventTarget.decorate(Unit);
182  Unit.reset();
183
184  // Known display units follow.
185  //////////////////////////////////////////////////////////////////////////////
186
187  Unit.define({
188    baseUnitName: 'timeDurationInMs',
189    baseJsonName: 'ms',
190    formatValue: function(value) {
191      return Unit.currentTimeDisplayMode_.format(value);
192    }
193  });
194
195  Unit.define({
196    baseUnitName: 'timeStampInMs',
197    baseJsonName: 'tsMs',
198    formatValue: function(value) {
199      return Unit.currentTimeDisplayMode_.format(value);
200    }
201  });
202
203  Unit.define({
204    baseUnitName: 'normalizedPercentage',
205    baseJsonName: 'n%',
206    formatValue: function(value) {
207      var tmp = new Number(Math.round(value * 100000) / 1000);
208      return tmp.toLocaleString(undefined, { minimumFractionDigits: 3 }) + '%';
209    }
210  });
211
212  Unit.define({
213    baseUnitName: 'sizeInBytes',
214    baseJsonName: 'sizeInBytes',
215    formatValue: function(value) {
216      var signPrefix = '';
217      if (value < 0) {
218        signPrefix = '-';
219        value = -value;
220      }
221
222      var i = 0;
223      while (value >= 1024 && i < BINARY_PREFIXES.length - 1) {
224        value /= 1024;
225        i++;
226      }
227
228      return signPrefix + value.toFixed(1) + ' ' + BINARY_PREFIXES[i] + 'B';
229    }
230  });
231
232  Unit.define({
233    baseUnitName: 'energyInJoules',
234    baseJsonName: 'J',
235    formatValue: function(value) {
236      return value.toLocaleString(
237          undefined, { minimumFractionDigits: 3 }) + ' J';
238    }
239  });
240
241  Unit.define({
242    baseUnitName: 'powerInWatts',
243    baseJsonName: 'W',
244    formatValue: function(value) {
245      return value.toLocaleString(
246          undefined, { minimumFractionDigits: 3 }) + ' W';
247    }
248  });
249
250  Unit.define({
251    baseUnitName: 'unitlessNumber',
252    baseJsonName: 'unitless',
253    formatValue: function(value) {
254      return value.toLocaleString(
255          undefined, { minimumFractionDigits: 3, maximumFractionDigits: 3 });
256    }
257  });
258
259  return {
260    ImprovementDirection: ImprovementDirection,
261    Unit: Unit
262  };
263});
264</script>
265