1// Copyright (c) 2013 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'use strict';
6
7base.requireStylesheet('tcmalloc.heap_instance_track');
8
9base.require('base.sorted_array_utils');
10base.require('tracing.tracks.heading_track');
11base.require('tracing.tracks.object_instance_track');
12base.require('tracing.color_scheme');
13base.require('ui');
14
15base.exportTo('tcmalloc', function() {
16
17  var palette = tracing.getColorPalette();
18  var highlightIdBoost = tracing.getColorPaletteHighlightIdBoost();
19
20  /**
21   * A track that displays heap memory data.
22   * @constructor
23   * @extends {HeadingTrack}
24   */
25
26  var HeapInstanceTrack = ui.define(
27      'heap-instance-track', tracing.tracks.HeadingTrack);
28
29  HeapInstanceTrack.prototype = {
30
31    __proto__: tracing.tracks.HeadingTrack.prototype,
32
33    decorate: function(viewport) {
34      tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
35      this.classList.add('heap-instance-track');
36      this.objectInstance_ = null;
37    },
38
39    set objectInstances(objectInstances) {
40      if (!objectInstances) {
41        this.objectInstance_ = [];
42        return;
43      }
44      if (objectInstances.length != 1)
45        throw new Error('Bad object instance count.');
46      this.objectInstance_ = objectInstances[0];
47      this.maxBytes_ = this.computeMaxBytes_(
48          this.objectInstance_.snapshots);
49    },
50
51    computeMaxBytes_: function(snapshots) {
52      var maxBytes = 0;
53      for (var i = 0; i < snapshots.length; i++) {
54        var snapshot = snapshots[i];
55        // Sum all the current allocations in this snapshot.
56        var traceNames = Object.keys(snapshot.heap_.children);
57        var sumBytes = 0;
58        for (var j = 0; j < traceNames.length; j++) {
59          sumBytes += snapshot.heap_.children[traceNames[j]].currentBytes;
60        }
61        // Keep track of the maximum across all snapshots.
62        if (sumBytes > maxBytes)
63          maxBytes = sumBytes;
64      }
65      return maxBytes;
66    },
67
68    get height() {
69      return window.getComputedStyle(this).height;
70    },
71
72    set height(height) {
73      this.style.height = height;
74    },
75
76    draw: function(type, viewLWorld, viewRWorld) {
77      switch (type) {
78        case tracing.tracks.DrawType.SLICE:
79          this.drawSlices_(viewLWorld, viewRWorld);
80          break;
81      }
82    },
83
84    drawSlices_: function(viewLWorld, viewRWorld) {
85      var ctx = this.context();
86      var pixelRatio = window.devicePixelRatio || 1;
87
88      var bounds = this.getBoundingClientRect();
89      var width = bounds.width * pixelRatio;
90      var height = bounds.height * pixelRatio;
91
92      // Culling parameters.
93      var vp = this.viewport;
94
95      // Scale by the size of the largest snapshot.
96      var maxBytes = this.maxBytes_;
97
98      var objectSnapshots = this.objectInstance_.snapshots;
99      var lowIndex = base.findLowIndexInSortedArray(
100          objectSnapshots,
101          function(snapshot) {
102            return snapshot.ts;
103          },
104          viewLWorld);
105      // Assure that the stack with the left edge off screen still gets drawn
106      if (lowIndex > 0)
107        lowIndex -= 1;
108
109      for (var i = lowIndex; i < objectSnapshots.length; ++i) {
110        var snapshot = objectSnapshots[i];
111
112        var left = snapshot.ts;
113        if (left > viewRWorld)
114          break;
115        var leftView = vp.xWorldToView(left);
116        if (leftView < 0)
117          leftView = 0;
118
119        // Compute the edges for the column graph bar.
120        var right;
121        if (i < objectSnapshots.length - 1)
122          right = objectSnapshots[i + 1].ts;
123        else
124          right = objectSnapshots[objectSnapshots.length - 1].ts + 5000;
125        var rightView = vp.xWorldToView(right);
126        if (rightView > width)
127          rightView = width;
128
129        // Floor the bounds to avoid a small gap between stacks.
130        leftView = Math.floor(leftView);
131        rightView = Math.floor(rightView);
132
133        // Draw a stacked bar graph. Largest item is stored first in the
134        // heap data structure, so iterate backwards. Likewise draw from
135        // the bottom of the bar upwards.
136        var currentY = height;
137        var keys = Object.keys(snapshot.heap_.children);
138        for (var k = keys.length - 1; k >= 0; k--) {
139          var trace = snapshot.heap_.children[keys[k]];
140          if (this.objectInstance_.selectedTraces &&
141              this.objectInstance_.selectedTraces.length > 0 &&
142              this.objectInstance_.selectedTraces[0] == keys[k]) {
143            // A trace selected in the analysis view is bright yellow.
144            ctx.fillStyle = 'rgb(239, 248, 206)';
145          } else {
146            // Selected snapshots get a lighter color.
147            var colorId = snapshot.selected ?
148                snapshot.objectInstance.colorId + highlightIdBoost :
149                snapshot.objectInstance.colorId;
150            ctx.fillStyle = palette[colorId + k];
151          }
152
153          var barHeight = height * trace.currentBytes / maxBytes;
154          ctx.fillRect(leftView, currentY - barHeight,
155                       Math.max(rightView - leftView, 1), barHeight);
156          currentY -= barHeight;
157        }
158      }
159      ctx.lineWidth = 1;
160    },
161
162    /**
163     * Used to hit-test clicks in the graph.
164     */
165    addIntersectingItemsInRangeToSelectionInWorldSpace: function(
166        loWX, hiWX, viewPixWidthWorld, selection) {
167      var that = this;
168      function onSnapshotHit(snapshot) {
169        selection.addObjectSnapshot(that, snapshot);
170      }
171      base.iterateOverIntersectingIntervals(
172          this.objectInstance_.snapshots,
173          function(x) { return x.ts; },
174          function(x) { return 5000; },
175          loWX, hiWX,
176          onSnapshotHit);
177    },
178
179    /**
180     * Add the item to the left or right of the provided hit, if any, to the
181     * selection.
182     * @param {slice} The current slice.
183     * @param {Number} offset Number of slices away from the hit to look.
184     * @param {Selection} selection The selection to add a hit to,
185     * if found.
186     * @return {boolean} Whether a hit was found.
187     * @private
188     */
189    addItemNearToProvidedHitToSelection: function(hit, offset, selection) {
190      if (hit instanceof tracing.SelectionObjectSnapshotHit) {
191        var objectSnapshots = this.objectInstance_.snapshots;
192        var index = objectSnapshots.indexOf(hit.objectSnapshot);
193        var newIndex = index + offset;
194        if (newIndex >= 0 && newIndex < objectSnapshots.length) {
195          selection.addObjectSnapshot(this, objectSnapshots[newIndex]);
196          return true;
197        }
198      } else {
199        throw new Error('Unrecognized hit');
200      }
201      return false;
202    },
203
204    addAllObjectsMatchingFilterToSelection: function(filter, selection) {
205    }
206  };
207
208  tracing.tracks.ObjectInstanceTrack.register(
209      'memory::Heap', HeapInstanceTrack);
210
211  return {
212    HeapInstanceTrack: HeapInstanceTrack
213  };
214});
215