blob: ec5145afb2402bad5f563e7952179820bbfa5efd [file] [log] [blame]
/*
* Copyright (C) 2016 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
WI.HeapAllocationsTimelineView = class HeapAllocationsTimelineView extends WI.TimelineView
{
constructor(timeline, extraArguments)
{
super(timeline, extraArguments);
console.assert(timeline.type === WI.TimelineRecord.Type.HeapAllocations, timeline);
this.element.classList.add("heap-allocations");
let columns = {
name: {
title: WI.UIString("Name"),
width: "150px",
icon: true,
},
timestamp: {
title: WI.UIString("Time"),
width: "80px",
sortable: true,
aligned: "right",
},
size: {
title: WI.UIString("Size"),
width: "80px",
sortable: true,
aligned: "right",
},
liveSize: {
title: WI.UIString("Live Size"),
width: "80px",
sortable: true,
aligned: "right",
},
};
this._importButtonNavigationItem = new WI.ButtonNavigationItem("import", WI.UIString("Import"), "Images/Import.svg", 15, 15);
this._importButtonNavigationItem.tooltip = WI.UIString("Import");
this._importButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
this._importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._importButtonNavigationItemClicked, this);
let snapshotTooltip = WI.UIString("Take snapshot");
this._takeHeapSnapshotButtonItem = new WI.ButtonNavigationItem("take-snapshot", snapshotTooltip, "Images/Camera.svg", 16, 16);
this._takeHeapSnapshotButtonItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._takeHeapSnapshotClicked, this);
let defaultToolTip = WI.UIString("Compare snapshots");
let activatedToolTip = WI.UIString("Cancel comparison");
this._compareHeapSnapshotsButtonItem = new WI.ActivateButtonNavigationItem("compare-heap-snapshots", defaultToolTip, activatedToolTip, "Images/Compare.svg", 16, 16);
this._compareHeapSnapshotsButtonItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._compareHeapSnapshotsClicked, this);
this._compareHeapSnapshotsButtonItem.enabled = false;
this._compareHeapSnapshotsButtonItem.activated = false;
this._compareHeapSnapshotHelpTextItem = new WI.TextNavigationItem("compare-heap-snapshot-help-text", "");
this._selectingComparisonHeapSnapshots = false;
this._baselineDataGridNode = null;
this._baselineHeapSnapshotTimelineRecord = null;
this._heapSnapshotDiff = null;
this._snapshotListScrollTop = 0;
this._showingSnapshotList = true;
this._snapshotListPathComponent = new WI.HierarchicalPathComponent(WI.UIString("Snapshot List"), "snapshot-list-icon", "snapshot-list", false, false);
this._snapshotListPathComponent.addEventListener(WI.HierarchicalPathComponent.Event.Clicked, this._snapshotListPathComponentClicked, this);
this._dataGrid = new WI.TimelineDataGrid(columns);
this._dataGrid.sortColumnIdentifier = "timestamp";
this._dataGrid.sortOrder = WI.DataGrid.SortOrder.Ascending;
this._dataGrid.createSettings("heap-allocations-timeline-view");
this._dataGrid.addEventListener(WI.DataGrid.Event.SelectedNodeChanged, this._dataGridNodeSelected, this);
this.setupDataGrid(this._dataGrid);
this.addSubview(this._dataGrid);
this._contentViewContainer = new WI.ContentViewContainer;
this._contentViewContainer.addEventListener(WI.ContentViewContainer.Event.CurrentContentViewDidChange, this._currentContentViewDidChange, this);
WI.ContentView.addEventListener(WI.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this);
this._pendingRecords = [];
timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._heapAllocationsTimelineRecordAdded, this);
WI.HeapSnapshotProxy.addEventListener(WI.HeapSnapshotProxy.Event.Invalidated, this._heapSnapshotInvalidated, this);
WI.HeapSnapshotWorkerProxy.singleton().addEventListener("HeapSnapshot.CollectionEvent", this._heapSnapshotCollectionEvent, this);
}
// Public
get scrollableElements()
{
if (this._showingSnapshotList)
return [this._dataGrid.scrollContainer];
if (this._contentViewContainer.currentContentView)
return this._contentViewContainer.currentContentView.scrollableElements;
return [];
}
showHeapSnapshotList()
{
if (this._showingSnapshotList)
return;
this._showingSnapshotList = true;
this._heapSnapshotDiff = null;
this._cancelSelectComparisonHeapSnapshots();
this._contentViewContainer.hidden();
this.removeSubview(this._contentViewContainer);
this.addSubview(this._dataGrid);
this._dataGrid.scrollContainer.scrollTop = this._snapshotListScrollTop;
this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
}
showHeapSnapshotTimelineRecord(heapSnapshotTimelineRecord)
{
if (this._showingSnapshotList) {
this._snapshotListScrollTop = this._dataGrid.scrollContainer.scrollTop;
this.removeSubview(this._dataGrid);
this.addSubview(this._contentViewContainer);
this._contentViewContainer.shown();
}
this._showingSnapshotList = false;
this._heapSnapshotDiff = null;
this._cancelSelectComparisonHeapSnapshots();
for (let dataGridNode of this._dataGrid.children) {
if (dataGridNode.record === heapSnapshotTimelineRecord) {
dataGridNode.hidden = false;
dataGridNode.select();
break;
}
}
let shouldTriggerContentViewUpdate = this._contentViewContainer.currentContentView && this._contentViewContainer.currentContentView.representedObject === heapSnapshotTimelineRecord.heapSnapshot;
this._contentViewContainer.showContentViewForRepresentedObject(heapSnapshotTimelineRecord.heapSnapshot);
if (shouldTriggerContentViewUpdate)
this._currentContentViewDidChange();
}
showHeapSnapshotDiff(heapSnapshotDiff)
{
if (this._showingSnapshotList) {
this.removeSubview(this._dataGrid);
this.addSubview(this._contentViewContainer);
this._contentViewContainer.shown();
}
this._showingSnapshotList = false;
this._heapSnapshotDiff = heapSnapshotDiff;
this._contentViewContainer.showContentViewForRepresentedObject(heapSnapshotDiff);
}
// Protected
get navigationItems()
{
if (this._showingSnapshotList) {
let items = [this._importButtonNavigationItem, this._takeHeapSnapshotButtonItem, this._compareHeapSnapshotsButtonItem];
if (this._selectingComparisonHeapSnapshots)
items.push(this._compareHeapSnapshotHelpTextItem);
return items;
}
if (this._contentViewContainer.currentContentView)
return this._contentViewContainer.currentContentView.navigationItems;
return [];
}
get selectionPathComponents()
{
let components = [this._snapshotListPathComponent];
if (this._showingSnapshotList)
return components;
if (this._heapSnapshotDiff) {
let firstSnapshotIdentifier = this._heapSnapshotDiff.snapshot1.identifier;
let secondSnapshotIdentifier = this._heapSnapshotDiff.snapshot2.identifier;
let diffComponent = new WI.HierarchicalPathComponent(WI.UIString("Snapshot Comparison (%d and %d)").format(firstSnapshotIdentifier, secondSnapshotIdentifier), "snapshot-diff-icon", "snapshot-diff");
components.push(diffComponent);
} else if (this._dataGrid.selectedNode) {
let heapSnapshotPathComponent = new WI.HeapAllocationsTimelineDataGridNodePathComponent(this._dataGrid.selectedNode);
heapSnapshotPathComponent.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._snapshotPathComponentSelected, this);
components.push(heapSnapshotPathComponent);
}
if (this._contentViewContainer.currentContentView)
components.pushAll(this._contentViewContainer.currentContentView.selectionPathComponents);
return components;
}
selectRecord(record)
{
if (record)
this.showHeapSnapshotTimelineRecord(record);
else
this.showHeapSnapshotList();
}
shown()
{
super.shown();
this._dataGrid.shown();
if (!this._showingSnapshotList)
this._contentViewContainer.shown();
}
hidden()
{
super.hidden();
this._dataGrid.hidden();
if (!this._showingSnapshotList)
this._contentViewContainer.hidden();
}
closed()
{
console.assert(this.representedObject instanceof WI.Timeline);
this.representedObject.removeEventListener(null, null, this);
this._dataGrid.closed();
this._contentViewContainer.closeAllContentViews();
WI.ContentView.removeEventListener(null, null, this);
WI.HeapSnapshotProxy.removeEventListener(null, null, this);
WI.HeapSnapshotWorkerProxy.singleton().removeEventListener("HeapSnapshot.CollectionEvent", this._heapSnapshotCollectionEvent, this);
}
layout()
{
super.layout();
if (!this._pendingRecords.length)
return;
for (let heapAllocationsTimelineRecord of this._pendingRecords) {
this._dataGrid.addRowInSortOrder(new WI.HeapAllocationsTimelineDataGridNode(heapAllocationsTimelineRecord, {
graphDataSource: this,
heapAllocationsView: this,
}));
}
this._pendingRecords = [];
this._updateCompareHeapSnapshotButton();
}
reset()
{
super.reset();
this._dataGrid.reset();
this.showHeapSnapshotList();
this._pendingRecords = [];
this._updateCompareHeapSnapshotButton();
}
updateFilter(filters)
{
if (this._showingSnapshotList) {
this._dataGrid.filterText = filters ? filters.text : "";
return;
}
console.assert(this._contentViewContainer.currentContentView);
this._contentViewContainer.currentContentView.updateFilter(filters);
}
// Private
_heapAllocationsTimelineRecordAdded(event)
{
this._pendingRecords.push(event.data.record);
this.needsLayout();
}
_heapSnapshotCollectionEvent(event)
{
function updateHeapSnapshotForEvent(heapSnapshot) {
if (heapSnapshot.invalid)
return;
heapSnapshot.updateForCollectionEvent(event);
}
for (let node of this._dataGrid.children)
updateHeapSnapshotForEvent(node.record.heapSnapshot);
for (let record of this._pendingRecords)
updateHeapSnapshotForEvent(record.heapSnapshot);
if (this._heapSnapshotDiff)
updateHeapSnapshotForEvent(this._heapSnapshotDiff);
}
_snapshotListPathComponentClicked(event)
{
this.showHeapSnapshotList();
}
_snapshotPathComponentSelected(event)
{
console.assert(event.data.pathComponent.representedObject instanceof WI.HeapAllocationsTimelineRecord);
this.showHeapSnapshotTimelineRecord(event.data.pathComponent.representedObject);
}
_currentContentViewDidChange(event)
{
this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
}
_contentViewSelectionPathComponentDidChange(event)
{
if (event.target !== this._contentViewContainer.currentContentView)
return;
this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
}
_heapSnapshotInvalidated(event)
{
let heapSnapshot = event.target;
if (this._baselineHeapSnapshotTimelineRecord) {
if (heapSnapshot === this._baselineHeapSnapshotTimelineRecord.heapSnapshot)
this._cancelSelectComparisonHeapSnapshots();
}
if (this._heapSnapshotDiff) {
if (heapSnapshot === this._heapSnapshotDiff.snapshot1 || heapSnapshot === this._heapSnapshotDiff.snapshot2)
this.showHeapSnapshotList();
} else if (this._dataGrid.selectedNode) {
if (heapSnapshot === this._dataGrid.selectedNode.record.heapSnapshot)
this.showHeapSnapshotList();
}
this._updateCompareHeapSnapshotButton();
}
_updateCompareHeapSnapshotButton()
{
let hasAtLeastTwoValidSnapshots = false;
let count = 0;
for (let node of this._dataGrid.children) {
if (node.revealed && !node.hidden && !node.record.heapSnapshot.invalid) {
count++;
if (count === 2) {
hasAtLeastTwoValidSnapshots = true;
break;
}
}
}
this._compareHeapSnapshotsButtonItem.enabled = hasAtLeastTwoValidSnapshots;
}
_importButtonNavigationItemClicked()
{
WI.FileUtilities.importText((result) => {
let snapshotStringData = result.text;
let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
workerProxy.createImportedSnapshot(snapshotStringData, result.filename, ({objectId, snapshot: serializedSnapshot}) => {
let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
snapshot.snapshotStringData = snapshotStringData;
const timestamp = NaN;
WI.timelineManager.heapSnapshotAdded(timestamp, snapshot);
this.dispatchEventToListeners(WI.TimelineView.Event.NeedsEntireSelectedRange);
});
}, {multiple: true});
}
_takeHeapSnapshotClicked()
{
WI.heapManager.snapshot((error, timestamp, snapshotStringData) => {
let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => {
let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
snapshot.snapshotStringData = snapshotStringData;
WI.timelineManager.heapSnapshotAdded(timestamp, snapshot);
this.dispatchEventToListeners(WI.TimelineView.Event.NeedsEntireSelectedRange);
});
});
}
_cancelSelectComparisonHeapSnapshots()
{
if (!this._selectingComparisonHeapSnapshots)
return;
if (this._baselineDataGridNode)
this._baselineDataGridNode.clearBaseline();
this._selectingComparisonHeapSnapshots = false;
this._baselineDataGridNode = null;
this._baselineHeapSnapshotTimelineRecord = null;
this._compareHeapSnapshotsButtonItem.activated = false;
this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
}
_compareHeapSnapshotsClicked(event)
{
let newActivated = !this._compareHeapSnapshotsButtonItem.activated;
this._compareHeapSnapshotsButtonItem.activated = newActivated;
if (!newActivated) {
this._cancelSelectComparisonHeapSnapshots();
return;
}
if (this._dataGrid.selectedNode)
this._dataGrid.selectedNode.deselect();
this._selectingComparisonHeapSnapshots = true;
this._baselineHeapSnapshotTimelineRecord = null;
this._compareHeapSnapshotHelpTextItem.text = WI.UIString("Select baseline snapshot");
this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
}
_dataGridNodeSelected(event)
{
if (!this._selectingComparisonHeapSnapshots)
return;
let dataGridNode = this._dataGrid.selectedNode;
if (!dataGridNode)
return;
let heapAllocationsTimelineRecord = dataGridNode.record;
// Cancel the selection if the heap snapshot is invalid, or was already selected as the baseline.
if (heapAllocationsTimelineRecord.heapSnapshot.invalid || this._baselineHeapSnapshotTimelineRecord === heapAllocationsTimelineRecord) {
this._dataGrid.selectedNode.deselect();
return;
}
// Selected Baseline.
if (!this._baselineHeapSnapshotTimelineRecord) {
this._baselineDataGridNode = dataGridNode;
this._baselineDataGridNode.markAsBaseline();
this._baselineHeapSnapshotTimelineRecord = heapAllocationsTimelineRecord;
this._dataGrid.selectedNode.deselect();
this._compareHeapSnapshotHelpTextItem.text = WI.UIString("Select comparison snapshot");
this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
return;
}
// Selected Comparison.
let snapshot1 = this._baselineHeapSnapshotTimelineRecord.heapSnapshot;
let snapshot2 = heapAllocationsTimelineRecord.heapSnapshot;
if (snapshot1.identifier > snapshot2.identifier)
[snapshot1, snapshot2] = [snapshot2, snapshot1];
let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
workerProxy.createSnapshotDiff(snapshot1.proxyObjectId, snapshot2.proxyObjectId, ({objectId, snapshotDiff: serializedSnapshotDiff}) => {
let diff = WI.HeapSnapshotDiffProxy.deserialize(objectId, serializedSnapshotDiff);
this.showHeapSnapshotDiff(diff);
});
this._baselineDataGridNode.clearBaseline();
this._baselineDataGridNode = null;
this._selectingComparisonHeapSnapshots = false;
this._compareHeapSnapshotsButtonItem.activated = false;
}
};