blob: 6b8c618f0f84362be8fec89598ed0bd83eab9479 [file] [log] [blame]
<!DOCTYPE html>
<!--
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.
-->
<link rel="import" href="/tracing/base/iteration_helpers.html">
<link rel="import" href="/tracing/base/unit.html">
<link rel="import" href="/tracing/model/container_memory_dump.html">
<link rel="import" href="/tracing/model/event_registry.html">
<link rel="import" href="/tracing/model/memory_allocator_dump.html">
<link rel="import" href="/tracing/value/numeric.html">
<script>
'use strict';
/**
* @fileoverview Provides the GlobalMemoryDump class.
*/
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
};
});
</script>