blob: aa993cb474e47559b43c81da8bc32336cecc7af5 [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/units/time_stamp.html">
<link rel="import" href="/tracing/model/attribute.html">
<link rel="import" href="/tracing/model/container_memory_dump.html">
<link rel="import" href="/tracing/model/memory_allocator_dump.html">
<script>
'use strict';
/**
* @fileoverview Provides the ProcessMemoryDump class.
*/
tr.exportTo('tr.model', function() {
// Names of MemoryAllocatorDump(s) from which tracing overhead should be
// discounted.
var DISCOUNTED_ALLOCATOR_NAMES = ['winheap', 'malloc'];
// The path to where the tracing overhead dump should be added to the
// winheap/malloc allocator dump tree.
var TRACING_OVERHEAD_PATH = ['allocated_objects', 'tracing_overhead'];
var SIZE_ATTRIBUTE_NAME = tr.model.MemoryAllocatorDump.SIZE_ATTRIBUTE_NAME;
var RESIDENT_SIZE_ATTRIBUTE_NAME =
tr.model.MemoryAllocatorDump.RESIDENT_SIZE_ATTRIBUTE_NAME;
function getSizeAttrValue(dump, sizeAttrName, opt_model) {
var sizeAttr = dump.getValidSizeAttributeOrUndefined(
sizeAttrName, opt_model);
if (sizeAttr === undefined)
return 0;
return sizeAttr.value;
}
/**
* The ProcessMemoryDump represents a memory dump of a single process.
* @constructor
*/
function ProcessMemoryDump(globalMemoryDump, process, start) {
tr.model.ContainerMemoryDump.call(this, start);
this.process = process;
this.globalMemoryDump = globalMemoryDump;
// Process memory totals (optional object) with the following fields (also
// optional):
// - residentBytes: Total resident bytes (number)
// - peakResidentBytes: Peak resident bytes (number)
// - arePeakResidentBytesResettable: Flag whether peak resident bytes are
// resettable (boolean)
// - platformSpecific: Map from OS-specific total names (string) to sizes
// (number)
this.totals = undefined;
this.vmRegions_ = undefined;
// Map from allocator names to heap dumps.
this.heapDumps = undefined;
this.tracingOverheadOwnershipSetUp_ = false;
this.tracingOverheadDiscountedFromVmRegions_ = false;
};
ProcessMemoryDump.prototype = {
__proto__: tr.model.ContainerMemoryDump.prototype,
get userFriendlyName() {
return 'Process memory dump at ' +
tr.b.u.TimeStamp.format(this.start);
},
get containerName() {
return this.process.userFriendlyName;
},
get processMemoryDumps() {
var dumps = {};
dumps[this.process.pid] = this;
return dumps;
},
get vmRegions() {
return this.vmRegions_;
},
set vmRegions(vmRegions) {
this.vmRegions_ = vmRegions;
},
get hasOwnVmRegions() {
return this.vmRegions_ !== undefined;
},
getMostRecentTotalVmRegionStat: function(statName) {
if (this.mostRecentVmRegions === undefined)
return undefined;
var total = 0;
this.mostRecentVmRegions.forEach(function(vmRegion) {
var statValue = vmRegion.byteStats[statName];
if (statValue === undefined)
return;
total += statValue;
});
return total;
},
setUpTracingOverheadOwnership: function(opt_model) {
// Make sure that calling this method twice won't lead to
// 'double-discounting'.
if (this.tracingOverheadOwnershipSetUp_)
return;
this.tracingOverheadOwnershipSetUp_ = true;
var tracingDump = this.getMemoryAllocatorDumpByFullName('tracing');
if (tracingDump === undefined || tracingDump.owns !== undefined) {
// The tracing dump either doesn't exist, or it already owns another
// dump.
return;
}
if (tracingDump.owns !== undefined)
return;
// Add an ownership link from tracing to
// malloc/allocated_objects/tracing_overhead or
// winheap/allocated_objects/tracing_overhead.
var hasDiscountedFromAllocatorDumps = DISCOUNTED_ALLOCATOR_NAMES.some(
function(allocatorName) {
// First check if the allocator root exists.
var allocatorDump = this.getMemoryAllocatorDumpByFullName(
allocatorName);
if (allocatorDump === undefined)
return false; // Allocator doesn't exist, try another one.
var nextPathIndex = 0;
var currentDump = allocatorDump;
var currentFullName = allocatorName;
// Descend from the root towards tracing_overhead as long as the dumps
// on the path exist.
for (; nextPathIndex < TRACING_OVERHEAD_PATH.length; nextPathIndex++) {
var childFullName = currentFullName + '/' +
TRACING_OVERHEAD_PATH[nextPathIndex];
var childDump = this.getMemoryAllocatorDumpByFullName(
childFullName);
if (childDump === undefined)
break;
currentDump = childDump;
currentFullName = childFullName;
}
// Create the missing descendant dumps on the path from the root
// towards tracing_overhead.
for (; nextPathIndex < TRACING_OVERHEAD_PATH.length; nextPathIndex++) {
var childFullName = currentFullName + '/' +
TRACING_OVERHEAD_PATH[nextPathIndex];
var childDump = new tr.model.MemoryAllocatorDump(
currentDump.containerMemoryDump, childFullName);
childDump.parent = currentDump;
currentDump.children.push(childDump);
currentFullName = childFullName;
currentDump = childDump;
}
// Add the ownership link.
var ownershipLink =
new tr.model.MemoryAllocatorDumpLink(tracingDump, currentDump);
tracingDump.owns = ownershipLink;
currentDump.ownedBy.push(ownershipLink);
return true;
}, this);
// Force rebuilding the memory allocator dump index (if we've just added
// a new memory allocator dump).
if (hasDiscountedFromAllocatorDumps)
this.memoryAllocatorDumps = this.memoryAllocatorDumps;
},
discountTracingOverheadFromVmRegions: function(opt_model) {
// Make sure that calling this method twice won't lead to
// 'double-discounting'.
if (this.tracingOverheadDiscountedFromVmRegions_)
return;
this.tracingOverheadDiscountedFromVmRegions_ = true;
var tracingDump = this.getMemoryAllocatorDumpByFullName('tracing');
if (tracingDump === undefined)
return;
var discountedSize = getSizeAttrValue(tracingDump, SIZE_ATTRIBUTE_NAME);
var discountedResidentSize =
getSizeAttrValue(tracingDump, RESIDENT_SIZE_ATTRIBUTE_NAME);
if (discountedSize <= 0 && discountedResidentSize <= 0)
return;
// Subtract the tracing size from the totals.
if (this.totals !== undefined) {
if (this.totals.residentBytes !== undefined)
this.totals.residentBytes -= discountedResidentSize;
if (this.totals.peakResidentBytes !== undefined)
this.totals.peakResidentBytes -= discountedResidentSize;
}
// Subtract the tracing size from VM regions. More precisely, subtract
// tracing resident_size from byte stats (private dirty and PSS) and
// tracing size from virtual size by injecting a fake VM region with
// negative values.
if (this.vmRegions_ !== undefined) {
var hasSizeInBytes = false;
var hasPrivateDirtyResident = false;
var hasProportionalResident = false;
for (var i = 0; i < this.vmRegions_.length; i++) {
var vmRegion = this.vmRegions_[i];
if (vmRegion.sizeInBytes !== undefined)
hasSizeInBytes = true;
var byteStats = vmRegion.byteStats;
if (byteStats.privateDirtyResident !== undefined)
hasPrivateDirtyResident = true;
if (byteStats.proportionalResident !== undefined)
hasProportionalResident = true;
if (hasSizeInBytes && hasPrivateDirtyResident &&
hasProportionalResident) {
break;
}
}
if ((hasSizeInBytes && discountedSize > 0) ||
((hasPrivateDirtyResident || hasProportionalResident) &&
discountedResidentSize > 0)) {
this.vmRegions_.push(VMRegion.fromDict({
mappedFile: '[discounted tracing overhead]',
sizeInBytes: hasSizeInBytes ? -discountedSize : undefined,
byteStats: {
privateDirtyResident: hasPrivateDirtyResident ?
-discountedResidentSize : undefined,
proportionalResident: hasProportionalResident ?
-discountedResidentSize : undefined
}
}));
}
}
}
};
ProcessMemoryDump.hookUpMostRecentVmRegionsLinks = function(processDumps) {
var mostRecentVmRegions = undefined;
processDumps.forEach(function(processDump) {
// Update the most recent VM regions from the current dump.
if (processDump.vmRegions_ !== undefined)
mostRecentVmRegions = processDump.vmRegions_;
// Set the most recent VM regions of the current dump.
processDump.mostRecentVmRegions = mostRecentVmRegions;
});
};
/**
* @constructor
*/
function VMRegion(startAddress, sizeInBytes, protectionFlags,
mappedFile, byteStats) {
this.startAddress = startAddress;
this.sizeInBytes = sizeInBytes;
this.protectionFlags = protectionFlags;
this.mappedFile = mappedFile;
this.byteStats = byteStats;
};
VMRegion.PROTECTION_FLAG_READ = 4;
VMRegion.PROTECTION_FLAG_WRITE = 2;
VMRegion.PROTECTION_FLAG_EXECUTE = 1;
VMRegion.prototype = {
get protectionFlagsToString() {
if (this.protectionFlags === undefined)
return undefined;
return (
(this.protectionFlags & VMRegion.PROTECTION_FLAG_READ ? 'r' : '-') +
(this.protectionFlags & VMRegion.PROTECTION_FLAG_WRITE ? 'w' : '-') +
(this.protectionFlags & VMRegion.PROTECTION_FLAG_EXECUTE ? 'x' : '-')
);
}
};
VMRegion.fromDict = function(dict) {
return new VMRegion(
dict.startAddress,
dict.sizeInBytes,
dict.protectionFlags,
dict.mappedFile,
VMRegionByteStats.fromDict(dict.byteStats));
};
/**
* @constructor
*/
function VMRegionByteStats(privateCleanResident, privateDirtyResident,
sharedCleanResident, sharedDirtyResident,
proportionalResident, swapped) {
this.privateCleanResident = privateCleanResident;
this.privateDirtyResident = privateDirtyResident;
this.sharedCleanResident = sharedCleanResident;
this.sharedDirtyResident = sharedDirtyResident;
this.proportionalResident = proportionalResident;
this.swapped = swapped;
}
VMRegionByteStats.fromDict = function(dict) {
return new VMRegionByteStats(
dict.privateCleanResident,
dict.privateDirtyResident,
dict.sharedCleanResident,
dict.sharedDirtyResident,
dict.proportionalResident,
dict.swapped);
}
tr.model.EventRegistry.register(
ProcessMemoryDump,
{
name: 'processMemoryDump',
pluralName: 'processMemoryDumps',
singleViewElementName: 'tr-ui-a-container-memory-dump-sub-view',
multiViewElementName: 'tr-ui-a-container-memory-dump-sub-view'
});
return {
ProcessMemoryDump: ProcessMemoryDump,
VMRegion: VMRegion,
VMRegionByteStats: VMRegionByteStats
};
});
</script>