1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5
6/**
7 * @fileoverview TimelineView visualizes GPU_TRACE events using the
8 * gpu.Timeline component.
9 */
10cr.define('gpu', function() {
11  function tsRound(ts) {
12    return Math.round(ts * 1000.0) / 1000.0;
13  }
14  function getPadding(text, width) {
15    width = width || 0;
16
17    if (typeof text != 'string')
18      text = String(text);
19
20    if (text.length >= width)
21      return '';
22
23    var pad = '';
24    for (var i = 0; i < width - text.length; i++)
25      pad += ' ';
26    return pad;
27  }
28
29  function leftAlign(text, width) {
30    return text + getPadding(text, width);
31  }
32
33  function rightAlign(text, width) {
34    return getPadding(text, width) + text;
35  }
36
37  /**
38   * TimelineView
39   * @constructor
40   * @extends {HTMLDivElement}
41   */
42  TimelineView = cr.ui.define('div');
43
44  TimelineView.prototype = {
45    __proto__: HTMLDivElement.prototype,
46
47    decorate: function() {
48      this.className = 'timeline-view';
49
50      this.timelineContainer_ = document.createElement('div');
51      this.timelineContainer_.className = 'timeline-container';
52
53      var summaryContainer_ = document.createElement('div');
54      summaryContainer_.className = 'summary-container';
55
56      this.summary_ = document.createElement('pre');
57      this.summary_.className = 'summary';
58
59      summaryContainer_.appendChild(this.summary_);
60      this.appendChild(this.timelineContainer_);
61      this.appendChild(summaryContainer_);
62
63      this.onSelectionChangedBoundToThis_ = this.onSelectionChanged_.bind(this);
64    },
65
66    set traceEvents(traceEvents) {
67      console.log('TimelineView.refresh');
68      this.timelineModel_ = new gpu.TimelineModel(traceEvents);
69
70      // remove old timeline
71      this.timelineContainer_.textContent = '';
72
73      // create new timeline if needed
74      if (traceEvents.length) {
75        this.timeline_ = new gpu.Timeline();
76        this.timeline_.model = this.timelineModel_;
77        this.timelineContainer_.appendChild(this.timeline_);
78        this.timeline_.onResize();
79        this.timeline_.addEventListener('selectionChange',
80                                        this.onSelectionChangedBoundToThis_);
81        this.onSelectionChanged_();
82      } else {
83        this.timeline_ = null;
84      }
85    },
86
87    onSelectionChanged_: function(e) {
88      console.log('selection changed');
89      var timeline = this.timeline_;
90      var selection = timeline.selection;
91      var outputDiv = this.summary_;
92      if (!selection.length) {
93        outputDiv.textContent = timeline.keyHelp;
94        return;
95      }
96
97      var text = '';
98      if (selection.length == 1) {
99        var c0Width = 10;
100        var slice = selection[0].slice;
101        text = 'Selected item:\n';
102        text += leftAlign('Title', c0Width) + ': ' + slice.title + '\n';
103        text += leftAlign('Start', c0Width) + ': ' +
104            tsRound(slice.start) + ' ms\n';
105        text += leftAlign('Duration', c0Width) + ': ' +
106            tsRound(slice.duration) + ' ms\n';
107
108        var n = 0;
109        for (var argName in slice.args) {
110          n += 1;
111        }
112        if (n > 0) {
113          text += leftAlign('Args', c0Width) + ':\n';
114          for (var argName in slice.args) {
115            var argVal = slice.args[argName];
116            text += leftAlign(' ' + argName, c0Width) + ': ' + argVal + '\n';
117          }
118        }
119      } else {
120        var c0Width = 55;
121        var c1Width = 12;
122        var c2Width = 5;
123        text = 'Selection summary:\n';
124        var tsLo = Math.min.apply(Math, selection.map(
125            function(s) {return s.slice.start;}));
126        var tsHi = Math.max.apply(Math, selection.map(
127            function(s) {return s.slice.end;}));
128
129        // compute total selection duration
130        var titles = selection.map(function(i) { return i.slice.title; });
131
132        var slicesByTitle = {};
133        for (var i = 0; i < selection.length; i++) {
134          var slice = selection[i].slice;
135          if (!slicesByTitle[slice.title])
136            slicesByTitle[slice.title] = {
137              slices: []
138            };
139          slicesByTitle[slice.title].slices.push(slice);
140        }
141        var totalDuration = 0;
142        for (var sliceGroupTitle in slicesByTitle) {
143          var sliceGroup = slicesByTitle[sliceGroupTitle];
144          var duration = 0;
145          for (i = 0; i < sliceGroup.slices.length; i++)
146            duration += sliceGroup.slices[i].duration;
147          totalDuration += duration;
148
149          text += ' ' +
150              leftAlign(sliceGroupTitle, c0Width) + ': ' +
151              rightAlign(tsRound(duration) + 'ms', c1Width) + '   ' +
152              rightAlign(String(sliceGroup.slices.length), c2Width) +
153              ' occurrences' + '\n';
154        }
155
156        text += leftAlign('*Totals', c0Width) + ' : ' +
157            rightAlign(tsRound(totalDuration) + 'ms', c1Width) + '   ' +
158            rightAlign(String(selection.length), c2Width) + ' occurrences' +
159            '\n';
160
161        text += '\n';
162
163        text += leftAlign('Selection start', c0Width) + ' : ' +
164            rightAlign(tsRound(tsLo) + 'ms', c1Width) +
165            '\n';
166        text += leftAlign('Selection extent', c0Width) + ' : ' +
167            rightAlign(tsRound(tsHi - tsLo) + 'ms', c1Width) +
168            '\n';
169      }
170
171      // done
172      outputDiv.textContent = text;
173    }
174  };
175
176  return {
177    TimelineView: TimelineView
178  };
179});
180