"use strict";
/**
Copyright (c) 2015 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
**/

require("../base/iteration_helpers.js");
require("../base/unit.js");
require("./container_memory_dump.js");
require("./event_registry.js");
require("./memory_allocator_dump.js");
require("../value/numeric.js");

'use strict';

/**
 * @fileoverview Provides the GlobalMemoryDump class.
 */
global.tr.exportTo('tr.model', function () {
  /**
   * The GlobalMemoryDump represents a simultaneous memory dump of all
   * processes.
   * @constructor
   */
  function GlobalMemoryDump(model, start) {
    tr.model.ContainerMemoryDump.call(this, start);
    this.model = model;
    this.processMemoryDumps = {};
  }

  // Size numeric names.
  var SIZE_NUMERIC_NAME = tr.model.MemoryAllocatorDump.SIZE_NUMERIC_NAME;
  var EFFECTIVE_SIZE_NUMERIC_NAME = tr.model.MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME;

  // Size numeric info types.
  var MemoryAllocatorDumpInfoType = tr.model.MemoryAllocatorDumpInfoType;
  var PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN = MemoryAllocatorDumpInfoType.PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN;
  var PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER = MemoryAllocatorDumpInfoType.PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER;

  // TODO(petrcermak): Move this to tracing/base/iteration_helpers.html.
  function inPlaceFilter(array, predicate, opt_this) {
    opt_this = opt_this || this;
    var nextPosition = 0;
    for (var i = 0; i < array.length; i++) {
      if (!predicate.call(opt_this, array[i], i)) continue;
      if (nextPosition < i) array[nextPosition] = array[i]; // Move elements only if necessary.
      nextPosition++;
    }

    if (nextPosition < array.length) array.length = nextPosition; // Truncate the array only if necessary.
  }

  function getSize(dump) {
    var numeric = dump.numerics[SIZE_NUMERIC_NAME];
    if (numeric === undefined) return 0;
    return numeric.value;
  }

  function hasSize(dump) {
    return dump.numerics[SIZE_NUMERIC_NAME] !== undefined;
  }

  function optional(value, defaultValue) {
    if (value === undefined) return defaultValue;
    return value;
  }

  GlobalMemoryDump.prototype = {
    __proto__: tr.model.ContainerMemoryDump.prototype,

    get userFriendlyName() {
      return 'Global memory dump at ' + tr.b.Unit.byName.timeStampInMs.format(this.start);
    },

    get containerName() {
      return 'global space';
    },

    finalizeGraph: function () {
      // 1. Transitively remove weak memory allocator dumps and all their
      // owners and descendants from the model. This must be performed before
      // any other steps.
      this.removeWeakDumps();

      // 2. Add ownership links from tracing MADs to descendants of malloc or
      // winheap MADs so that tracing would be automatically discounted from
      // them later (step 3).
      this.setUpTracingOverheadOwnership();

      // 3. Aggregate all other numerics of all MADs (*excluding* sizes and
      // effective sizes) and propagate numerics from global MADs to their
      // owners (*including* sizes and effective sizes). This step must be
      // carried out before the sizes of all MADs are calculated (step 3).
      // Otherwise, the propagated sizes of all MADs would not be aggregated.
      this.aggregateNumerics();

      // 4. Calculate the sizes of all memory allocator dumps (MADs). This step
      // requires that the memory allocator dump graph has been finalized (step
      // 1) and numerics were propagated from global MADs (step 2). Subsequent
      // modifications of the graph will most likely break the calculation
      // invariants.
      this.calculateSizes();

      // 5. Calculate the effective sizes of all MADs. This step requires that
      // the sizes of all MADs have already been calculated (step 3).
      this.calculateEffectiveSizes();

      // 6. Discount tracing from VM regions stats. This steps requires that
      // resident sizes (step 2) and sizes (step 3) of the tracing MADs have
      // already been calculated.
      this.discountTracingOverheadFromVmRegions();

      // 7. The above steps (especially steps 1 and 3) can create new memory
      // allocator dumps, so we force rebuilding the memory allocator dump
      // indices of all container memory dumps.
      this.forceRebuildingMemoryAllocatorDumpByFullNameIndices();
    },

    removeWeakDumps: function () {
      // Mark all transitive owners and children of weak memory allocator dumps
      // as weak.
      this.traverseAllocatorDumpsInDepthFirstPreOrder(function (dump) {
        if (dump.weak) return;
        if (dump.owns !== undefined && dump.owns.target.weak || dump.parent !== undefined && dump.parent.weak) {
          dump.weak = true;
        }
      });

      function removeWeakDumpsFromListRecursively(dumps) {
        inPlaceFilter(dumps, function (dump) {
          if (dump.weak) {
            // The dump is weak, so remove it. This will implicitly remove all
            // its descendants, which are also weak due to the initial marking
            // step.
            return false;
          }

          // This dump is non-weak, so keep it. Recursively remove its weak
          // descendants and ownership links from weak dumps instead.
          removeWeakDumpsFromListRecursively(dump.children);
          inPlaceFilter(dump.ownedBy, function (ownershipLink) {
            return !ownershipLink.source.weak;
          });

          return true;
        });
      }

      this.iterateContainerDumps(function (containerDump) {
        var memoryAllocatorDumps = containerDump.memoryAllocatorDumps;
        if (memoryAllocatorDumps !== undefined) removeWeakDumpsFromListRecursively(memoryAllocatorDumps);
      });
    },

    /**
     * Calculate the size of all memory allocator dumps in the dump graph.
     *
     * The size refers to the allocated size of a (sub)component. It is a
     * natural extension of the optional size numeric provided by
     * MemoryAllocatorDump(s):
     *
     *   - If a MAD provides a size numeric, then its size is assumed to be
     *     equal to it.
     *   - If a MAD does not provide a size numeric, then its size is assumed
     *     to be the maximum of (1) the size of the largest owner of the MAD
     *     and (2) the aggregated size of the MAD's children.
     *
     * Metric motivation: "How big is a (sub)system?"
     *
     * Please refer to the Memory Dump Graph Metric Calculation design document
     * for more details (https://goo.gl/fKg0dt).
     */
    calculateSizes: function () {
      this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateMemoryAllocatorDumpSize_.bind(this));
    },

    /**
     * Calculate the size of the given MemoryAllocatorDump. This method assumes
     * that the size of both the children and owners of the dump has already
     * been calculated.
     */
    calculateMemoryAllocatorDumpSize_: function (dump) {
      // This flag becomes true if the size numeric of the current dump should
      // be defined, i.e. if (1) the current dump's size numeric is defined,
      // (2) the size of at least one of its children is defined or (3) the
      // size of at least one of its owners is defined.
      var shouldDefineSize = false;

      // This helper function returns the value of the size numeric of the
      // given dependent memory allocator dump. If the numeric is defined, the
      // shouldDefineSize flag above is also set to true (because condition
      // (2) or (3) is satisfied). Otherwise, zero is returned (and the flag is
      // left unchanged).
      function getDependencySize(dependencyDump) {
        var numeric = dependencyDump.numerics[SIZE_NUMERIC_NAME];
        if (numeric === undefined) return 0;
        shouldDefineSize = true;
        return numeric.value;
      }

      // 1. Get the size provided by the dump. If present, define a function
      // for checking dependent size consistency (a dump must always be bigger
      // than all its children aggregated together and/or its largest owner).
      var sizeNumeric = dump.numerics[SIZE_NUMERIC_NAME];
      var size = 0;
      var checkDependencySizeIsConsistent = function () {/* no-op */};
      if (sizeNumeric !== undefined) {
        size = sizeNumeric.value;
        shouldDefineSize = true;
        if (sizeNumeric.unit !== tr.b.Unit.byName.sizeInBytes_smallerIsBetter) {
          this.model.importWarning({
            type: 'memory_dump_parse_error',
            message: 'Invalid unit of \'size\' numeric of memory allocator ' + 'dump ' + dump.quantifiedName + ': ' + sizeNumeric.unit.unitName + '.'
          });
        }
        checkDependencySizeIsConsistent = function (dependencySize, dependencyInfoType, dependencyName) {
          if (size >= dependencySize) return;
          this.model.importWarning({
            type: 'memory_dump_parse_error',
            message: 'Size provided by memory allocator dump \'' + dump.fullName + '\'' + tr.b.Unit.byName.sizeInBytes.format(size) + ') is less than ' + dependencyName + ' (' + tr.b.Unit.byName.sizeInBytes.format(dependencySize) + ').'
          });
          dump.infos.push({
            type: dependencyInfoType,
            providedSize: size,
            dependencySize: dependencySize
          });
        }.bind(this);
      }

      // 2. Aggregate size of children. The recursive function traverses all
      // descendants and ensures that double-counting due to ownership within a
      // subsystem is avoided.
      var aggregatedChildrenSize = 0;
      // Owned child dump name -> (Owner child dump name -> overlapping size).
      var allOverlaps = {};
      dump.children.forEach(function (childDump) {
        function aggregateDescendantDump(descendantDump) {
          // Don't count this descendant dump if it owns another descendant of
          // the current dump (would cause double-counting).
          var ownedDumpLink = descendantDump.owns;
          if (ownedDumpLink !== undefined && ownedDumpLink.target.isDescendantOf(dump)) {
            // If the target owned dump is a descendant of a *different* child
            // of the the current dump (i.e. not childDump), then we remember
            // the ownership so that we could explain why the size of the
            // current dump is not equal to the sum of its children.
            var ownedChildDump = ownedDumpLink.target;
            while (ownedChildDump.parent !== dump) ownedChildDump = ownedChildDump.parent;
            if (childDump !== ownedChildDump) {
              var ownedBySiblingSize = getDependencySize(descendantDump);
              if (ownedBySiblingSize > 0) {
                var previousTotalOwnedBySiblingSize = ownedChildDump.ownedBySiblingSizes.get(childDump) || 0;
                var updatedTotalOwnedBySiblingSize = previousTotalOwnedBySiblingSize + ownedBySiblingSize;
                ownedChildDump.ownedBySiblingSizes.set(childDump, updatedTotalOwnedBySiblingSize);
              }
            }
            return;
          }

          // If this descendant dump is a leaf node, add its size to the
          // aggregated size.
          if (descendantDump.children.length === 0) {
            aggregatedChildrenSize += getDependencySize(descendantDump);
            return;
          }

          // If this descendant dump is an intermediate node, recurse down into
          // its children. Note that the dump's size is NOT added because it is
          // an aggregate of its children (would cause double-counting).
          descendantDump.children.forEach(aggregateDescendantDump);
        }
        aggregateDescendantDump(childDump);
      });
      checkDependencySizeIsConsistent(aggregatedChildrenSize, PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN, 'the aggregated size of its children');

      // 3. Calculate the largest owner size.
      var largestOwnerSize = 0;
      dump.ownedBy.forEach(function (ownershipLink) {
        var owner = ownershipLink.source;
        var ownerSize = getDependencySize(owner);
        largestOwnerSize = Math.max(largestOwnerSize, ownerSize);
      });
      checkDependencySizeIsConsistent(largestOwnerSize, PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER, 'the size of its largest owner');

      // If neither the dump nor any of its dependencies (children and owners)
      // provide a size, do NOT add a zero size numeric.
      if (!shouldDefineSize) {
        // The rest of the pipeline relies on size being either a valid
        // ScalarNumeric, or undefined.
        delete dump.numerics[SIZE_NUMERIC_NAME];
        return;
      }

      // A dump must always be bigger than all its children aggregated
      // together and/or its largest owner.
      size = Math.max(size, aggregatedChildrenSize, largestOwnerSize);

      dump.numerics[SIZE_NUMERIC_NAME] = new tr.v.ScalarNumeric(tr.b.Unit.byName.sizeInBytes_smallerIsBetter, size);

      // Add a virtual child to make up for extra size of the dump with
      // respect to its children (if applicable).
      if (aggregatedChildrenSize < size && dump.children !== undefined && dump.children.length > 0) {
        var virtualChild = new tr.model.MemoryAllocatorDump(dump.containerMemoryDump, dump.fullName + '/<unspecified>');
        virtualChild.parent = dump;
        dump.children.unshift(virtualChild);
        virtualChild.numerics[SIZE_NUMERIC_NAME] = new tr.v.ScalarNumeric(tr.b.Unit.byName.sizeInBytes_smallerIsBetter, size - aggregatedChildrenSize);
      }
    },

    /**
     * Calculate the effective size of all memory allocator dumps in the dump
     * graph.
     *
     * The effective size refers to the amount of memory a particular component
     * is using/consuming. In other words, every (reported) byte of used memory
     * is uniquely attributed to exactly one component. Consequently, unlike
     * size, effective size is cumulative, i.e. the sum of the effective sizes
     * of (top-level) components is equal to the total amount of (reported)
     * used memory.
     *
     * Metric motivation: "How much memory does a (sub)system use?" or "For how
     * much memory should a (sub)system be 'charged'?"
     *
     * Please refer to the Memory Dump Graph Metric Calculation design document
     * for more details (https://goo.gl/fKg0dt).
     *
     * This method assumes that the size of all contained memory allocator
     * dumps has already been calculated [see calculateSizes()].
     */
    calculateEffectiveSizes: function () {
      // 1. Calculate not-owned and not-owning sub-sizes of all MADs
      // (depth-first post-order traversal).
      this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateDumpSubSizes_.bind(this));

      // 2. Calculate owned and owning coefficients of owned and owner MADs
      // respectively (arbitrary traversal).
      this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateDumpOwnershipCoefficient_.bind(this));

      // 3. Calculate cumulative owned and owning coefficients of all MADs
      // (depth-first pre-order traversal).
      this.traverseAllocatorDumpsInDepthFirstPreOrder(this.calculateDumpCumulativeOwnershipCoefficient_.bind(this));

      // 4. Calculate the effective sizes of all MADs (depth-first post-order
      // traversal).
      this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateDumpEffectiveSize_.bind(this));
    },

    /**
     * Calculate not-owned and not-owning sub-sizes of a memory allocator dump
     * from its children's (sub-)sizes.
     *
     * Not-owned sub-size refers to the aggregated memory of all children which
     * is not owned by other MADs. Conversely, not-owning sub-size is the
     * aggregated memory of all children which do not own another MAD. The
     * diagram below illustrates these two concepts:
     *
     *     ROOT 1                         ROOT 2
     *     size: 4                        size: 5
     *     not-owned sub-size: 4          not-owned sub-size: 1 (!)
     *     not-owning sub-size: 0 (!)     not-owning sub-size: 5
     *
     *      ^                              ^
     *      |                              |
     *
     *     PARENT 1   ===== owns =====>   PARENT 2
     *     size: 4                        size: 5
     *     not-owned sub-size: 4          not-owned sub-size: 5
     *     not-owning sub-size: 4         not-owning sub-size: 5
     *
     *      ^                              ^
     *      |                              |
     *
     *     CHILD 1                        CHILD 2
     *     size [given]: 4                size [given]: 5
     *     not-owned sub-size: 4          not-owned sub-size: 5
     *     not-owning sub-size: 4         not-owning sub-size: 5
     *
     * This method assumes that (1) the size of the dump, its children, and its
     * owners [see calculateSizes()] and (2) the not-owned and not-owning
     * sub-sizes of both the children and owners of the dump have already been
     * calculated [depth-first post-order traversal].
     */
    calculateDumpSubSizes_: function (dump) {
      // Completely skip dumps with undefined size.
      if (!hasSize(dump)) return;

      // If the dump is a leaf node, then both sub-sizes are equal to the size.
      if (dump.children === undefined || dump.children.length === 0) {
        var size = getSize(dump);
        dump.notOwningSubSize_ = size;
        dump.notOwnedSubSize_ = size;
        return;
      }

      // Calculate this dump's not-owning sub-size by summing up the not-owning
      // sub-sizes of children MADs which do not own another MAD.
      var notOwningSubSize = 0;
      dump.children.forEach(function (childDump) {
        if (childDump.owns !== undefined) return;
        notOwningSubSize += optional(childDump.notOwningSubSize_, 0);
      });
      dump.notOwningSubSize_ = notOwningSubSize;

      // Calculate this dump's not-owned sub-size.
      var notOwnedSubSize = 0;
      dump.children.forEach(function (childDump) {
        // If the child dump is not owned, then add its not-owned sub-size.
        if (childDump.ownedBy.length === 0) {
          notOwnedSubSize += optional(childDump.notOwnedSubSize_, 0);
          return;
        }
        // If the child dump is owned, then add the difference between its size
        // and the largest owner.
        var largestChildOwnerSize = 0;
        childDump.ownedBy.forEach(function (ownershipLink) {
          largestChildOwnerSize = Math.max(largestChildOwnerSize, getSize(ownershipLink.source));
        });
        notOwnedSubSize += getSize(childDump) - largestChildOwnerSize;
      });
      dump.notOwnedSubSize_ = notOwnedSubSize;
    },

    /**
     * Calculate owned and owning coefficients of a memory allocator dump and
     * its owners.
     *
     * The owning coefficient refers to the proportion of a dump's not-owning
     * sub-size which is attributed to the dump (only relevant to owning MADs).
     * Conversely, the owned coefficient is the proportion of a dump's
     * not-owned sub-size, which is attributed to it (only relevant to owned
     * MADs).
     *
     * The not-owned size of the owned dump is split among its owners in the
     * order of the ownership importance as demonstrated by the following
     * example:
     *
     *                                          memory allocator dumps
     *                                   OWNED  OWNER1  OWNER2  OWNER3  OWNER4
     *       not-owned sub-size [given]     10       -       -       -       -
     *      not-owning sub-size [given]      -       6       7       5       8
     *               importance [given]      -       2       2       1       0
     *    attributed not-owned sub-size      2       -       -       -       -
     *   attributed not-owning sub-size      -       3       4       0       1
     *                owned coefficient   2/10       -       -       -       -
     *               owning coefficient      -     3/6     4/7     0/5     1/8
     *
     * Explanation: Firstly, 6 bytes are split equally among OWNER1 and OWNER2
     * (highest importance). OWNER2 owns one more byte, so its attributed
     * not-owning sub-size is 6/2 + 1 = 4 bytes. OWNER3 is attributed no size
     * because it is smaller than the owners with higher priority. However,
     * OWNER4 is larger, so it's attributed the difference 8 - 7 = 1 byte.
     * Finally, 2 bytes remain unattributed and are hence kept in the OWNED
     * dump as attributed not-owned sub-size. The coefficients are then
     * directly calculated as fractions of the sub-sizes and corresponding
     * attributed sub-sizes.
     *
     * Note that we always assume that all ownerships of a dump overlap (e.g.
     * OWNER3 is subsumed by both OWNER1 and OWNER2). Hence, the table could
     * be alternatively represented as follows:
     *
     *                                 owned memory range
     *              0   1   2    3    4    5    6        7        8   9  10
     *   Priority 2 |  OWNER1 + OWNER2 (split)  | OWNER2 |
     *   Priority 1 | (already attributed) |
     *   Priority 0 | - - -  (already attributed)  - - - | OWNER4 |
     *    Remainder | - - - - - (already attributed) - - - - - -  | OWNED |
     *
     * This method assumes that (1) the size of the dump [see calculateSizes()]
     * and (2) the not-owned size of the dump and not-owning sub-sizes of its
     * owners [see the first step of calculateEffectiveSizes()] have already
     * been calculated. Note that the method doesn't make any assumptions about
     * the order in which dumps are visited.
     */
    calculateDumpOwnershipCoefficient_: function (dump) {
      // Completely skip dumps with undefined size.
      if (!hasSize(dump)) return;

      // We only need to consider owned dumps.
      if (dump.ownedBy.length === 0) return;

      // Sort the owners in decreasing order of ownership importance and
      // increasing order of not-owning sub-size (in case of equal importance).
      var owners = dump.ownedBy.map(function (ownershipLink) {
        return {
          dump: ownershipLink.source,
          importance: optional(ownershipLink.importance, 0),
          notOwningSubSize: optional(ownershipLink.source.notOwningSubSize_, 0)
        };
      });
      owners.sort(function (a, b) {
        if (a.importance === b.importance) return a.notOwningSubSize - b.notOwningSubSize;
        return b.importance - a.importance;
      });

      // Loop over the list of owners and distribute the owned dump's not-owned
      // sub-size among them according to their ownership importance and
      // not-owning sub-size.
      var currentImportanceStartPos = 0;
      var alreadyAttributedSubSize = 0;
      while (currentImportanceStartPos < owners.length) {
        var currentImportance = owners[currentImportanceStartPos].importance;

        // Find the position of the first owner with lower priority.
        var nextImportanceStartPos = currentImportanceStartPos + 1;
        while (nextImportanceStartPos < owners.length && owners[nextImportanceStartPos].importance === currentImportance) {
          nextImportanceStartPos++;
        }

        // Visit the owners with the same importance in increasing order of
        // not-owned sub-size, split the owned memory among them appropriately,
        // and calculate their owning coefficients.
        var attributedNotOwningSubSize = 0;
        for (var pos = currentImportanceStartPos; pos < nextImportanceStartPos; pos++) {
          var owner = owners[pos];
          var notOwningSubSize = owner.notOwningSubSize;
          if (notOwningSubSize > alreadyAttributedSubSize) {
            attributedNotOwningSubSize += (notOwningSubSize - alreadyAttributedSubSize) / (nextImportanceStartPos - pos);
            alreadyAttributedSubSize = notOwningSubSize;
          }

          var owningCoefficient = 0;
          if (notOwningSubSize !== 0) owningCoefficient = attributedNotOwningSubSize / notOwningSubSize;
          owner.dump.owningCoefficient_ = owningCoefficient;
        }

        currentImportanceStartPos = nextImportanceStartPos;
      }

      // Attribute the remainder of the owned dump's not-owned sub-size to
      // the dump itself and calculate its owned coefficient.
      var notOwnedSubSize = optional(dump.notOwnedSubSize_, 0);
      var remainderSubSize = notOwnedSubSize - alreadyAttributedSubSize;
      var ownedCoefficient = 0;
      if (notOwnedSubSize !== 0) ownedCoefficient = remainderSubSize / notOwnedSubSize;
      dump.ownedCoefficient_ = ownedCoefficient;
    },

    /**
     * Calculate cumulative owned and owning coefficients of a memory allocator
     * dump from its (non-cumulative) owned and owning coefficients and the
     * cumulative coefficients of its parent and/or owned dump.
     *
     * The cumulative coefficients represent the total effect of all
     * (non-strict) ancestor ownerships on a memory allocator dump. The
     * cumulative owned coefficient of a MAD can be calculated simply as:
     *
     *   cumulativeOwnedC(M) = ownedC(M) * cumulativeOwnedC(parent(M))
     *
     * This reflects the assumption that if a parent of a child MAD is
     * (partially) owned, then the parent's owner also indirectly owns (a part
     * of) the child MAD.
     *
     * The cumulative owning coefficient of a MAD depends on whether the MAD
     * owns another dump:
     *
     *                           [if M doesn't own another MAD]
     *                         / cumulativeOwningC(parent(M))
     *   cumulativeOwningC(M) =
     *                         \ [if M owns another MAD]
     *                           owningC(M) * cumulativeOwningC(owned(M))
     *
     * The reasoning behind the first case is similar to the one for cumulative
     * owned coefficient above. The only difference is that we don't need to
     * include the dump's (non-cumulative) owning coefficient because it is
     * implicitly 1.
     *
     * The formula for the second case is derived as follows: Since the MAD
     * owns another dump, its memory is not included in its parent's not-owning
     * sub-size and hence shouldn't be affected by the parent's corresponding
     * cumulative coefficient. Instead, the MAD indirectly owns everything
     * owned by its owned dump (and so it should be affected by the
     * corresponding coefficient).
     *
     * Note that undefined coefficients (and coefficients of non-existent
     * dumps) are implicitly assumed to be 1.
     *
     * This method assumes that (1) the size of the dump [see calculateSizes()],
     * (2) the (non-cumulative) owned and owning coefficients of the dump [see
     * the second step of calculateEffectiveSizes()], and (3) the cumulative
     * coefficients of the dump's parent and owned MADs (if present)
     * [depth-first pre-order traversal] have already been calculated.
     */
    calculateDumpCumulativeOwnershipCoefficient_: function (dump) {
      // Completely skip dumps with undefined size.
      if (!hasSize(dump)) return;

      var cumulativeOwnedCoefficient = optional(dump.ownedCoefficient_, 1);
      var parent = dump.parent;
      if (dump.parent !== undefined) cumulativeOwnedCoefficient *= dump.parent.cumulativeOwnedCoefficient_;
      dump.cumulativeOwnedCoefficient_ = cumulativeOwnedCoefficient;

      var cumulativeOwningCoefficient;
      if (dump.owns !== undefined) {
        cumulativeOwningCoefficient = dump.owningCoefficient_ * dump.owns.target.cumulativeOwningCoefficient_;
      } else if (dump.parent !== undefined) {
        cumulativeOwningCoefficient = dump.parent.cumulativeOwningCoefficient_;
      } else {
        cumulativeOwningCoefficient = 1;
      }
      dump.cumulativeOwningCoefficient_ = cumulativeOwningCoefficient;
    },

    /**
     * Calculate the effective size of a memory allocator dump.
     *
     * In order to simplify the (already complex) calculation, we use the fact
     * that effective size is cumulative (unlike regular size), i.e. the
     * effective size of a non-leaf node is equal to the sum of effective sizes
     * of its children. The effective size of a leaf MAD is calculated as:
     *
     *   effectiveSize(M) = size(M) * cumulativeOwningC(M) * cumulativeOwnedC(M)
     *
     * This method assumes that (1) the size of the dump and its children [see
     * calculateSizes()] and (2) the cumulative owning and owned coefficients
     * of the dump (if it's a leaf node) [see the third step of
     * calculateEffectiveSizes()] or the effective sizes of its children (if
     * it's a non-leaf node) [depth-first post-order traversal] have already
     * been calculated.
     */
    calculateDumpEffectiveSize_: function (dump) {
      // Completely skip dumps with undefined size. As a result, each dump will
      // have defined effective size if and only if it has defined size.
      if (!hasSize(dump)) {
        // The rest of the pipeline relies on effective size being either a
        // valid ScalarNumeric, or undefined.
        delete dump.numerics[EFFECTIVE_SIZE_NUMERIC_NAME];
        return;
      }

      var effectiveSize;
      if (dump.children === undefined || dump.children.length === 0) {
        // Leaf dump.
        effectiveSize = getSize(dump) * dump.cumulativeOwningCoefficient_ * dump.cumulativeOwnedCoefficient_;
      } else {
        // Non-leaf dump.
        effectiveSize = 0;
        dump.children.forEach(function (childDump) {
          if (!hasSize(childDump)) return;
          effectiveSize += childDump.numerics[EFFECTIVE_SIZE_NUMERIC_NAME].value;
        });
      }
      dump.numerics[EFFECTIVE_SIZE_NUMERIC_NAME] = new tr.v.ScalarNumeric(tr.b.Unit.byName.sizeInBytes_smallerIsBetter, effectiveSize);
    },

    aggregateNumerics: function () {
      // 1. Aggregate numerics in this global memory dump.
      this.iterateRootAllocatorDumps(function (dump) {
        dump.aggregateNumericsRecursively(this.model);
      });

      // 2. Propagate numerics and diagnostics from global memory allocator
      // dumps to their owners.
      this.iterateRootAllocatorDumps(this.propagateNumericsAndDiagnosticsRecursively);

      // 3. Aggregate numerics in the associated process memory dumps.
      tr.b.iterItems(this.processMemoryDumps, function (pid, processMemoryDump) {
        processMemoryDump.iterateRootAllocatorDumps(function (dump) {
          dump.aggregateNumericsRecursively(this.model);
        }, this);
      }, this);
    },

    propagateNumericsAndDiagnosticsRecursively: function (globalAllocatorDump) {
      ['numerics', 'diagnostics'].forEach(function (field) {
        tr.b.iterItems(globalAllocatorDump[field], function (name, value) {
          globalAllocatorDump.ownedBy.forEach(function (ownershipLink) {
            var processAllocatorDump = ownershipLink.source;
            if (processAllocatorDump[field][name] !== undefined) {
              // Numerics and diagnostics provided by process memory allocator
              // dumps themselves have precedence over numerics and diagnostics
              // propagated from global memory allocator dumps.
              return;
            }
            processAllocatorDump[field][name] = value;
          });
        });
      });

      // Recursively propagate numerics from all child memory allocator dumps.
      globalAllocatorDump.children.forEach(this.propagateNumericsAndDiagnosticsRecursively, this);
    },

    setUpTracingOverheadOwnership: function () {
      tr.b.iterItems(this.processMemoryDumps, function (pid, dump) {
        dump.setUpTracingOverheadOwnership(this.model);
      }, this);
    },

    discountTracingOverheadFromVmRegions: function () {
      // TODO(petrcermak): Consider factoring out all the finalization code and
      // constants to a single file.
      tr.b.iterItems(this.processMemoryDumps, function (pid, dump) {
        dump.discountTracingOverheadFromVmRegions(this.model);
      }, this);
    },

    forceRebuildingMemoryAllocatorDumpByFullNameIndices: function () {
      this.iterateContainerDumps(function (containerDump) {
        containerDump.forceRebuildingMemoryAllocatorDumpByFullNameIndex();
      });
    },

    iterateContainerDumps: function (fn) {
      fn.call(this, this);
      tr.b.iterItems(this.processMemoryDumps, function (pid, processDump) {
        fn.call(this, processDump);
      }, this);
    },

    iterateAllRootAllocatorDumps: function (fn) {
      this.iterateContainerDumps(function (containerDump) {
        containerDump.iterateRootAllocatorDumps(fn, this);
      });
    },

    /**
     * Traverse the memory dump graph in a depth first post-order, i.e.
     * children and owners of a memory allocator dump are visited before the
     * dump itself. This method will throw an exception if the graph contains
     * a cycle.
     */
    traverseAllocatorDumpsInDepthFirstPostOrder: function (fn) {
      var visitedDumps = new WeakSet();
      var openDumps = new WeakSet();

      function visit(dump) {
        if (visitedDumps.has(dump)) return;

        if (openDumps.has(dump)) throw new Error(dump.userFriendlyName + ' contains a cycle');
        openDumps.add(dump);

        // Visit owners before the dumps they own.
        dump.ownedBy.forEach(function (ownershipLink) {
          visit.call(this, ownershipLink.source);
        }, this);

        // Visit children before parents.
        dump.children.forEach(visit, this);

        // Actually visit the current memory allocator dump.
        fn.call(this, dump);
        visitedDumps.add(dump);

        openDumps.delete(dump);
      }

      this.iterateAllRootAllocatorDumps(visit);
    },

    /**
     * Traverse the memory dump graph in a depth first pre-order, i.e.
     * children and owners of a memory allocator dump are visited after the
     * dump itself. This method will not visit some dumps if the graph contains
     * a cycle.
     */
    traverseAllocatorDumpsInDepthFirstPreOrder: function (fn) {
      var visitedDumps = new WeakSet();

      function visit(dump) {
        if (visitedDumps.has(dump)) return;

        // If this dumps owns another dump which hasn't been visited yet, then
        // wait for this dump to be visited later.
        if (dump.owns !== undefined && !visitedDumps.has(dump.owns.target)) return;

        // If this dump's parent hasn't been visited yet, then wait for this
        // dump to be visited later.
        if (dump.parent !== undefined && !visitedDumps.has(dump.parent)) return;

        // Actually visit the current memory allocator dump.
        fn.call(this, dump);
        visitedDumps.add(dump);

        // Visit owners after the dumps they own.
        dump.ownedBy.forEach(function (ownershipLink) {
          visit.call(this, ownershipLink.source);
        }, this);

        // Visit children after parents.
        dump.children.forEach(visit, this);
      }

      this.iterateAllRootAllocatorDumps(visit);
    }
  };

  tr.model.EventRegistry.register(GlobalMemoryDump, {
    name: 'globalMemoryDump',
    pluralName: 'globalMemoryDumps'
  });

  return {
    GlobalMemoryDump: GlobalMemoryDump
  };
});