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/base.html">
9
10<script>
11'use strict';
12
13/**
14 * @fileoverview Provides classes for representing and classifying VM regions.
15 *
16 * See https://goo.gl/5SSPv0 for more details.
17 */
18tr.exportTo('tr.model', function() {
19
20  /**
21   * A single virtual memory region (also called a memory map).
22   *
23   * @constructor
24   */
25  function VMRegion(startAddress, sizeInBytes, protectionFlags,
26      mappedFile, byteStats) {
27    this.startAddress = startAddress;
28    this.sizeInBytes = sizeInBytes;
29    this.protectionFlags = protectionFlags;
30    this.mappedFile = mappedFile || '';
31    this.byteStats = byteStats || {};
32  };
33
34  VMRegion.PROTECTION_FLAG_READ = 4;
35  VMRegion.PROTECTION_FLAG_WRITE = 2;
36  VMRegion.PROTECTION_FLAG_EXECUTE = 1;
37  VMRegion.PROTECTION_FLAG_MAYSHARE = 128;
38
39  VMRegion.prototype = {
40    get uniqueIdWithinProcess() {
41      // This value is assumed to be unique within a process.
42      return this.mappedFile + '#' + this.startAddress;
43    },
44
45    get protectionFlagsToString() {
46      if (this.protectionFlags === undefined)
47        return undefined;
48      return (
49          (this.protectionFlags & VMRegion.PROTECTION_FLAG_READ ? 'r' : '-') +
50          (this.protectionFlags & VMRegion.PROTECTION_FLAG_WRITE ? 'w' : '-') +
51          (this.protectionFlags & VMRegion.PROTECTION_FLAG_EXECUTE ?
52              'x' : '-') +
53          (this.protectionFlags & VMRegion.PROTECTION_FLAG_MAYSHARE ? 's' : 'p')
54      );
55    }
56  };
57
58  VMRegion.fromDict = function(dict) {
59    return new VMRegion(
60        dict.startAddress,
61        dict.sizeInBytes,
62        dict.protectionFlags,
63        dict.mappedFile,
64        dict.byteStats);
65  };
66
67  /**
68   * Node in a VM region classification tree.
69   *
70   * Note: Most users of this class should use the
71   * VMRegionClassificationNode.fromRegions static method instead of this
72   * constructor because it leads to better performance due to fewer memory
73   * allocations.
74   *
75   * @constructor
76   */
77  function VMRegionClassificationNode(opt_rule) {
78    this.rule_ = opt_rule || VMRegionClassificationNode.CLASSIFICATION_RULES;
79
80    // True iff this node or any of its descendant classification nodes has at
81    // least one classified VM region.
82    this.hasRegions = false;
83
84    // Total virtual size and byte stats of all regions matching this node's
85    // rule (including its sub-rules).
86    this.sizeInBytes = undefined;
87    this.byteStats = {};
88
89    // Array of child classification nodes if this is an intermediate node.
90    this.children_ = undefined;
91
92    // Array of VM regions. If this is an intermediate node, then the regions
93    // are cached for lazy tree construction (i.e. its child classification
94    // nodes yet have to be built).
95    this.regions_ = [];
96  }
97
98  /**
99   * Rules for classifying memory maps.
100   *
101   * These rules are derived from core/jni/android_os_Debug.cpp in Android.
102   */
103  VMRegionClassificationNode.CLASSIFICATION_RULES = {
104    name: 'Total',
105    children: [
106      {
107        name: 'Android',
108        file: /^\/dev\/ashmem(?!\/libc malloc)/,
109        children: [
110          {
111            name: 'Java runtime',
112            file: /^\/dev\/ashmem\/dalvik-/,
113            children: [
114              {
115                name: 'Spaces',
116                file: /\/dalvik-(alloc|main|large object|non moving|zygote) space/,  // @suppress longLineCheck
117                children: [
118                  {
119                    name: 'Normal',
120                    file: /\/dalvik-(alloc|main)/
121                  },
122                  {
123                    name: 'Large',
124                    file: /\/dalvik-large object/
125                  },
126                  {
127                    name: 'Zygote',
128                    file: /\/dalvik-zygote/
129                  },
130                  {
131                    name: 'Non-moving',
132                    file: /\/dalvik-non moving/
133                  }
134                ]
135              },
136              {
137                name: 'Linear Alloc',
138                file: /\/dalvik-LinearAlloc/
139              },
140              {
141                name: 'Indirect Reference Table',
142                file: /\/dalvik-indirect.ref/
143              },
144              {
145                name: 'Cache',
146                file: /\/dalvik-jit-code-cache/
147              },
148              {
149                name: 'Accounting'
150              }
151            ]
152          },
153          {
154            name: 'Cursor',
155            file: /\/CursorWindow/
156          },
157          {
158            name: 'Ashmem'
159          }
160        ]
161      },
162      {
163        name: 'Native heap',
164        file: /^((\[heap\])|(\[anon:)|(\/dev\/ashmem\/libc malloc)|(\[discounted tracing overhead\])|$)/  // @suppress longLineCheck
165      },
166      {
167        name: 'Stack',
168        file: /^\[stack/
169      },
170      {
171        name: 'Files',
172        file: /\.((((jar)|(apk)|(ttf)|(odex)|(oat)|(art))$)|(dex)|(so))/,
173        children: [
174          {
175            name: 'so',
176            file: /\.so/
177          },
178          {
179            name: 'jar',
180            file: /\.jar$/
181          },
182          {
183            name: 'apk',
184            file: /\.apk$/
185          },
186          {
187            name: 'ttf',
188            file: /\.ttf$/
189          },
190          {
191            name: 'dex',
192            file: /\.((dex)|(odex$))/
193          },
194          {
195            name: 'oat',
196            file: /\.oat$/
197          },
198          {
199            name: 'art',
200            file: /\.art$/
201          }
202        ]
203      },
204      {
205        name: 'Devices',
206        file: /(^\/dev\/)|(anon_inode:dmabuf)/,
207        children: [
208          {
209            name: 'GPU',
210            file: /\/((nv)|(mali)|(kgsl))/
211          },
212          {
213            name: 'DMA',
214            file: /anon_inode:dmabuf/
215          }
216        ]
217      }
218    ]
219  };
220  VMRegionClassificationNode.OTHER_RULE = { name: 'Other' };
221
222  VMRegionClassificationNode.fromRegions = function(regions, opt_rules) {
223    var tree = new VMRegionClassificationNode(opt_rules);
224    tree.regions_ = regions;
225    for (var i = 0; i < regions.length; i++)
226      tree.addStatsFromRegion_(regions[i]);
227    return tree;
228  };
229
230  VMRegionClassificationNode.prototype = {
231    get title() {
232      return this.rule_.name;
233    },
234
235    get children() {
236      if (this.isLeafNode)
237        return undefined;  // Leaf nodes don't have children (by definition).
238      if (this.children_ === undefined)
239        this.buildTree_();  // Lazily classify VM regions.
240      return this.children_;
241    },
242
243    get regions() {
244      if (!this.isLeafNode) {
245        // Intermediate nodes only temporarily cache VM regions for lazy tree
246        // construction.
247        return undefined;
248      }
249      return this.regions_;
250    },
251
252    get allRegionsForTesting() {
253      if (this.regions_ !== undefined) {
254        if (this.children_ !== undefined) {
255          throw new Error('Internal error: a VM region classification node ' +
256              'cannot have both regions and children');
257        }
258        // Leaf node (or caching internal node).
259        return this.regions_;
260      }
261
262      // Intermediate node.
263      var regions = [];
264      this.children_.forEach(function(childNode) {
265        regions = regions.concat(childNode.allRegionsForTesting);
266      });
267      return regions;
268    },
269
270    get isLeafNode() {
271      var children = this.rule_.children;
272      return children === undefined || children.length === 0;
273    },
274
275    addRegion: function(region) {
276      this.addRegionRecursively_(region, true /* addStatsToThisNode */);
277    },
278
279    someRegion: function(fn, opt_this) {
280      if (this.regions_ !== undefined) {
281        // Leaf node (or caching internal node).
282        return this.regions_.some(fn, opt_this);
283      }
284
285      // Intermediate node.
286      return this.children_.some(function(childNode) {
287        return childNode.someRegion(fn, opt_this);
288      });
289    },
290
291    addRegionRecursively_: function(region, addStatsToThisNode) {
292      if (addStatsToThisNode)
293        this.addStatsFromRegion_(region);
294
295      if (this.regions_ !== undefined) {
296        if (this.children_ !== undefined) {
297          throw new Error('Internal error: a VM region classification node ' +
298              'cannot have both regions and children');
299        }
300        // Leaf node or an intermediate node caching VM regions (add the
301        // region to this node and don't classify further).
302        this.regions_.push(region);
303        return;
304      }
305
306      // Non-leaf rule (classify region row further down the tree).
307      function regionRowMatchesChildNide(child) {
308        var fileRegExp = child.rule_.file;
309        if (fileRegExp === undefined)
310          return true;
311        return fileRegExp.test(region.mappedFile);
312      }
313
314      var matchedChild = tr.b.findFirstInArray(
315          this.children_, regionRowMatchesChildNide);
316      if (matchedChild === undefined) {
317        // Region belongs to the 'Other' node (created lazily).
318        if (this.children_.length !== this.rule_.children.length)
319          throw new Error('Internal error');
320        matchedChild = new VMRegionClassificationNode(
321            VMRegionClassificationNode.OTHER_RULE);
322        this.children_.push(matchedChild);
323      }
324
325      matchedChild.addRegionRecursively_(region, true);
326    },
327
328    buildTree_: function() {
329      var cachedRegions = this.regions_;
330      this.regions_ = undefined;
331
332      this.buildChildNodesRecursively_();
333      for (var i = 0; i < cachedRegions.length; i++) {
334        // Note that we don't add the VM region's stats to this node because
335        // they have already been added to it.
336        this.addRegionRecursively_(
337            cachedRegions[i], false /* addStatsToThisNode */);
338      }
339    },
340
341    buildChildNodesRecursively_: function() {
342      if (this.children_ !== undefined) {
343        throw new Error(
344            'Internal error: Classification node already has children');
345      }
346      if (this.regions_ !== undefined && this.regions_.length !== 0) {
347        throw new Error(
348            'Internal error: Classification node should have no regions');
349      }
350
351      if (this.isLeafNode)
352        return;  // Leaf node: Nothing to do.
353
354      // Intermediate node: Clear regions and build children recursively.
355      this.regions_ = undefined;
356      this.children_ = this.rule_.children.map(function(childRule) {
357        var child = new VMRegionClassificationNode(childRule);
358        child.buildChildNodesRecursively_();
359        return child;
360      });
361    },
362
363    addStatsFromRegion_: function(region) {
364      this.hasRegions = true;
365
366      // Aggregate virtual size.
367      var regionSizeInBytes = region.sizeInBytes;
368      if (regionSizeInBytes !== undefined)
369        this.sizeInBytes = (this.sizeInBytes || 0) + regionSizeInBytes;
370
371      // Aggregate byte stats.
372      var thisByteStats = this.byteStats;
373      var regionByteStats = region.byteStats;
374      for (var byteStatName in regionByteStats) {
375        var regionByteStatValue = regionByteStats[byteStatName];
376        if (regionByteStatValue === undefined)
377          continue;
378        thisByteStats[byteStatName] =
379            (thisByteStats[byteStatName] || 0) + regionByteStatValue;
380      }
381    }
382  };
383
384  return {
385    VMRegion: VMRegion,
386    VMRegionClassificationNode: VMRegionClassificationNode
387  };
388});
389</script>
390