global_memory_dump.html revision 46b43bff003ceda46cf9a5d40a47f7674996d2e0
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/units/time_stamp.html">
9<link rel="import" href="/tracing/model/attribute.html">
10<link rel="import" href="/tracing/model/container_memory_dump.html">
11<link rel="import" href="/tracing/model/memory_allocator_dump.html">
12
13<script>
14'use strict';
15
16/**
17 * @fileoverview Provides the GlobalMemoryDump class.
18 */
19tr.exportTo('tr.model', function() {
20  /**
21   * The GlobalMemoryDump represents a simultaneous memory dump of all
22   * processes.
23   * @constructor
24   */
25  function GlobalMemoryDump(model, start) {
26    tr.model.ContainerMemoryDump.call(this, start);
27    this.model = model;
28    this.processMemoryDumps = {};
29  }
30
31  var SIZE_ATTRIBUTE_NAME = 'size';
32  var EFFECTIVE_SIZE_ATTRIBUTE_NAME = 'effective_size';
33
34  function getSize(dump) {
35    var attr = dump.attributes[SIZE_ATTRIBUTE_NAME];
36    if (attr === undefined)
37      return 0;
38    return attr.value;
39  }
40
41  function hasSize(dump) {
42    return dump.attributes[SIZE_ATTRIBUTE_NAME] !== undefined;
43  }
44
45  function optional(value, defaultValue) {
46    if (value === undefined)
47      return defaultValue;
48    return value;
49  }
50
51  function ownershipToUserFriendlyString(dump, importance) {
52    return dump.quantifiedName + ' (importance: ' +
53        optional(importance, 0) + ')';
54  }
55
56  GlobalMemoryDump.prototype = {
57    __proto__: tr.model.ContainerMemoryDump.prototype,
58
59    get userFriendlyName() {
60      return 'Global memory dump at ' + tr.b.u.TimeStamp.format(this.start);
61    },
62
63    get containerName() {
64      return 'global space';
65    },
66
67    calculateGraphAttributes: function() {
68      // 1. Calculate the sizes of all memory allocator dumps (MADs).
69      this.calculateSizes();
70
71      // 2. Calculate the effective sizes of all MADs. This step requires that
72      // the sizes of all MADs have already been calculated (step 1).
73      this.calculateEffectiveSizes();
74
75      // 3. Aggregate all other attributes of all MADs. This step must be
76      // carried out after the sizes of all MADs were calculated (step 1).
77      // Otherwise, the sizes of all MADs would be aggregated as a direct sums
78      // of their children, which would most likely lead to double-counting.
79      this.aggregateAttributes();
80
81      // 4. Discount tracing from VM regions stats and malloc or winheap
82      // allocator stats. This steps requires that the sizes (step 1),
83      // effective sizes (step 2), and resident sizes (step 3) of the relevant
84      // MADs have already been calculated.
85      this.discountTracingOverhead();
86    },
87
88    /**
89     * Calculate the size of all memory allocator dumps in the dump graph.
90     *
91     * The size refers to the allocated size of a (sub)component. It is a
92     * natural extension of the optional size attribute provided by
93     * MemoryAllocatorDump(s):
94     *
95     *   - If a MAD provides a size attribute, then its size is assumed to be
96     *     equal to it.
97     *   - If a MAD does not provide a size attribute, then its size is assumed
98     *     to be the maximum of (1) the size of the largest owner of the MAD
99     *     and (2) the aggregated size of the MAD's children.
100     *
101     * Metric motivation: "How big is a (sub)system?"
102     *
103     * Please refer to the Memory Dump Graph Metric Calculation design document
104     * for more details (https://goo.gl/fKg0dt).
105     */
106    calculateSizes: function() {
107      this.traverseAllocatorDumpsInDepthFirstPostOrder(
108          this.calculateMemoryAllocatorDumpSize_.bind(this));
109    },
110
111    /**
112     * Calculate the size of the given MemoryAllocatorDump. This method assumes
113     * that the size of both the children and owners of the dump has already
114     * been calculated.
115     */
116    calculateMemoryAllocatorDumpSize_: function(dump) {
117      // This flag becomes true if the size attribute of the current dump
118      // should be defined, i.e. if (1) the current dump's size attribute is
119      // defined, (2) the size of at least one of its children is defined or
120      // (3) the size of at least one of its owners is defined.
121      var shouldDefineSize = false;
122
123      // This helper function returns the numeric value of the size attribute
124      // of the given dependent memory allocator dump. If the attribute is
125      // defined, the shouldDefineSize flag above is also set to true (because
126      // condition (2) or (3) is satisfied). Otherwise, zero is returned (and
127      // the flag is left unchanged).
128      function getDependencySize(dependencyDump) {
129        var attr = dependencyDump.attributes[SIZE_ATTRIBUTE_NAME];
130        if (attr === undefined)
131          return 0;
132        shouldDefineSize = true;
133        return attr.value;
134      }
135
136      // 1. Get the size provided by the dump. If present, define a function
137      // for checking dependent size consistency (a dump must always be bigger
138      // than all its children aggregated together and/or its largest owner).
139      var sizeAttribute = dump.getValidSizeAttributeOrUndefined(
140          SIZE_ATTRIBUTE_NAME, this.model);
141      var size = 0;
142      var infos = [];
143      var checkDependentSizeIsConsistent = function() { /* no-op */ };
144      if (sizeAttribute !== undefined) {
145        size = sizeAttribute.value;
146        shouldDefineSize = true;
147        checkDependentSizeIsConsistent = function(dependentSize,
148            dependentName) {
149          if (size >= dependentSize)
150            return;
151          var messageSuffix = ' (' + tr.b.u.Units.sizeInBytes.format(size) +
152              ') is less than ' + dependentName + ' (' +
153                tr.b.u.Units.sizeInBytes.format(dependentSize) + ').';
154          this.model.importWarning({
155            type: 'memory_dump_parse_error',
156            message: 'Size provided by memory allocator dump \'' +
157                dump.fullName + '\'' + messageSuffix
158          });
159          infos.push(new tr.model.AttributeInfo(
160              tr.model.AttributeInfoType.WARNING,
161              'Size provided by this memory allocator dump' + messageSuffix));
162        }.bind(this);
163      }
164
165      // 2. Aggregate size of children. The recursive function traverses all
166      // descendants and ensures that double-counting due to ownership within a
167      // subsystem is avoided.
168      var aggregatedChildrenSize = 0;
169      // Owned child dump name -> (Owner child dump name -> overlapping size).
170      var allOverlaps = {};
171      dump.children.forEach(function(childDump) {
172        function aggregateDescendantDump(descendantDump) {
173          // Don't count this descendant dump if it owns another descendant of
174          // the current dump (would cause double-counting).
175          var ownedDumpLink = descendantDump.owns;
176          if (ownedDumpLink !== undefined &&
177              ownedDumpLink.target.isDescendantOf(dump)) {
178            // If the target owned dump is a descendant of a *different* child
179            // of the the current dump (i.e. not childDump), then we remember
180            // the ownership so that we could explain why the size of the
181            // current dump is not equal to the sum of its children.
182            var ownedDescendantDump = ownedDumpLink.target;
183            var ownedChildDump = ownedDescendantDump;
184            while (ownedChildDump.parent !== dump)
185              ownedChildDump = ownedChildDump.parent;
186            if (childDump !== ownedChildDump) {
187              var overlap = getDependencySize(descendantDump);
188              if (overlap > 0) {
189                // Owner child dump -> total overlapping size.
190                var ownedChildOverlaps = allOverlaps[ownedChildDump.name];
191                if (ownedChildOverlaps === undefined)
192                  allOverlaps[ownedChildDump.name] = ownedChildOverlaps = {};
193                var previousTotalOverlap =
194                    ownedChildOverlaps[childDump.name] || 0;
195                var updatedTotalOverlap = previousTotalOverlap + overlap;
196                ownedChildOverlaps[childDump.name] = updatedTotalOverlap;
197              }
198            }
199            return;
200          }
201
202          // If this descendant dump is a leaf node, add its size to the
203          // aggregated size.
204          if (descendantDump.children.length === 0) {
205            aggregatedChildrenSize += getDependencySize(descendantDump);
206            return;
207          }
208
209          // If this descendant dump is an intermediate node, recurse down into
210          // its children. Note that the dump's size is NOT added because it is
211          // an aggregate of its children (would cause double-counting).
212          descendantDump.children.forEach(aggregateDescendantDump);
213        }
214        aggregateDescendantDump(childDump);
215      });
216      // If the size of the dump is not equal to the sum of its children, add
217      // infos to its children explaining the difference.
218      dump.children.forEach(function(childDump) {
219        var childOverlaps = allOverlaps[childDump.name];
220        if (childOverlaps === undefined)
221          return;
222
223        var message = tr.b.dictionaryValues(tr.b.mapItems(childOverlaps,
224            function(ownerChildName, overlap) {
225          return 'overlaps with its sibling \'' + ownerChildName + '\' (' +
226              tr.b.u.Units.sizeInBytes.format(overlap) + ')';
227        })).join(' ');
228
229        childDump.attributes[SIZE_ATTRIBUTE_NAME].infos.push(
230            new tr.model.AttributeInfo(
231                tr.model.AttributeInfoType.INFORMATION, message));
232      });
233      checkDependentSizeIsConsistent(
234          aggregatedChildrenSize, 'the aggregated size of its children');
235
236      // 3. Calculate the largest owner size.
237      var largestOwnerSize = 0;
238      dump.ownedBy.forEach(function(ownershipLink) {
239        var owner = ownershipLink.source;
240        var ownerSize = getDependencySize(owner);
241        largestOwnerSize = Math.max(largestOwnerSize, ownerSize);
242      });
243      checkDependentSizeIsConsistent(
244          largestOwnerSize, 'the size of its largest owner');
245
246      // If neither the dump nor any of its dependencies (children and owners)
247      // provide a size, do NOT add a zero size attribute.
248      if (!shouldDefineSize) {
249        // The rest of the pipeline relies on size being either a valid
250        // ScalarAttribute, or undefined.
251        dump.attributes[SIZE_ATTRIBUTE_NAME] = undefined;
252        return;
253      }
254
255      // A dump must always be bigger than all its children aggregated
256      // together and/or its largest owner.
257      size = Math.max(size, aggregatedChildrenSize, largestOwnerSize);
258
259      var sizeAttribute = new tr.model.ScalarAttribute('bytes', size);
260      sizeAttribute.infos = infos;
261      dump.attributes[SIZE_ATTRIBUTE_NAME] = sizeAttribute;
262
263      // Add a virtual child to make up for extra size of the dump with
264      // respect to its children (if applicable).
265      if (aggregatedChildrenSize < size &&
266          dump.children !== undefined && dump.children.length > 0) {
267        var virtualChild = new tr.model.MemoryAllocatorDump(
268            dump.containerMemoryDump, dump.fullName + '/<unspecified>');
269        virtualChild.parent = dump;
270        dump.children.unshift(virtualChild);
271        virtualChild.attributes[SIZE_ATTRIBUTE_NAME] =
272            new tr.model.ScalarAttribute(
273                'bytes', size - aggregatedChildrenSize);
274      }
275    },
276
277    /**
278     * Calculate the effective size of all memory allocator dumps in the dump
279     * graph.
280     *
281     * The effective size refers to the amount of memory a particular component
282     * is using/consuming. In other words, every (reported) byte of used memory
283     * is uniquely attributed to exactly one component. Consequently, unlike
284     * size, effective size is cumulative, i.e. the sum of the effective sizes
285     * of (top-level) components is equal to the total amount of (reported)
286     * used memory.
287     *
288     * Metric motivation: "How much memory does a (sub)system use?" or "For how
289     * much memory should a (sub)system be 'charged'?"
290     *
291     * Please refer to the Memory Dump Graph Metric Calculation design document
292     * for more details (https://goo.gl/fKg0dt).
293     *
294     * This method assumes that the size of all contained memory allocator
295     * dumps has already been calculated [see calculateSizes()].
296     */
297    calculateEffectiveSizes: function() {
298      // 1. Calculate not-owned and not-owning sub-sizes of all MADs
299      // (depth-first post-order traversal).
300      this.traverseAllocatorDumpsInDepthFirstPostOrder(
301          this.calculateDumpSubSizes_.bind(this));
302
303      // 2. Calculate owned and owning coefficients of owned and owner MADs
304      // respectively (arbitrary traversal).
305      this.traverseAllocatorDumpsInDepthFirstPostOrder(
306          this.calculateDumpOwnershipCoefficient_.bind(this));
307
308      // 3. Calculate cumulative owned and owning coefficients of all MADs
309      // (depth-first pre-order traversal).
310      this.traverseAllocatorDumpsInDepthFirstPreOrder(
311          this.calculateDumpCumulativeOwnershipCoefficient_.bind(this));
312
313      // 4. Calculate the effective sizes of all MADs (depth-first post-order
314      // traversal).
315      this.traverseAllocatorDumpsInDepthFirstPostOrder(
316          this.calculateDumpEffectiveSize_.bind(this));
317    },
318
319    /**
320     * Calculate not-owned and not-owning sub-sizes of a memory allocator dump
321     * from its children's (sub-)sizes.
322     *
323     * Not-owned sub-size refers to the aggregated memory of all children which
324     * is not owned by other MADs. Conversely, not-owning sub-size is the
325     * aggregated memory of all children which do not own another MAD. The
326     * diagram below illustrates these two concepts:
327     *
328     *     ROOT 1                         ROOT 2
329     *     size: 4                        size: 5
330     *     not-owned sub-size: 4          not-owned sub-size: 1 (!)
331     *     not-owning sub-size: 0 (!)     not-owning sub-size: 5
332     *
333     *      ^                              ^
334     *      |                              |
335     *
336     *     PARENT 1   ===== owns =====>   PARENT 2
337     *     size: 4                        size: 5
338     *     not-owned sub-size: 4          not-owned sub-size: 5
339     *     not-owning sub-size: 4         not-owning sub-size: 5
340     *
341     *      ^                              ^
342     *      |                              |
343     *
344     *     CHILD 1                        CHILD 2
345     *     size [given]: 4                size [given]: 5
346     *     not-owned sub-size: 4          not-owned sub-size: 5
347     *     not-owning sub-size: 4         not-owning sub-size: 5
348     *
349     * This method assumes that (1) the size of the dump, its children, and its
350     * owners [see calculateSizes()] and (2) the not-owned and not-owning
351     * sub-sizes of both the children and owners of the dump have already been
352     * calculated [depth-first post-order traversal].
353     */
354    calculateDumpSubSizes_: function(dump) {
355      // Completely skip dumps with undefined size.
356      if (!hasSize(dump))
357        return;
358
359      // If the dump is a leaf node, then both sub-sizes are equal to the size.
360      if (dump.children === undefined || dump.children.length === 0) {
361        var size = getSize(dump);
362        dump.notOwningSubSize_ = size;
363        dump.notOwnedSubSize_ = size;
364        return;
365      }
366
367      // Calculate this dump's not-owning sub-size by summing up the not-owning
368      // sub-sizes of children MADs which do not own another MAD.
369      var notOwningSubSize = 0;
370      dump.children.forEach(function(childDump) {
371        if (childDump.owns !== undefined)
372          return;
373        notOwningSubSize += optional(childDump.notOwningSubSize_, 0);
374      });
375      dump.notOwningSubSize_ = notOwningSubSize;
376
377      // Calculate this dump's not-owned sub-size.
378      var notOwnedSubSize = 0;
379      dump.children.forEach(function(childDump) {
380        // If the child dump is not owned, then add its not-owned sub-size.
381        if (childDump.ownedBy.length === 0) {
382          notOwnedSubSize += optional(childDump.notOwnedSubSize_, 0);
383          return;
384        }
385        // If the child dump is owned, then add the difference between its size
386        // and the largest owner.
387        var largestChildOwnerSize = 0;
388        childDump.ownedBy.forEach(function(ownershipLink) {
389          largestChildOwnerSize = Math.max(
390              largestChildOwnerSize, getSize(ownershipLink.source));
391        });
392        notOwnedSubSize += getSize(childDump) - largestChildOwnerSize;
393      });
394      dump.notOwnedSubSize_ = notOwnedSubSize;
395    },
396
397    /**
398     * Calculate owned and owning coefficients of a memory allocator dump and
399     * its owners.
400     *
401     * The owning coefficient refers to the proportion of a dump's not-owning
402     * sub-size which is attributed to the dump (only relevant to owning MADs).
403     * Conversely, the owned coefficient is the proportion of a dump's
404     * not-owned sub-size, which is attributed to it (only relevant to owned
405     * MADs).
406     *
407     * The not-owned size of the owned dump is split among its owners in the
408     * order of the ownership importance as demonstrated by the following
409     * example:
410     *
411     *                                          memory allocator dumps
412     *                                   OWNED  OWNER1  OWNER2  OWNER3  OWNER4
413     *       not-owned sub-size [given]     10       -       -       -       -
414     *      not-owning sub-size [given]      -       6       7       5       8
415     *               importance [given]      -       2       2       1       0
416     *    attributed not-owned sub-size      2       -       -       -       -
417     *   attributed not-owning sub-size      -       3       4       0       1
418     *                owned coefficient   2/10       -       -       -       -
419     *               owning coefficient      -     3/6     4/7     0/5     1/8
420     *
421     * Explanation: Firstly, 6 bytes are split equally among OWNER1 and OWNER2
422     * (highest importance). OWNER2 owns one more byte, so its attributed
423     * not-owning sub-size is 6/2 + 1 = 4 bytes. OWNER3 is attributed no size
424     * because it is smaller than the owners with higher priority. However,
425     * OWNER4 is larger, so it's attributed the difference 8 - 7 = 1 byte.
426     * Finally, 2 bytes remain unattributed and are hence kept in the OWNED
427     * dump as attributed not-owned sub-size. The coefficients are then
428     * directly calculated as fractions of the sub-sizes and corresponding
429     * attributed sub-sizes.
430     *
431     * Note that we always assume that all ownerships of a dump overlap (e.g.
432     * OWNER3 is subsumed by both OWNER1 and OWNER2). Hence, the table could
433     * be alternatively represented as follows:
434     *
435     *                                 owned memory range
436     *              0   1   2    3    4    5    6        7        8   9  10
437     *   Priority 2 |  OWNER1 + OWNER2 (split)  | OWNER2 |
438     *   Priority 1 | (already attributed) |
439     *   Priority 0 | - - -  (already attributed)  - - - | OWNER4 |
440     *    Remainder | - - - - - (already attributed) - - - - - -  | OWNED |
441     *
442     * This method assumes that (1) the size of the dump [see calculateSizes()]
443     * and (2) the not-owned size of the dump and not-owning sub-sizes of its
444     * owners [see the first step of calculateEffectiveSizes()] have already
445     * been calculated. Note that the method doesn't make any assumptions about
446     * the order in which dumps are visited.
447     */
448    calculateDumpOwnershipCoefficient_: function(dump) {
449      // Completely skip dumps with undefined size.
450      if (!hasSize(dump))
451        return;
452
453      // We only need to consider owned dumps.
454      if (dump.ownedBy.length === 0)
455        return;
456
457      // Sort the owners in decreasing order of ownership importance and
458      // increasing order of not-owning sub-size (in case of equal importance).
459      var owners = dump.ownedBy.map(function(ownershipLink) {
460        return {
461          dump: ownershipLink.source,
462          importance: optional(ownershipLink.importance, 0),
463          notOwningSubSize: optional(ownershipLink.source.notOwningSubSize_, 0)
464        };
465      });
466      owners.sort(function(a, b) {
467        if (a.importance === b.importance)
468          return a.notOwningSubSize - b.notOwningSubSize;
469        return b.importance - a.importance;
470      });
471
472      // Loop over the list of owners and distribute the owned dump's not-owned
473      // sub-size among them according to their ownership importance and
474      // not-owning sub-size.
475      var currentImportanceStartPos = 0;
476      var alreadyAttributedSubSize = 0;
477      while (currentImportanceStartPos < owners.length) {
478        var currentImportance = owners[currentImportanceStartPos].importance;
479
480        // Find the position of the first owner with lower priority.
481        var nextImportanceStartPos = currentImportanceStartPos + 1;
482        while (nextImportanceStartPos < owners.length &&
483               owners[nextImportanceStartPos].importance ===
484                  currentImportance) {
485          nextImportanceStartPos++;
486        }
487
488        // Visit the owners with the same importance in increasing order of
489        // not-owned sub-size, split the owned memory among them appropriately,
490        // and calculate their owning coefficients.
491        var attributedNotOwningSubSize = 0;
492        for (var pos = currentImportanceStartPos; pos < nextImportanceStartPos;
493             pos++) {
494          var owner = owners[pos];
495          var notOwningSubSize = owner.notOwningSubSize;
496          if (notOwningSubSize > alreadyAttributedSubSize) {
497            attributedNotOwningSubSize +=
498                (notOwningSubSize - alreadyAttributedSubSize) /
499                (nextImportanceStartPos - pos);
500            alreadyAttributedSubSize = notOwningSubSize;
501          }
502
503          var owningCoefficient = 0;
504          if (notOwningSubSize !== 0)
505            owningCoefficient = attributedNotOwningSubSize / notOwningSubSize;
506          owner.dump.owningCoefficient_ = owningCoefficient;
507        }
508
509        currentImportanceStartPos = nextImportanceStartPos;
510      }
511
512      // Attribute the remainder of the owned dump's not-owned sub-size to
513      // the dump itself and calculate its owned coefficient.
514      var notOwnedSubSize = optional(dump.notOwnedSubSize_, 0);
515      var remainderSubSize = notOwnedSubSize - alreadyAttributedSubSize;
516      var ownedCoefficient = 0;
517      if (notOwnedSubSize !== 0)
518        ownedCoefficient = remainderSubSize / notOwnedSubSize;
519      dump.ownedCoefficient_ = ownedCoefficient;
520    },
521
522    /**
523     * Calculate cumulative owned and owning coefficients of a memory allocator
524     * dump from its (non-cumulative) owned and owning coefficients and the
525     * cumulative coefficients of its parent and/or owned dump.
526     *
527     * The cumulative coefficients represent the total effect of all
528     * (non-strict) ancestor ownerships on a memory allocator dump. The
529     * cumulative owned coefficient of a MAD can be calculated simply as:
530     *
531     *   cumulativeOwnedC(M) = ownedC(M) * cumulativeOwnedC(parent(M))
532     *
533     * This reflects the assumption that if a parent of a child MAD is
534     * (partially) owned, then the parent's owner also indirectly owns (a part
535     * of) the child MAD.
536     *
537     * The cumulative owning coefficient of a MAD depends on whether the MAD
538     * owns another dump:
539     *
540     *                           [if M doesn't own another MAD]
541     *                         / cumulativeOwningC(parent(M))
542     *   cumulativeOwningC(M) =
543     *                         \ [if M owns another MAD]
544     *                           owningC(M) * cumulativeOwningC(owned(M))
545     *
546     * The reasoning behind the first case is similar to the one for cumulative
547     * owned coefficient above. The only difference is that we don't need to
548     * include the dump's (non-cumulative) owning coefficient because it is
549     * implicitly 1.
550     *
551     * The formula for the second case is derived as follows: Since the MAD
552     * owns another dump, its memory is not included in its parent's not-owning
553     * sub-size and hence shouldn't be affected by the parent's corresponding
554     * cumulative coefficient. Instead, the MAD indirectly owns everything
555     * owned by its owned dump (and so it should be affected by the
556     * corresponding coefficient).
557     *
558     * Note that undefined coefficients (and coefficients of non-existent
559     * dumps) are implicitly assumed to be 1.
560     *
561     * This method assumes that (1) the size of the dump [see calculateSizes()],
562     * (2) the (non-cumulative) owned and owning coefficients of the dump [see
563     * the second step of calculateEffectiveSizes()], and (3) the cumulative
564     * coefficients of the dump's parent and owned MADs (if present)
565     * [depth-first pre-order traversal] have already been calculated.
566     */
567    calculateDumpCumulativeOwnershipCoefficient_: function(dump) {
568      // Completely skip dumps with undefined size.
569      if (!hasSize(dump))
570        return;
571
572      var cumulativeOwnedCoefficient = optional(dump.ownedCoefficient_, 1);
573      var parent = dump.parent;
574      if (dump.parent !== undefined)
575        cumulativeOwnedCoefficient *= dump.parent.cumulativeOwnedCoefficient_;
576      dump.cumulativeOwnedCoefficient_ = cumulativeOwnedCoefficient;
577
578      var cumulativeOwningCoefficient;
579      if (dump.owns !== undefined) {
580        cumulativeOwningCoefficient = dump.owningCoefficient_ *
581            dump.owns.target.cumulativeOwningCoefficient_;
582      } else if (dump.parent !== undefined) {
583        cumulativeOwningCoefficient = dump.parent.cumulativeOwningCoefficient_;
584      } else {
585        cumulativeOwningCoefficient = 1;
586      }
587      dump.cumulativeOwningCoefficient_ = cumulativeOwningCoefficient;
588    },
589
590    /**
591     * Calculate the effective size of a memory allocator dump.
592     *
593     * In order to simplify the (already complex) calculation, we use the fact
594     * that effective size is cumulative (unlike regular size), i.e. the
595     * effective size of a non-leaf node is equal to the sum of effective sizes
596     * of its children. The effective size of a leaf MAD is calculated as:
597     *
598     *   effectiveSize(M) = size(M) * cumulativeOwningC(M) * cumulativeOwnedC(M)
599     *
600     * This method assumes that (1) the size of the dump and its children [see
601     * calculateSizes()] and (2) the cumulative owning and owned coefficients
602     * of the dump (if it's a leaf node) [see the third step of
603     * calculateEffectiveSizes()] or the effective sizes of its children (if
604     * it's a non-leaf node) [depth-first post-order traversal] have already
605     * been calculated.
606     */
607    calculateDumpEffectiveSize_: function(dump) {
608      // Completely skip dumps with undefined size. As a result, each dump will
609      // have defined effective size if and only if it has defined size.
610      if (!hasSize(dump)) {
611        // The rest of the pipeline relies on effective size being either a
612        // valid ScalarAttribute, or undefined.
613        dump.attributes[EFFECTIVE_SIZE_ATTRIBUTE_NAME] = undefined;
614        return;
615      }
616
617      var effectiveSize;
618      if (dump.children === undefined || dump.children.length === 0) {
619        // Leaf dump.
620        effectiveSize = getSize(dump) * dump.cumulativeOwningCoefficient_ *
621            dump.cumulativeOwnedCoefficient_;
622      } else {
623        // Non-leaf dump.
624        effectiveSize = 0;
625        dump.children.forEach(function(childDump) {
626          if (!hasSize(childDump))
627            return;
628          effectiveSize +=
629              childDump.attributes[EFFECTIVE_SIZE_ATTRIBUTE_NAME].value;
630        });
631      }
632      var attribute = new tr.model.ScalarAttribute('bytes', effectiveSize);
633      dump.attributes[EFFECTIVE_SIZE_ATTRIBUTE_NAME] = attribute;
634
635      // Add attribute infos regarding ownership (if applicable).
636      // TODO(petrcermak): This belongs to the corresponding analysis UI code.
637      if (dump.ownedBy.length > 0) {
638        var message = 'shared by:' +
639            dump.ownedBy.map(function(ownershipLink) {
640              return '\n  - ' + ownershipToUserFriendlyString(
641                  ownershipLink.source, ownershipLink.importance);
642            }).join();
643        attribute.infos.push(new tr.model.AttributeInfo(
644            tr.model.AttributeInfoType.MEMORY_OWNED, message));
645      }
646      if (dump.owns !== undefined) {
647        var target = dump.owns.target;
648        var message = 'shares ' +
649            ownershipToUserFriendlyString(target, dump.owns.importance) +
650            ' with';
651
652        var otherOwnershipLinks = target.ownedBy.filter(
653            function(ownershipLink) {
654          return ownershipLink.source !== dump;
655        });
656        if (otherOwnershipLinks.length > 0) {
657          message += ':';
658          message += otherOwnershipLinks.map(function(ownershipLink) {
659            return '\n  - ' + ownershipToUserFriendlyString(
660                ownershipLink.source, ownershipLink.importance);
661          }).join();
662        } else {
663          message += ' no other dumps';
664        }
665
666        attribute.infos.push(new tr.model.AttributeInfo(
667            tr.model.AttributeInfoType.MEMORY_OWNER, message));
668      }
669    },
670
671    aggregateAttributes: function() {
672      this.iterateRootAllocatorDumps(function(dump) {
673        dump.aggregateAttributes(this.model);
674      });
675    },
676
677    discountTracingOverhead: function() {
678      // TODO(petrcermak): Consider factoring out all the finalization code and
679      // constants to a single file.
680      tr.b.iterItems(this.processMemoryDumps, function(pid, dump) {
681        dump.discountTracingOverhead(this.model);
682      }, this);
683    },
684
685    iterateContainerDumps: function(fn) {
686      fn.call(this, this);
687      tr.b.iterItems(this.processMemoryDumps, function(pid, processDump) {
688        fn.call(this, processDump);
689      }, this);
690    },
691
692    iterateRootAllocatorDumps: function(fn) {
693      this.iterateContainerDumps(function(containerDump) {
694        var memoryAllocatorDumps = containerDump.memoryAllocatorDumps;
695        if (memoryAllocatorDumps === undefined)
696          return;
697        memoryAllocatorDumps.forEach(fn, this);
698      });
699    },
700
701    /**
702     * Traverse the memory dump graph in a depth first post-order, i.e.
703     * children and owners of a memory allocator dump are visited before the
704     * dump itself. This method will throw an exception if the graph contains
705     * a cycle.
706     */
707    traverseAllocatorDumpsInDepthFirstPostOrder: function(fn) {
708      var visitedDumps = new WeakSet();
709      var openDumps = new WeakSet();
710
711      function visit(dump) {
712        if (visitedDumps.has(dump))
713          return;
714
715        if (openDumps.has(dump))
716          throw new Error(dump.userFriendlyName + ' contains a cycle');
717        openDumps.add(dump);
718
719        // Visit owners before the dumps they own.
720        dump.ownedBy.forEach(function(ownershipLink) {
721          visit.call(this, ownershipLink.source);
722        }, this);
723
724        // Visit children before parents.
725        dump.children.forEach(visit, this);
726
727        // Actually visit the current memory allocator dump.
728        fn.call(this, dump);
729        visitedDumps.add(dump);
730
731        openDumps.delete(dump);
732      }
733
734      this.iterateRootAllocatorDumps(visit);
735    },
736
737    /**
738     * Traverse the memory dump graph in a depth first pre-order, i.e.
739     * children and owners of a memory allocator dump are visited after the
740     * dump itself. This method will not visit some dumps if the graph contains
741     * a cycle.
742     */
743    traverseAllocatorDumpsInDepthFirstPreOrder: function(fn) {
744      var visitedDumps = new WeakSet();
745
746      function visit(dump) {
747        if (visitedDumps.has(dump))
748          return;
749
750        // If this dumps owns another dump which hasn't been visited yet, then
751        // wait for this dump to be visited later.
752        if (dump.owns !== undefined && !visitedDumps.has(dump.owns.target))
753          return;
754
755        // If this dump's parent hasn't been visited yet, then wait for this
756        // dump to be visited later.
757        if (dump.parent !== undefined && !visitedDumps.has(dump.parent))
758          return;
759
760        // Actually visit the current memory allocator dump.
761        fn.call(this, dump);
762        visitedDumps.add(dump);
763
764        // Visit owners after the dumps they own.
765        dump.ownedBy.forEach(function(ownershipLink) {
766          visit.call(this, ownershipLink.source);
767        }, this);
768
769        // Visit children after parents.
770        dump.children.forEach(visit, this);
771      }
772
773      this.iterateRootAllocatorDumps(visit);
774    }
775  };
776
777  tr.model.EventRegistry.register(
778      GlobalMemoryDump,
779      {
780        name: 'globalMemoryDump',
781        pluralName: 'globalMemoryDumps',
782        singleViewElementName: 'tr-ui-a-single-global-memory-dump-sub-view',
783        multiViewElementName: 'tr-ui-a-multi-global-memory-dump-sub-view'
784      });
785
786  return {
787    GlobalMemoryDump: GlobalMemoryDump
788  };
789});
790</script>
791