1<!DOCTYPE html>
2<!--
3Copyright (c) 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/iteration_helpers.html">
9<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html">
10<link rel="import" href="/tracing/ui/analysis/stacked_pane.html">
11<link rel="import" href="/tracing/ui/base/table.html">
12<link rel="import" href="/tracing/value/numeric.html">
13<link rel="import" href="/tracing/value/unit.html">
14
15<polymer-element name="tr-ui-a-memory-dump-vm-regions-details-pane"
16    extends="tr-ui-a-stacked-pane">
17  <template>
18    <style>
19      :host {
20        display: flex;
21        flex-direction: column;
22      }
23
24      #label {
25        flex: 0 0 auto;
26        padding: 8px;
27
28        background-color: #eee;
29        border-bottom: 1px solid #8e8e8e;
30        border-top: 1px solid white;
31
32        font-size:  15px;
33        font-weight: bold;
34      }
35
36      #contents {
37        flex: 1 0 auto;
38        align-self: stretch;
39        font-size: 12px;
40      }
41
42      #info_text {
43        padding: 8px;
44        color: #666;
45        font-style: italic;
46        text-align: center;
47      }
48
49      #table {
50        display: none;  /* Hide until memory dumps are set. */
51        flex: 1 0 auto;
52        align-self: stretch;
53      }
54    </style>
55    <div id="label">Memory maps</div>
56    <div id="contents">
57      <div id="info_text">No memory maps selected</div>
58      <tr-ui-b-table id="table"></tr-ui-b-table>
59    </div>
60  </template>
61</polymer-element>
62<script>
63'use strict';
64
65tr.exportTo('tr.ui.analysis', function() {
66
67  var ScalarNumeric = tr.v.ScalarNumeric;
68  var sizeInBytes_smallerIsBetter =
69      tr.v.Unit.byName.sizeInBytes_smallerIsBetter;
70
71  var CONSTANT_COLUMN_RULES = [
72    {
73      condition: 'Start address',
74      importance: 0,
75      columnConstructor: tr.ui.analysis.StringMemoryColumn
76    }
77  ];
78
79  var VARIABLE_COLUMN_RULES = [
80    {
81      condition: 'Virtual size',
82      importance: 7,
83      columnConstructor: tr.ui.analysis.NumericMemoryColumn
84    },
85    {
86      condition: 'Protection flags',
87      importance: 6,
88      columnConstructor: tr.ui.analysis.StringMemoryColumn
89    },
90    {
91      condition: 'PSS',
92      importance: 5,
93      columnConstructor: tr.ui.analysis.NumericMemoryColumn
94    },
95    {
96      condition: 'Private dirty',
97      importance: 4,
98      columnConstructor: tr.ui.analysis.NumericMemoryColumn
99    },
100    {
101      condition: 'Private clean',
102      importance: 3,
103      columnConstructor: tr.ui.analysis.NumericMemoryColumn
104    },
105    {
106      condition: 'Shared dirty',
107      importance: 2,
108      columnConstructor: tr.ui.analysis.NumericMemoryColumn
109    },
110    {
111      condition: 'Shared clean',
112      importance: 1,
113      columnConstructor: tr.ui.analysis.NumericMemoryColumn
114    },
115    {
116      condition: 'Swapped',
117      importance: 0,
118      columnConstructor: tr.ui.analysis.NumericMemoryColumn
119    }
120  ];
121
122  var BYTE_STAT_COLUMN_MAP = {
123    'proportionalResident': 'PSS',
124    'privateDirtyResident': 'Private dirty',
125    'privateCleanResident': 'Private clean',
126    'sharedDirtyResident': 'Shared dirty',
127    'sharedCleanResident': 'Shared clean',
128    'swapped': 'Swapped'
129  };
130
131  function hexString(address, is64BitAddress) {
132    if (address === undefined)
133      return undefined;
134    var hexPadding = is64BitAddress ? '0000000000000000' : '00000000';
135    return (hexPadding + address.toString(16)).substr(-hexPadding.length);
136  }
137
138  function pruneEmptyRuleRows(row) {
139    if (row.subRows === undefined || row.subRows.length === 0)
140      return;
141
142    // Either all sub-rows are rule rows, or all sub-rows are VM region rows.
143    if (row.subRows[0].rule === undefined) {
144      // VM region rows: Early out to avoid filtering a large array for
145      // performance reasons (no sub-rows would be removed, but the whole array
146      // would be unnecessarily copied to a new array).
147      return;
148    }
149
150    row.subRows.forEach(pruneEmptyRuleRows);
151    row.subRows = row.subRows.filter(function(subRow) {
152      return subRow.subRows.length > 0;
153    });
154  }
155
156  Polymer('tr-ui-a-memory-dump-vm-regions-details-pane', {
157    created: function() {
158      this.vmRegions_ = undefined;
159      this.aggregationMode_ = undefined;
160    },
161
162    ready: function() {
163      this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW;
164    },
165
166    /**
167     * Sets the VM regions and schedules rebuilding the pane.
168     *
169     * The provided value should be a chronological list of lists of VM
170     * regions. All VM regions are assumed to belong to the same process.
171     * Example:
172     *
173     *   [
174     *     [
175     *       // VM regions at timestamp 1.
176     *       tr.model.VMRegion {},
177     *       tr.model.VMRegion {},
178     *       tr.model.VMRegion {}
179     *     ],
180     *     undefined,  // No VM regions provided at timestamp 2.
181     *     [
182     *       // VM regions at timestamp 3.
183     *       tr.model.VMRegion {},
184     *       tr.model.VMRegion {}
185     *     ]
186     *   ]
187     */
188    set vmRegions(vmRegions) {
189      this.vmRegions_ = vmRegions;
190      this.scheduleRebuildPane_();
191    },
192
193    get vmRegions() {
194      return this.vmRegions_;
195    },
196
197    set aggregationMode(aggregationMode) {
198      this.aggregationMode_ = aggregationMode;
199      this.scheduleRebuildPane_();
200    },
201
202    get aggregationMode() {
203      return this.aggregationMode_;
204    },
205
206    rebuildPane_: function() {
207      if (this.vmRegions_ === undefined || this.vmRegions_.length === 0) {
208        // Show the info text (hide the table).
209        this.$.info_text.style.display = 'block';
210        this.$.table.style.display = 'none';
211
212        this.$.table.clear();
213        this.$.table.rebuild();
214        return;
215      }
216
217      // Show the table (hide the info text).
218      this.$.info_text.style.display = 'none';
219      this.$.table.style.display = 'block';
220
221      var rows = this.createRows_(this.vmRegions_);
222      var columns = this.createColumns_(rows);
223
224      // Note: There is no need to aggregate fields of the VM regions because
225      // the classification tree already takes care of that.
226
227      this.$.table.tableRows = rows;
228      this.$.table.tableColumns = columns;
229
230      // TODO(petrcermak): This can be quite slow. Consider doing this somehow
231      // asynchronously.
232      this.$.table.rebuild();
233
234      tr.ui.analysis.expandTableRowsRecursively(this.$.table);
235    },
236
237    createRows_: function(timeToVmRegionTree) {
238      // Determine if any start address is outside the 32-bit range.
239      var is64BitAddress = timeToVmRegionTree.some(function(vmRegionTree) {
240        if (vmRegionTree === undefined)
241          return false;
242        return vmRegionTree.someRegion(function(region) {
243          if (region.startAddress === undefined)
244            return false;
245          return region.startAddress >= 4294967296 /* 2^32 */;
246        });
247      });
248
249      return [
250        this.createClassificationNodeRow(timeToVmRegionTree, is64BitAddress)
251      ];
252    },
253
254    createClassificationNodeRow: function(timeToNode, is64BitAddress) {
255      // Get any defined classification node so that we can extract the
256      // properties which don't change over time.
257      var definedNode = tr.b.findFirstInArray(timeToNode);
258
259      // Child node ID (list index) -> Timestamp (list index) ->
260      // VM region classification node.
261      var childNodeIdToTimeToNode = tr.b.dictionaryValues(
262          tr.b.invertArrayOfDicts(timeToNode, function(node) {
263            var children = node.children;
264            if (children === undefined)
265              return undefined;
266            var childMap = {};
267            children.forEach(function(childNode) {
268              if (!childNode.hasRegions)
269                return;
270              childMap[childNode.title] = childNode;
271            });
272            return childMap;
273          }));
274      var childNodeSubRows = childNodeIdToTimeToNode.map(
275          function(timeToChildNode) {
276            return this.createClassificationNodeRow(
277                timeToChildNode, is64BitAddress);
278          }, this);
279
280      // Region ID (list index) -> Timestamp (list index) -> VM region.
281      var regionIdToTimeToRegion = tr.b.dictionaryValues(
282          tr.b.invertArrayOfDicts(timeToNode, function(node) {
283            var regions = node.regions;
284            if (regions === undefined)
285              return undefined;
286            return tr.b.arrayToDict(regions, function(region) {
287              return region.uniqueIdWithinProcess;
288            });
289          }));
290      var regionSubRows = regionIdToTimeToRegion.map(function(timeToRegion) {
291        return this.createRegionRow_(timeToRegion, is64BitAddress);
292      }, this);
293
294      var subRows = childNodeSubRows.concat(regionSubRows);
295
296      return {
297        title: definedNode.title,
298        contexts: timeToNode,
299        variableCells: this.createVariableCells_(timeToNode),
300        subRows: subRows
301      };
302    },
303
304    createRegionRow_: function(timeToRegion, is64BitAddress) {
305      // Get any defined VM region so that we can extract the properties which
306      // don't change over time.
307      var definedRegion = tr.b.findFirstInArray(timeToRegion);
308
309      return {
310        title: definedRegion.mappedFile,
311        contexts: timeToRegion,
312        constantCells: this.createConstantCells_(definedRegion, is64BitAddress),
313        variableCells: this.createVariableCells_(timeToRegion)
314      };
315    },
316
317    /**
318     * Create cells for VM region properties which DON'T change over time.
319     *
320     * Note that there are currently no such properties of classification nodes.
321     */
322    createConstantCells_: function(definedRegion, is64BitAddress) {
323      return tr.ui.analysis.createCells([definedRegion], function(region) {
324        var startAddress = region.startAddress;
325        if (startAddress === undefined)
326          return undefined;
327        return { 'Start address': hexString(startAddress, is64BitAddress) };
328      });
329    },
330
331    /**
332     * Create cells for VM region (classification node) properties which DO
333     * change over time.
334     */
335    createVariableCells_: function(timeToRegion) {
336      return tr.ui.analysis.createCells(timeToRegion, function(region) {
337          var fields = {};
338
339          var sizeInBytes = region.sizeInBytes;
340          if (sizeInBytes !== undefined) {
341            fields['Virtual size'] = new ScalarNumeric(
342                sizeInBytes_smallerIsBetter, sizeInBytes);
343          }
344          var protectionFlags = region.protectionFlagsToString;
345          if (protectionFlags !== undefined)
346            fields['Protection flags'] = protectionFlags;
347
348          tr.b.iterItems(BYTE_STAT_COLUMN_MAP,
349              function(byteStatName, columnName) {
350                var byteStat = region.byteStats[byteStatName];
351                if (byteStat === undefined)
352                  return;
353                fields[columnName] = new ScalarNumeric(
354                    sizeInBytes_smallerIsBetter, byteStat);
355              });
356
357          return fields;
358        });
359    },
360
361    createColumns_: function(rows) {
362      var titleColumn = new tr.ui.analysis.TitleColumn('Mapped file');
363      titleColumn.width = '200px';
364
365      var constantColumns = tr.ui.analysis.MemoryColumn.fromRows(
366          rows, 'constantCells', undefined, CONSTANT_COLUMN_RULES);
367      var variableColumns = tr.ui.analysis.MemoryColumn.fromRows(
368          rows, 'variableCells', this.aggregationMode_, VARIABLE_COLUMN_RULES);
369      var fieldColumns = constantColumns.concat(variableColumns);
370      tr.ui.analysis.MemoryColumn.spaceEqually(fieldColumns);
371
372      var columns = [titleColumn].concat(fieldColumns);
373      return columns;
374    }
375  });
376
377  return {};
378});
379</script>
380