c1f11fed3f
- Make the size tools less dependent on quirks mode. - Add explicit language attribute so Chrome stops offering to translate. - Add zip_size to look at the Dart SDK and Flutter APKs. Change-Id: I0017bd68145344e2427644fcccd13f9b9f307218 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/428064 Reviewed-by: Brian Quinlan <bquinlan@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
2021 lines
58 KiB
JavaScript
2021 lines
58 KiB
JavaScript
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
// TODO:
|
|
// - starting dominator treemap from a group of nodes instead of only a
|
|
// single node
|
|
|
|
"use strict";
|
|
|
|
function Graph() {
|
|
// Create all slots up front to prevent V8 map transitions.
|
|
|
|
// We extensively use parallel arrays instead of objects like
|
|
//
|
|
// function Vertex {
|
|
// this.size = 0;
|
|
// this.successors = new Array();
|
|
// this.predecessors = new Array();
|
|
// this.semi = 0;
|
|
// this.dom = 0;
|
|
// this.label = 0;
|
|
// this.parent = 0;
|
|
// this.ancestor = 0;
|
|
// }
|
|
//
|
|
// This avoids GC work in V8, and it allows us to release the memory for
|
|
// intermediate values that are only needed while computing the dominators.
|
|
|
|
// Inputs.
|
|
this.N_ = 0; // Number of nodes.
|
|
this.E_ = 0; // Number of edges.
|
|
this.strings_ = null;
|
|
this.name_ = null;
|
|
this.class_ = null;
|
|
this.shallowSize_ = null;
|
|
this.firstSuccessor_ = null;
|
|
this.successors_ = null;
|
|
this.successorName_ = null;
|
|
|
|
// Outputs.
|
|
this.shallowSizeSum_ = 0;
|
|
this.firstPredecessor_ = null;
|
|
this.predecessors_ = null;
|
|
this.predecessorName_ = null;
|
|
this.retainedSize_ = null;
|
|
this.ownedSize_ = null;
|
|
this.successorsOwnedSize_ = null;
|
|
this.dom_ = null;
|
|
this.domHead_ = null;
|
|
this.domNext_ = null;
|
|
this.mergedDomHead_ = null;
|
|
this.mergedDomNext_ = null;
|
|
|
|
// Intermediates.
|
|
this.Nconnected_ = 0; // Number of nodes reachable from root.
|
|
this.vertex_ = null;
|
|
this.semi_ = null;
|
|
this.parent_ = null;
|
|
this.ancestor_ = null;
|
|
this.label_ = null;
|
|
this.bucket_ = null;
|
|
}
|
|
|
|
// Load a graph in V8 heap profile format from parsed JSON `data`.
|
|
Graph.prototype.loadV8Profile = function(data) {
|
|
console.log("Building successors...");
|
|
|
|
const N = data.snapshot.node_count;
|
|
const E = data.snapshot.edge_count;
|
|
|
|
const firstSuccessor = new Uint32Array(N + 2);
|
|
const successors = new Uint32Array(E);
|
|
const successorName = new Uint32Array(E);
|
|
|
|
const name = new Uint32Array(N + 1);
|
|
const clazz = new Array(N + 1);
|
|
const shallowSize = new Uint32Array(N + 1);
|
|
let shallowSizeSum = 0;
|
|
|
|
const node_stride = data.snapshot.meta.node_fields.length;
|
|
const node_type_offset = data.snapshot.meta.node_fields.indexOf("type");
|
|
const node_name_offset = data.snapshot.meta.node_fields.indexOf("name");
|
|
const node_id_offset = data.snapshot.meta.node_fields.indexOf("id");
|
|
const node_size_offset = data.snapshot.meta.node_fields.indexOf("self_size");
|
|
const node_edge_count_offset = data.snapshot.meta.node_fields.indexOf("edge_count");
|
|
|
|
const edge_stride = data.snapshot.meta.edge_fields.length;
|
|
const edge_type_offset = data.snapshot.meta.edge_fields.indexOf("type");
|
|
const edge_name_or_index_offset = data.snapshot.meta.edge_fields.indexOf("name_or_index");
|
|
const edge_target_offset = data.snapshot.meta.edge_fields.indexOf("to_node");
|
|
const edge_type_property = data.snapshot.meta.edge_types[0].indexOf("property");
|
|
|
|
let nextSuccessorIndex = 0;
|
|
let edge_cursor = 0;
|
|
let i = 1;
|
|
for (let node_cursor = 0;
|
|
node_cursor < data.nodes.length;
|
|
node_cursor += node_stride) {
|
|
let type = data.nodes[node_cursor + node_type_offset];
|
|
let node_name = data.nodes[node_cursor + node_name_offset];
|
|
let id = data.nodes[node_cursor + node_id_offset];
|
|
let self_size = data.nodes[node_cursor + node_size_offset];
|
|
let edge_count = data.nodes[node_cursor + node_edge_count_offset];
|
|
|
|
name[i] = node_name;
|
|
clazz[i] = data.snapshot.meta.node_types[0][type];
|
|
shallowSize[i] = self_size;
|
|
shallowSizeSum += self_size;
|
|
firstSuccessor[i] = nextSuccessorIndex;
|
|
|
|
for (let j = 0; j < edge_count; j++) {
|
|
let edge_type = data.edges[edge_cursor + edge_type_offset];
|
|
let edge_name_or_index = data.edges[edge_cursor + edge_name_or_index_offset];
|
|
let edge_to_node = (data.edges[edge_cursor + edge_target_offset] / node_stride) + 1;
|
|
edge_cursor += edge_stride;
|
|
|
|
if (edge_to_node < 1 || edge_to_node > N) {
|
|
throw "Invalid edge target";
|
|
}
|
|
|
|
successors[nextSuccessorIndex] = edge_to_node;
|
|
|
|
if (edge_type == edge_type_property) {
|
|
successorName[nextSuccessorIndex] = edge_name_or_index;
|
|
}
|
|
|
|
nextSuccessorIndex++;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
firstSuccessor[N + 1] = nextSuccessorIndex;
|
|
|
|
if (i != (N + 1)) {
|
|
throw "Incorrect node_count!";
|
|
}
|
|
if (nextSuccessorIndex != E) {
|
|
throw "Incorrect edge_count!";
|
|
}
|
|
|
|
this.N_ = N;
|
|
this.E_ = E;
|
|
this.strings_ = data.strings;
|
|
this.firstSuccessor_ = firstSuccessor;
|
|
this.successorName_ = successorName;
|
|
this.successors_ = successors;
|
|
this.name_ = name;
|
|
this.class_ = clazz;
|
|
this.shallowSize_ = shallowSize;
|
|
this.shallowSizeSum_ = shallowSizeSum;
|
|
};
|
|
|
|
// Load a graph in Dart heap snapshot format from ArrayBuffer `data`.
|
|
Graph.prototype.loadDartHeapSnapshot = function(data) {
|
|
console.log("Building successors...");
|
|
|
|
let stream = new Stream(data);
|
|
|
|
for (let i = 0; i < 8; i++) {
|
|
if (stream.byte() != 'dartheap'.charCodeAt(i)) {
|
|
throw "Wrong format identifier."
|
|
}
|
|
}
|
|
|
|
stream.uleb128(); // Flags.
|
|
stream.utf8(); // Name.
|
|
stream.uleb128(); // Shallow size (used).
|
|
stream.uleb128(); // Capacity.
|
|
stream.uleb128(); // External size.
|
|
|
|
const strings = new Array();
|
|
strings.push("???");
|
|
|
|
const classCount = stream.uleb128();
|
|
const classNames = new Uint32Array(classCount + 1);
|
|
classNames[0] = strings.length;
|
|
strings.push("Root");
|
|
const classEdges = new Array(classCount + 1);
|
|
for (let cid = 1; cid <= classCount; cid++) {
|
|
stream.uleb128(); // Flags.
|
|
classNames[cid] = strings.length;
|
|
strings.push(stream.utf8()); // Class name.
|
|
stream.utf8(); // Library name.
|
|
stream.utf8(); // Library name.
|
|
stream.utf8(); // Reserved.
|
|
const fieldCount = stream.uleb128();
|
|
const edgeNames = new Array(fieldCount + 1);
|
|
classEdges[cid] = edgeNames;
|
|
for (let j = 0; j < fieldCount; j++) {
|
|
stream.uleb128(); // Flags.
|
|
const fieldIndex = stream.uleb128();
|
|
const fieldName = stream.utf8();
|
|
stream.utf8(); // Reserved.
|
|
|
|
edgeNames[fieldIndex] = strings.length;
|
|
strings.push(fieldName);
|
|
}
|
|
}
|
|
|
|
const E = stream.uleb128(); // Reference count.
|
|
const N = stream.uleb128(); // Object count.
|
|
|
|
const firstSuccessor = new Uint32Array(N + 2);
|
|
const successors = new Uint32Array(E);
|
|
const successorName = new Uint32Array(E);
|
|
const name = new Uint32Array(N + 1);
|
|
const clazz = new Array(N + 1);
|
|
const shallowSize = new Uint32Array(N + 1);
|
|
|
|
name[0] = strings.length;
|
|
strings.push("<omitted-object>");
|
|
clazz[0] = "<omitted-object>";
|
|
const unknownEdge = strings.length;
|
|
strings.push("<unknown>");
|
|
|
|
let shallowSizeSum = 0;
|
|
let nextSuccessorIndex = 0;
|
|
for (let i = 1; i <= N; i++) {
|
|
const cid = stream.uleb128();
|
|
clazz[i] = strings[classNames[cid]];
|
|
const objShallowSize = stream.uleb128();
|
|
shallowSize[i] = objShallowSize;
|
|
shallowSizeSum += objShallowSize;
|
|
|
|
const tag = stream.uleb128();
|
|
switch (tag) {
|
|
case 0: // NoData
|
|
name[i] = classNames[cid];
|
|
break;
|
|
case 1: // NullData
|
|
name[i] = strings.length;
|
|
strings.push("null");
|
|
break;
|
|
case 2: // BoolData
|
|
name[i] = strings.length;
|
|
strings.push(stream.uleb128() == 0 ? "false" : "true");
|
|
break;
|
|
case 3: // IntegerData
|
|
name[i] = stream.sleb128();
|
|
break;
|
|
case 4: // DoubleData
|
|
name[i] = stream.float64();
|
|
break;
|
|
case 5: // Latin1StringData
|
|
stream.uleb128(); // Full length.
|
|
name[i] = strings.length;
|
|
strings.push(stream.latin1());
|
|
break;
|
|
case 6: // Utf16StringData
|
|
stream.uleb128(); // Full length.
|
|
name[i] = strings.length;
|
|
strings.push(stream.utf16());
|
|
break;
|
|
case 7: // LengthData
|
|
name[i] = strings.length;
|
|
strings.push(strings[classNames[cid]] + "(" + stream.uleb128() + ")");
|
|
break;
|
|
case 8: // NameData
|
|
name[i] = strings.length;
|
|
strings.push(stream.utf8());
|
|
break;
|
|
default:
|
|
throw "Unknown tag " + tag;
|
|
}
|
|
|
|
firstSuccessor[i] = nextSuccessorIndex;
|
|
const edge_count = stream.uleb128();
|
|
for (let j = 0; j < edge_count; j++) {
|
|
successors[nextSuccessorIndex] = stream.uleb128();
|
|
successorName[nextSuccessorIndex] = unknownEdge;
|
|
const edgeNames = classEdges[cid];
|
|
if (edgeNames !== undefined) {
|
|
const edgeName = classEdges[cid][j];
|
|
if (edgeName !== undefined) {
|
|
successorName[nextSuccessorIndex] = edgeName;
|
|
}
|
|
}
|
|
nextSuccessorIndex++;
|
|
}
|
|
}
|
|
firstSuccessor[N + 1] = nextSuccessorIndex;
|
|
|
|
if (nextSuccessorIndex != E) {
|
|
throw "Incorrect edge_count!";
|
|
}
|
|
|
|
const externalPropertyCount = stream.uleb128();
|
|
for (let i = 0; i < externalPropertyCount; i++) {
|
|
let object = stream.uleb128();
|
|
let externalSize = stream.uleb128();
|
|
stream.utf8(); // Name.
|
|
shallowSize[object] += externalSize;
|
|
shallowSizeSum += externalSize;
|
|
}
|
|
|
|
this.N_ = N;
|
|
this.E_ = E;
|
|
this.strings_ = strings;
|
|
this.firstSuccessor_ = firstSuccessor;
|
|
this.successorName_ = successorName;
|
|
this.successors_ = successors;
|
|
this.name_ = name;
|
|
this.class_ = clazz;
|
|
this.shallowSize_ = shallowSize;
|
|
this.shallowSizeSum_ = shallowSizeSum;
|
|
};
|
|
|
|
// Compute the graph's dominator tree and the retained size of each vertex.
|
|
//
|
|
// If `rewriteForOwners` is true, for each vertex that has an "owner" edge,
|
|
// replace all edges to the vertex with an edge from the owner to the vertex.
|
|
// This can be the graph more hierarchical and reveal more structure in the
|
|
// dominator tree.
|
|
Graph.prototype.compute = function(rewriteForOwners) {
|
|
this.computePredecessors();
|
|
if (rewriteForOwners) {
|
|
this.rewriteEdgesForOwners();
|
|
}
|
|
this.computePreorder(1);
|
|
this.computeDominators();
|
|
this.computeOwnedSizes();
|
|
this.computeRetainedSizes();
|
|
this.linkDominatorChildren();
|
|
this.sortDominatorChildren();
|
|
this.mergeDominatorSiblings(1);
|
|
};
|
|
|
|
Graph.prototype.computePredecessors = function() {
|
|
console.log("Building predecessors...");
|
|
|
|
const N = this.N_;
|
|
const E = this.E_;
|
|
const firstSuccessor = this.firstSuccessor_;
|
|
const successors = this.successors_;
|
|
const successorName = this.successorName_;
|
|
const firstPredecessor = new Uint32Array(N + 2);
|
|
const predecessors = new Uint32Array(E);
|
|
const predecessorName = new Uint32Array(E);
|
|
|
|
const predecessorCount = new Uint32Array(N + 1);
|
|
for (let i = 1; i <= N; i++) {
|
|
let startSuccessorIndex = firstSuccessor[i];
|
|
let limitSuccessorIndex = firstSuccessor[i + 1];
|
|
for (let successorIndex = startSuccessorIndex;
|
|
successorIndex < limitSuccessorIndex;
|
|
successorIndex++) {
|
|
let successor = successors[successorIndex];
|
|
if (successor == 0) continue; // Omitted object.
|
|
predecessorCount[successor]++;
|
|
}
|
|
}
|
|
|
|
let nextPredecessorIndex = 0;
|
|
for (let i = 1; i <= N; i++) {
|
|
firstPredecessor[i] = nextPredecessorIndex;
|
|
nextPredecessorIndex += predecessorCount[i];
|
|
}
|
|
firstPredecessor[N + 1] = nextPredecessorIndex;
|
|
|
|
for (let i = 1; i <= N; i++) {
|
|
let startSuccessorIndex = firstSuccessor[i];
|
|
let limitSuccessorIndex = firstSuccessor[i + 1];
|
|
for (let successorIndex = startSuccessorIndex;
|
|
successorIndex < limitSuccessorIndex;
|
|
successorIndex++) {
|
|
let successor = successors[successorIndex];
|
|
if (successor == 0) continue; // Omitted object.
|
|
let count = --predecessorCount[successor];
|
|
let predecessorIndex = firstPredecessor[successor] + count;
|
|
predecessors[predecessorIndex] = i;
|
|
predecessorName[predecessorIndex] = successorName[successorIndex];
|
|
}
|
|
}
|
|
|
|
this.firstPredecessor_ = firstPredecessor;
|
|
this.predecessors_ = predecessors;
|
|
this.predecessorName_ = predecessorName;
|
|
};
|
|
|
|
Graph.prototype.rewriteEdgesForOwners = function() {
|
|
console.log("Rewriting edges for owners...");
|
|
|
|
// Rewrite some edges to make the graph more hierarchical.
|
|
// If there is an edge A.owner -> B,
|
|
// - remove all edges to A, and
|
|
// - add edge B.<unnamed> -> A.
|
|
|
|
const N = this.N_;
|
|
const E = this.E_;
|
|
const firstSuccessor = this.firstSuccessor_;
|
|
const successors = this.successors_;
|
|
const successorName = this.successorName_;
|
|
const firstPredecessor = this.firstPredecessor_;
|
|
const predecessors = this.predecessors_;
|
|
const predecessorName = this.predecessorName_;
|
|
const owners = new Uint32Array(N + 1);
|
|
const owneeCount = new Uint32Array(N + 1);
|
|
|
|
// Identify owner.
|
|
for (let i = 1; i <= N; i++) {
|
|
let cls = this.class_[i];
|
|
let ownerEdgeName;
|
|
|
|
if (cls == "Class") { // Dart VM
|
|
ownerEdgeName = "library_";
|
|
} else if (cls == "PatchClass") { // Dart VM
|
|
ownerEdgeName = "patched_class_";
|
|
} else if (cls == "Function") { // Dart VM
|
|
ownerEdgeName = "owner_";
|
|
} else if (cls == "Field") { // Dart VM
|
|
ownerEdgeName = "owner_";
|
|
} else if (cls == "Code") { // Dart VM
|
|
ownerEdgeName = "owner_";
|
|
} else if (cls == "ICData") { // Dart VM
|
|
ownerEdgeName = "owner_";
|
|
} else if (cls == "Method") { // Primordial Soup
|
|
ownerEdgeName = "mixin";
|
|
} else if (cls.startsWith("InstanceMixin`")) { // Primordial Soup
|
|
ownerEdgeName = "_enclosingMixin";
|
|
} else if (cls.startsWith("ClassMixin`")) { // Primordial Soup
|
|
ownerEdgeName = "_instanceMixin";
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
let startSuccessorIndex = firstSuccessor[i];
|
|
let limitSuccessorIndex = firstSuccessor[i + 1];
|
|
for (let successorIndex = startSuccessorIndex;
|
|
successorIndex < limitSuccessorIndex;
|
|
successorIndex++) {
|
|
let edge = this.strings_[this.successorName_[successorIndex]];
|
|
if (edge == ownerEdgeName) {
|
|
let owner = successors[successorIndex];
|
|
owners[i] = owner;
|
|
owneeCount[owner]++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove successors if the target has an owner.
|
|
// Allocate space for extra successors added to owners.
|
|
const newSuccessors = new Uint32Array(E);
|
|
const newSuccessorName = new Uint32Array(E);
|
|
let newSuccessorIndex = 0;
|
|
for (let i = 1; i <= N; i++) {
|
|
let startSuccessorIndex = firstSuccessor[i];
|
|
let limitSuccessorIndex = firstSuccessor[i + 1];
|
|
firstSuccessor[i] = newSuccessorIndex;
|
|
for (let successorIndex = startSuccessorIndex;
|
|
successorIndex < limitSuccessorIndex;
|
|
successorIndex++) {
|
|
let successor = successors[successorIndex];
|
|
let name = successorName[successorIndex];
|
|
|
|
if (owners[successor] != 0) {
|
|
// Drop successor.
|
|
} else {
|
|
newSuccessors[newSuccessorIndex] = successor;
|
|
newSuccessorName[newSuccessorIndex] = name;
|
|
newSuccessorIndex++;
|
|
}
|
|
}
|
|
newSuccessorIndex += owneeCount[i];
|
|
}
|
|
firstSuccessor[N + 1] = newSuccessorIndex;
|
|
|
|
// Remove predecessors if the target has an owner.
|
|
// Add the owner as a predecessor.
|
|
// Add extra successors for owner.
|
|
for (let i = 1; i <= N; i++) {
|
|
let owner = owners[i];
|
|
if (owner == 0) {
|
|
continue;
|
|
}
|
|
|
|
let startPredecessorIndex = firstPredecessor[i];
|
|
let limitPredecessorIndex = firstPredecessor[i + 1];
|
|
for (let predecessorIndex = startPredecessorIndex;
|
|
predecessorIndex < limitPredecessorIndex;
|
|
predecessorIndex++) {
|
|
predecessors[predecessorIndex] = 0;
|
|
}
|
|
predecessors[startPredecessorIndex] = owner;
|
|
|
|
let nextSuccessorIndex = firstSuccessor[owner + 1] - owneeCount[owner];
|
|
newSuccessors[nextSuccessorIndex] = i;
|
|
owneeCount[owner]--;
|
|
}
|
|
|
|
this.successors_ = newSuccessors;
|
|
this.successorName_ = newSuccessorName;
|
|
};
|
|
|
|
// Thomas Lengauer and Robert Endre Tarjan. 1979. A fast algorithm for finding
|
|
// dominators in a flowgraph. ACM Trans. Program. Lang. Syst. 1, 1 (January
|
|
// 1979), 121-141. DOI: https://doi.org/10.1145/357062.357071
|
|
|
|
Graph.prototype.computePreorder = function(root) {
|
|
console.log("Computing preorder...");
|
|
|
|
// Lengauer and Tarjan Step 1.
|
|
const N = this.N_;
|
|
const firstSuccessor = this.firstSuccessor_;
|
|
const successors = this.successors_;
|
|
|
|
const semi = new Uint32Array(N + 1);
|
|
const vertex = new Uint32Array(N + 1);
|
|
const ancestor = new Uint32Array(N + 1);
|
|
const parent = new Uint32Array(N + 1);
|
|
const label = new Uint32Array(N + 1);
|
|
|
|
let preorderNumber = 0;
|
|
|
|
let stackNodes = new Uint32Array(N + 1);
|
|
let stackEdges = new Uint32Array(N + 1);
|
|
let stackTop = 0;
|
|
|
|
// Push root.
|
|
preorderNumber++;
|
|
vertex[preorderNumber] = root;
|
|
semi[root] = preorderNumber;
|
|
label[root] = root;
|
|
ancestor[root] = 0;
|
|
stackNodes[stackTop] = root;
|
|
stackEdges[stackTop] = firstSuccessor[root];
|
|
|
|
while (stackTop >= 0) {
|
|
let v = stackNodes[stackTop];
|
|
let e = stackEdges[stackTop];
|
|
|
|
if (e < firstSuccessor[v + 1]) {
|
|
// Visit next successor.
|
|
let w = successors[e];
|
|
e++;
|
|
stackEdges[stackTop] = e;
|
|
|
|
if (w == 0) {
|
|
// Omitted object.
|
|
} else if (semi[w] != 0) {
|
|
// Already visited.
|
|
} else {
|
|
parent[w] = v;
|
|
|
|
preorderNumber++;
|
|
vertex[preorderNumber] = w;
|
|
semi[w] = preorderNumber;
|
|
label[w] = w;
|
|
ancestor[w] = 0;
|
|
|
|
// Push successor.
|
|
stackTop++;
|
|
stackNodes[stackTop] = w;
|
|
stackEdges[stackTop] = firstSuccessor[w];
|
|
}
|
|
} else {
|
|
// No more successors: pop.
|
|
stackTop--;
|
|
}
|
|
}
|
|
|
|
this.Nconnected_ = preorderNumber;
|
|
if (this.Nconnected_ != N) {
|
|
console.log("Graph is not fully connected: " + this.Nconnected_ +
|
|
" nodes are reachable, but graph has " + this.N_ + " nodes");
|
|
}
|
|
|
|
this.semi_ = semi;
|
|
this.vertex_ = vertex;
|
|
this.ancestor_ = ancestor;
|
|
this.parent_ = parent;
|
|
this.label_ = label;
|
|
};
|
|
|
|
Graph.prototype.computeDominators = function() {
|
|
console.log("Computing dominator tree...");
|
|
|
|
const N = this.N_;
|
|
const Nconnected = this.Nconnected_;
|
|
const firstPredecessor = this.firstPredecessor_;
|
|
const predecessors = this.predecessors_;
|
|
const vertex = this.vertex_;
|
|
const semi = this.semi_;
|
|
const bucket = new Array(N + 1);
|
|
const parent = this.parent_;
|
|
const dom = new Uint32Array(N + 1);
|
|
const stackNode = new Uint32Array(N + 1);
|
|
const stackState = new Uint8Array(N + 1);
|
|
|
|
for (let i = Nconnected; i > 1; i--) {
|
|
let w = vertex[i];
|
|
|
|
// Lengauer and Tarjan Step 2.
|
|
let startPredecessorIndex = firstPredecessor[w];
|
|
let limitPredecessorIndex = firstPredecessor[w + 1];
|
|
for (let predecessorIndex = startPredecessorIndex;
|
|
predecessorIndex < limitPredecessorIndex;
|
|
predecessorIndex++) {
|
|
let v = predecessors[predecessorIndex];
|
|
|
|
if (semi[v] == 0) {
|
|
// The predecessor was not reachable from the root: ignore
|
|
// this edge.
|
|
continue;
|
|
}
|
|
|
|
let u = this.forestEval(v, stackNode, stackState);
|
|
if (semi[u] < semi[w]) {
|
|
semi[w] = semi[u]
|
|
}
|
|
}
|
|
|
|
let z = vertex[semi[w]];
|
|
let b = bucket[z];
|
|
if (b == null) {
|
|
b = new Array();
|
|
bucket[z] = b;
|
|
}
|
|
b.push(w);
|
|
this.forestLink(parent[w], w);
|
|
|
|
// Lengauer and Tarjan Step 3.
|
|
z = parent[w];
|
|
b = bucket[z];
|
|
bucket[z] = null;
|
|
if (b != null) {
|
|
for (let j = 0; j < b.length; j++) {
|
|
let v = b[j];
|
|
let u = this.forestEval(v, stackNode, stackState);
|
|
dom[v] = semi[u] < semi[v] ? u : parent[w];
|
|
}
|
|
}
|
|
}
|
|
|
|
this.ancestor_ = null;
|
|
this.label_ = null;
|
|
this.parent_ = null;
|
|
this.bucket_ = null;
|
|
|
|
// Lengauer and Tarjan Step 4.
|
|
for (let i = 2; i <= Nconnected; i++) {
|
|
let w = vertex[i];
|
|
if (dom[w] != vertex[semi[w]]) {
|
|
dom[w] = dom[dom[w]];
|
|
}
|
|
}
|
|
dom[vertex[1]] = 0;
|
|
|
|
this.semi_ = null;
|
|
this.dom_ = dom;
|
|
};
|
|
|
|
Graph.prototype.forestEval = function(v, stackNode, stackState) {
|
|
if (this.ancestor_[v] == 0) {
|
|
return v;
|
|
} else {
|
|
const ancestor = this.ancestor_;
|
|
const semi = this.semi_;
|
|
const label = this.label_;
|
|
{
|
|
// Inlined 'compress' with an explicit stack to prevent JS stack
|
|
// overflow.
|
|
let top = 0;
|
|
stackNode[top] = v;
|
|
stackState[top] = 0;
|
|
while (top >= 0) {
|
|
let v = stackNode[top];
|
|
let state = stackState[top];
|
|
if (state == 0) {
|
|
if (ancestor[ancestor[v]] != 0) {
|
|
stackState[top] = 1;
|
|
// Recurse with ancestor[v]
|
|
top++;
|
|
stackNode[top] = ancestor[v];
|
|
stackState[top] = 0;
|
|
} else {
|
|
top--;
|
|
}
|
|
} else {
|
|
if (semi[label[ancestor[v]]] < semi[label[v]]) {
|
|
label[v] = label[ancestor[v]];
|
|
}
|
|
ancestor[v] = ancestor[ancestor[v]];
|
|
top--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (semi[label[ancestor[v]]] >= semi[label[v]]) {
|
|
return label[v];
|
|
} else {
|
|
return label[ancestor[v]];
|
|
}
|
|
}
|
|
};
|
|
|
|
Graph.prototype.forestLink = function(v, w) {
|
|
this.ancestor_[w] = v;
|
|
};
|
|
|
|
Graph.prototype.computeOwnedSizes = function() {
|
|
console.log("Computing in-degree(1) groups...");
|
|
|
|
const N = this.N_;
|
|
const E = this.E_;
|
|
const Nconnected = this.Nconnected_;
|
|
const shallowSize = this.shallowSize_;
|
|
const vertex = this.vertex_;
|
|
const successors = this.successors_;
|
|
const firstSuccessor = this.firstSuccessor_;
|
|
const predecessors = this.predecessors_;
|
|
const firstPredecessor = this.firstPredecessor_;
|
|
const SENTINEL = 0;
|
|
const ROOT = vertex[1];
|
|
const ownedSize = new Uint32Array(N + 1);
|
|
const successorsOwnedSize = new Uint32Array(E + 1);
|
|
|
|
for (let i = 0; i <= N; i++) {
|
|
ownedSize[i] = shallowSize[i];
|
|
}
|
|
|
|
for (let i = Nconnected; i > 1; i--) {
|
|
const w = vertex[i];
|
|
|
|
let onlyPred = SENTINEL;
|
|
const startPred = firstPredecessor[w];
|
|
const limitPred = firstPredecessor[w + 1];
|
|
for (let predIndex = startPred; predIndex < limitPred; predIndex++) {
|
|
const v = predecessors[predIndex];
|
|
if (v == w) {
|
|
// Ignore self-predecessor.
|
|
} else if (onlyPred == SENTINEL) {
|
|
onlyPred = v;
|
|
} else if (onlyPred == v) {
|
|
// Repeated predecessor.
|
|
} else {
|
|
// Multiple predecessors.
|
|
onlyPred = SENTINEL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((onlyPred != SENTINEL) && (onlyPred != ROOT)) {
|
|
let size = ownedSize[w];
|
|
ownedSize[w] = 0;
|
|
ownedSize[onlyPred] += size;
|
|
|
|
const startSucc = firstSuccessor[onlyPred];
|
|
const limitSucc = firstSuccessor[onlyPred + 1];
|
|
for (let succIndex = startSucc; succIndex < limitSucc; succIndex++) {
|
|
if (successors[succIndex] == w) {
|
|
successorsOwnedSize[succIndex] = size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.ownedSize_ = ownedSize;
|
|
this.successorsOwnedSize_ = successorsOwnedSize;
|
|
};
|
|
|
|
Graph.prototype.computeRetainedSizes = function() {
|
|
console.log("Computing retained sizes...");
|
|
|
|
const N = this.N_;
|
|
const Nconnected = this.Nconnected_;
|
|
const vertex = this.vertex_;
|
|
const dom = this.dom_;
|
|
const shallowSize = this.shallowSize_;
|
|
const retainedSize = new Uint32Array(N + 1);
|
|
|
|
let shallowSum = 0;
|
|
|
|
for (let i = 1; i <= Nconnected; i++) {
|
|
let w = vertex[i];
|
|
let shallow = shallowSize[w];
|
|
retainedSize[w] = shallow;
|
|
shallowSum += shallow;
|
|
}
|
|
|
|
for (let i = Nconnected; i > 1; i--) {
|
|
let w = vertex[i];
|
|
retainedSize[dom[w]] += retainedSize[w];
|
|
}
|
|
|
|
if (retainedSize[vertex[1]] != shallowSum) {
|
|
console.log("Retained size mismatch: root retains " + retainedSize[vertex[1]] +
|
|
" but shallow sizes sum to " + shallowSum);
|
|
}
|
|
|
|
this.retainedSize_ = retainedSize;
|
|
};
|
|
|
|
Graph.prototype.linkDominatorChildren = function() {
|
|
console.log("Linking dominator tree children...");
|
|
|
|
const N = this.N_;
|
|
const Nconnected = this.Nconnected_;
|
|
|
|
const vertex = this.vertex_;
|
|
const dom = this.dom_;
|
|
const head = new Uint32Array(N + 1);
|
|
const next = new Uint32Array(N + 1);
|
|
|
|
for (let i = 2; i <= Nconnected; i++) {
|
|
let child = vertex[i];
|
|
let parent = dom[child];
|
|
next[child] = head[parent];
|
|
head[parent] = child;
|
|
}
|
|
|
|
this.domHead_ = head;
|
|
this.domNext_ = next;
|
|
};
|
|
|
|
// Merge the given lists according to the given key in ascending order.
|
|
// Returns the head of the merged list.
|
|
function mergeSorted(head1, head2, next, key) {
|
|
let head = head1;
|
|
let beforeInsert = 0;
|
|
let afterInsert = head1;
|
|
let startInsert = head2;
|
|
|
|
while (startInsert != 0) {
|
|
while ((afterInsert != 0) &&
|
|
(key[afterInsert] <= key[startInsert])) {
|
|
beforeInsert = afterInsert;
|
|
afterInsert = next[beforeInsert];
|
|
}
|
|
let endInsert = startInsert;
|
|
let peek = next[endInsert];
|
|
|
|
while ((peek != 0) && (key[peek] < key[afterInsert])) {
|
|
endInsert = peek;
|
|
peek = next[endInsert];
|
|
}
|
|
|
|
if (beforeInsert == 0) {
|
|
head = startInsert;
|
|
} else {
|
|
next[beforeInsert] = startInsert;
|
|
}
|
|
next[endInsert] = afterInsert;
|
|
|
|
startInsert = peek;
|
|
beforeInsert = endInsert;
|
|
}
|
|
|
|
return head;
|
|
}
|
|
|
|
Graph.prototype.sortDominatorChildren = function() {
|
|
console.log("Sorting dominator tree children...");
|
|
|
|
const N = this.N_;
|
|
const Nconnected = this.Nconnected_;
|
|
const cids = this.class_;
|
|
const head = this.domHead_;
|
|
const next = this.domNext_;
|
|
|
|
function sort(head) {
|
|
if (head == 0) return 0;
|
|
if (next[head] == 0) return head;
|
|
|
|
// Find the middle of the list.
|
|
let head1 = head;
|
|
let slow = head;
|
|
let fast = head;
|
|
while (next[fast] != 0 && next[next[fast]] != 0) {
|
|
slow = next[slow];
|
|
fast = next[next[fast]];
|
|
}
|
|
|
|
// Split the list in half.
|
|
let head2 = next[slow];
|
|
next[slow] = 0;
|
|
|
|
// Recursively sort the sublists and merge.
|
|
let newHead1 = sort(head1);
|
|
let newHead2 = sort(head2);
|
|
return mergeSorted(newHead1, newHead2, next, cids);
|
|
};
|
|
|
|
// Sort all list of dominator tree children by cid.
|
|
for (let parent = 1; parent <= N; parent++) {
|
|
head[parent] = sort(head[parent]);
|
|
}
|
|
};
|
|
|
|
Graph.prototype.mergeDominatorSiblings = function(root) {
|
|
console.log("Merging dominator tree siblings...");
|
|
|
|
const N = this.N_;
|
|
const cids = this.class_;
|
|
const head = new Uint32Array(this.domHead_);
|
|
const next = new Uint32Array(this.domNext_);
|
|
const workStack = new Uint32Array(N);
|
|
let workStackTop = 0;
|
|
|
|
function mergeChildrenAndSort(parent1, end) {
|
|
if (next[parent1] == end) return;
|
|
|
|
// Find the middle of the list.
|
|
let slow = parent1;
|
|
let fast = parent1;
|
|
while (next[fast] != end && next[next[fast]] != end) {
|
|
slow = next[slow];
|
|
fast = next[next[fast]];
|
|
}
|
|
|
|
let parent2 = next[slow];
|
|
|
|
// Recursively sort the sublists.
|
|
mergeChildrenAndSort(parent1, parent2);
|
|
mergeChildrenAndSort(parent2, end);
|
|
|
|
// Merge sorted sublists.
|
|
head[parent1] = mergeSorted(head[parent1], head[parent2], next, cids);
|
|
|
|
// Children moved to parent1.
|
|
head[parent2] = 0;
|
|
}
|
|
|
|
// Push root.
|
|
workStack[workStackTop++] = root;
|
|
|
|
while (workStackTop > 0) {
|
|
let parent = workStack[--workStackTop];
|
|
|
|
let child = head[parent];
|
|
while (child != 0) {
|
|
// Push child.
|
|
workStack[workStackTop++] = child;
|
|
|
|
// Find next sibling with a different cid.
|
|
let after = child;
|
|
while (after != 0 && cids[after] == cids[child]) {
|
|
after = next[after];
|
|
}
|
|
|
|
// From all the siblings between child and after, take their children,
|
|
// merge them and given to child.
|
|
mergeChildrenAndSort(child, after);
|
|
|
|
child = after;
|
|
}
|
|
}
|
|
|
|
this.mergedDomHead_ = head;
|
|
this.mergedDomNext_ = next;
|
|
};
|
|
|
|
Graph.prototype.getTotalSize = function() {
|
|
return this.shallowSizeSum_;
|
|
};
|
|
|
|
Graph.prototype.getRoot = function() {
|
|
return this.vertex_[1];
|
|
};
|
|
|
|
Graph.prototype.getAll = function() {
|
|
let nodes = new Array();
|
|
for (let v = 1; v <= this.N_; v++) {
|
|
nodes.push(v);
|
|
}
|
|
return nodes;
|
|
};
|
|
|
|
function removeDuplicates(array) {
|
|
let set = new Set(array);
|
|
let result = new Array();
|
|
for (let element of set) {
|
|
result.push(element);
|
|
}
|
|
return result
|
|
}
|
|
|
|
Graph.prototype.successorsOfDo = function(v, action) {
|
|
let cls = this.class_[v];
|
|
let startSuccessorIndex = this.firstSuccessor_[v];
|
|
let limitSuccessorIndex = this.firstSuccessor_[v + 1];
|
|
for (let successorIndex = startSuccessorIndex;
|
|
successorIndex < limitSuccessorIndex;
|
|
successorIndex++) {
|
|
let successor = this.successors_[successorIndex];
|
|
let edgeName = this.strings_[this.successorName_[successorIndex]];
|
|
let edgeOwnedSize = this.successorsOwnedSize_[successorIndex];
|
|
action(successor, cls + "::" + edgeName, edgeOwnedSize);
|
|
}
|
|
}
|
|
|
|
Graph.prototype.predecessorsOfDo = function(v, action) {
|
|
let startPredecessorIndex = this.firstPredecessor_[v];
|
|
let limitPredecessorIndex = this.firstPredecessor_[v + 1];
|
|
for (let predecessorIndex = startPredecessorIndex;
|
|
predecessorIndex < limitPredecessorIndex;
|
|
predecessorIndex++) {
|
|
let predecessor = this.predecessors_[predecessorIndex];
|
|
let cls = this.class_[predecessor];
|
|
let edgeName = this.strings_[this.predecessorName_[predecessorIndex]];
|
|
action(predecessor, cls + "::" + edgeName);
|
|
}
|
|
}
|
|
|
|
Graph.prototype.parentOf = function(v) {
|
|
return this.dom_[v];
|
|
};
|
|
|
|
Graph.prototype.dominatorChildrenOfDo = function(v, action) {
|
|
for (let w = this.domHead_[v]; w != 0; w = this.domNext_[w]) {
|
|
action(w);
|
|
}
|
|
};
|
|
|
|
Graph.prototype.nameOf = function(v) {
|
|
return this.strings_[this.name_[v]];
|
|
};
|
|
|
|
Graph.prototype.classOf = function(v) {
|
|
return this.class_[v];
|
|
};
|
|
|
|
Graph.prototype.shallowSizeOf = function(v) {
|
|
return this.shallowSize_[v];
|
|
};
|
|
|
|
Graph.prototype.retainedSizeOf = function(v) {
|
|
return this.retainedSize_[v];
|
|
};
|
|
|
|
Graph.prototype.setFrom = function(v) {
|
|
return [v];
|
|
};
|
|
|
|
Graph.prototype.shallowSizeOfSet = function(nodes) {
|
|
let sum = 0;
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
sum += this.shallowSize_[nodes[i]];
|
|
}
|
|
return sum;
|
|
};
|
|
|
|
Graph.prototype.ownedSizeOfSet = function(nodes) {
|
|
const ownedSize = this.ownedSize_;
|
|
let sum = 0;
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
let v = nodes[i];
|
|
sum += ownedSize[v];
|
|
}
|
|
return sum;
|
|
};
|
|
|
|
Graph.prototype.toggleMerge = function() {
|
|
return new MergedGraph(this);
|
|
};
|
|
|
|
function MergedGraph(graph) {
|
|
this.graph_ = graph;
|
|
}
|
|
|
|
MergedGraph.prototype.nameOf = function(v) {
|
|
const cids = this.graph_.class_;
|
|
const next = this.graph_.mergedDomNext_;
|
|
let count = 0;
|
|
let sibling = v;
|
|
while (sibling != 0 && cids[sibling] == cids[v]) {
|
|
count++;
|
|
sibling = next[sibling];
|
|
}
|
|
return count.toString() + " instances of " + this.graph_.class_[v];
|
|
};
|
|
|
|
MergedGraph.prototype.classOf = function(v) {
|
|
return this.graph_.class_[v];
|
|
};
|
|
|
|
MergedGraph.prototype.shallowSizeOf = function(v) {
|
|
const cids = this.graph_.class_;
|
|
const shallowSize = this.graph_.shallowSize_;
|
|
const next = this.graph_.mergedDomNext_;
|
|
let size = 0;
|
|
let sibling = v;
|
|
while (sibling != 0 && cids[sibling] == cids[v]) {
|
|
size += shallowSize[sibling];
|
|
sibling = next[sibling];
|
|
}
|
|
return size;
|
|
};
|
|
|
|
MergedGraph.prototype.retainedSizeOf = function(v) {
|
|
const cids = this.graph_.class_;
|
|
const retainedSize = this.graph_.retainedSize_;
|
|
const next = this.graph_.mergedDomNext_;
|
|
let size = 0;
|
|
let sibling = v;
|
|
while (sibling != 0 && cids[sibling] == cids[v]) {
|
|
size += retainedSize[sibling];
|
|
sibling = next[sibling];
|
|
}
|
|
return size;
|
|
};
|
|
|
|
MergedGraph.prototype.setFrom = function(v) {
|
|
const cids = this.graph_.class_;
|
|
const next = this.graph_.mergedDomNext_;
|
|
let set = new Array();
|
|
let sibling = v;
|
|
while (sibling != 0 && cids[sibling] == cids[v]) {
|
|
set.push(sibling);
|
|
sibling = next[sibling];
|
|
}
|
|
return set;
|
|
};
|
|
|
|
MergedGraph.prototype.parentOf = function(v) {
|
|
// N.B.: Not dom_[v], which might not be the representative element of the
|
|
// merged group.
|
|
const N = this.graph_.N_;
|
|
const head = this.graph_.mergedDomHead_;
|
|
const next = this.graph_.mergedDomNext_;
|
|
for (let parent = 1; parent <= N; parent++) {
|
|
for (let child = head[parent]; child != 0; child = next[child]) {
|
|
if (child == v) {
|
|
return parent;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
MergedGraph.prototype.dominatorChildrenOfDo = function(v, action) {
|
|
const next = this.graph_.mergedDomNext_;
|
|
const cids = this.graph_.class_;
|
|
let prev = 0;
|
|
let child = this.graph_.mergedDomHead_[v];
|
|
// Walk the list of children and look for the representative objects, i.e.
|
|
// the first sibling of each cid.
|
|
while (child != 0) {
|
|
if (prev == 0 || cids[prev] != cids[child]) {
|
|
action(child);
|
|
}
|
|
prev = child;
|
|
child = next[child];
|
|
}
|
|
};
|
|
|
|
MergedGraph.prototype.getTotalSize = function() {
|
|
return this.graph_.shallowSizeSum_;
|
|
};
|
|
|
|
MergedGraph.prototype.toggleMerge = function() {
|
|
return this.graph_;
|
|
};
|
|
|
|
function Stream(bytes) {
|
|
this.bytes_ = new Uint8Array(bytes);
|
|
this.position_ = 0;
|
|
}
|
|
|
|
Stream.prototype.byte = function() {
|
|
const position = this.position_;
|
|
const bytes = this.bytes_;
|
|
if ((position >= 0) && (position < bytes.length)) {
|
|
const result = bytes[position];
|
|
this.position_ = position + 1;
|
|
return result;
|
|
}
|
|
throw "Attempt to read past end of stream";
|
|
};
|
|
|
|
Stream.prototype.uleb128 = function() {
|
|
let result = 0;
|
|
let shift = 0;
|
|
for (;;) {
|
|
const part = this.byte();
|
|
result |= (part & 0x7F) << shift;
|
|
if ((part & 0x80) == 0) {
|
|
break;
|
|
}
|
|
shift += 7;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
Stream.prototype.sleb128 = function() {
|
|
let result = 0;
|
|
let shift = 0;
|
|
for (;;) {
|
|
const part = this.byte();
|
|
result |= (part & 0x7F) << shift;
|
|
shift += 7;
|
|
if ((part & 0x80) == 0) {
|
|
if ((part & 0x40) != 0) {
|
|
result |= (-1 << shift);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
Stream.prototype.float64 = function() {
|
|
const buffer = new ArrayBuffer(8);
|
|
const bytes = new Uint8Array(buffer);
|
|
for (let i = 0; i < 8; i++) {
|
|
bytes[i] = this.byte();
|
|
}
|
|
return new Float64Array(buffer)[0];
|
|
};
|
|
|
|
Stream.prototype.utf8 = function() {
|
|
const length = this.uleb128();
|
|
let result = '';
|
|
for (let i = 0; i < length; i++) {
|
|
// This is incorrect outside of ASCII, but good enough for our purpose
|
|
// since we're mostly interested in identifiers.
|
|
result += String.fromCharCode(this.byte());
|
|
}
|
|
// Can we force flattening of the rope string?
|
|
return result.toString();
|
|
};
|
|
|
|
Stream.prototype.latin1 = function() {
|
|
const length = this.uleb128();
|
|
let result = '';
|
|
for (let i = 0; i < length; i++) {
|
|
result += String.fromCharCode(this.byte());
|
|
}
|
|
// Can we force flattening of the rope string?
|
|
return result.toString();
|
|
};
|
|
|
|
Stream.prototype.utf16 = function() {
|
|
const length = this.uleb128();
|
|
let result = '';
|
|
for (let i = 0; i < length; i++) {
|
|
const lo = this.byte();
|
|
const hi = this.byte();
|
|
result += String.fromCharCode((hi << 8) | lo);
|
|
}
|
|
// Can we force flattening of the rope string?
|
|
return result.toString();
|
|
};
|
|
|
|
function hash(string) {
|
|
// Jenkin's one_at_a_time.
|
|
let h = string.length;
|
|
for (let i = 0; i < string.length; i++) {
|
|
h += string.charCodeAt(i);
|
|
h += h << 10;
|
|
h ^= h >> 6;
|
|
}
|
|
h += h << 3;
|
|
h ^= h >> 11;
|
|
h += h << 15;
|
|
return h;
|
|
}
|
|
|
|
function color(string) {
|
|
let hue = hash(string) % 360;
|
|
return "hsl(" + hue + ",90%,80%)";
|
|
}
|
|
|
|
function prettySize(size) {
|
|
if (size < 1024) return size + "B";
|
|
size /= 1024;
|
|
if (size < 1024) return size.toFixed(1) + "KiB";
|
|
size /= 1024;
|
|
if (size < 1024) return size.toFixed(1) + "MiB";
|
|
size /= 1024;
|
|
return size.toFixed(1) + "GiB";
|
|
}
|
|
|
|
function prettyPercent(fraction) {
|
|
return (fraction * 100).toFixed(1);
|
|
}
|
|
|
|
function createTreemapTile(graph, v, width, height, depth) {
|
|
let div = document.createElement("div");
|
|
div.className = "treemapTile";
|
|
div.style["background-color"] = color(graph.classOf(v));
|
|
div.ondblclick = function(event) {
|
|
event.stopPropagation();
|
|
if (depth == 0) {
|
|
let dom = graph.parentOf(v);
|
|
if (dom == 0) {
|
|
// Already at root.
|
|
} else {
|
|
showDominatorTree(graph, dom); // Zoom out.
|
|
}
|
|
} else {
|
|
showDominatorTree(graph, v); // Zoom in.
|
|
}
|
|
};
|
|
div.oncontextmenu = function(event) {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
showTables(graph.setFrom(v));
|
|
};
|
|
|
|
let left = 0;
|
|
let top = 0;
|
|
|
|
const kPadding = 5;
|
|
const kBorder = 1;
|
|
left += kPadding - kBorder;
|
|
top += kPadding - kBorder;
|
|
width -= 2 * kPadding;
|
|
height -= 2 * kPadding;
|
|
|
|
div.title =
|
|
graph.nameOf(v) +
|
|
" \nclass: " + graph.classOf(v) +
|
|
" \nretained: " + graph.retainedSizeOf(v) +
|
|
" \nshallow: " + graph.shallowSizeOf(v);
|
|
|
|
if (width < 10 || height < 10) {
|
|
// Too small: don't render label or children.
|
|
return div;
|
|
}
|
|
|
|
let label = graph.nameOf(v) + " [" + prettySize(graph.retainedSizeOf(v)) + "]";
|
|
div.appendChild(document.createTextNode(label));
|
|
const kLabelHeight = 13;
|
|
top += kLabelHeight;
|
|
height -= kLabelHeight;
|
|
|
|
if (depth > 2) {
|
|
// Too deep: don't render children.
|
|
return div;
|
|
}
|
|
if (width < 4 || height < 4) {
|
|
// Too small: don't render children.
|
|
return div;
|
|
}
|
|
|
|
let children = new Array();
|
|
graph.dominatorChildrenOfDo(v, function(c) {
|
|
// Size 0 children seem to confuse the layout algorithm (accumulating
|
|
// rounding errors?).
|
|
if (graph.retainedSizeOf(c) > 0) {
|
|
children.push(c);
|
|
}
|
|
});
|
|
children.sort(function (a, b) {
|
|
return graph.retainedSizeOf(b) - graph.retainedSizeOf(a);
|
|
});
|
|
|
|
const scale = width * height / graph.retainedSizeOf(v);
|
|
|
|
// Bruls M., Huizing K., van Wijk J.J. (2000) Squarified Treemaps. In: de
|
|
// Leeuw W.C., van Liere R. (eds) Data Visualization 2000. Eurographics.
|
|
// Springer, Vienna.
|
|
for (let rowStart = 0; // Index of first child in the next row.
|
|
rowStart < children.length;) {
|
|
// Prefer wider rectangles, the better to fit text labels.
|
|
const GOLDEN_RATIO = 1.61803398875;
|
|
let verticalSplit = (width / height) > GOLDEN_RATIO;
|
|
|
|
let space;
|
|
if (verticalSplit) {
|
|
space = height;
|
|
} else {
|
|
space = width;
|
|
}
|
|
|
|
let rowMin = graph.retainedSizeOf(children[rowStart]) * scale;
|
|
let rowMax = rowMin;
|
|
let rowSum = 0;
|
|
let lastRatio = 0;
|
|
|
|
let rowEnd; // One after index of last child in the next row.
|
|
for (rowEnd = rowStart; rowEnd < children.length; rowEnd++) {
|
|
let size = graph.retainedSizeOf(children[rowEnd]) * scale;
|
|
if (size < rowMin) rowMin = size;
|
|
if (size > rowMax) rowMax = size;
|
|
rowSum += size;
|
|
|
|
let ratio = Math.max((space * space * rowMax) / (rowSum * rowSum),
|
|
(rowSum * rowSum) / (space * space * rowMin));
|
|
if ((lastRatio != 0) && (ratio > lastRatio)) {
|
|
// Adding the next child makes the aspect ratios worse: remove it and
|
|
// add the row.
|
|
rowSum -= size;
|
|
break;
|
|
}
|
|
lastRatio = ratio;
|
|
}
|
|
|
|
let rowLeft = left;
|
|
let rowTop = top;
|
|
let rowSpace = rowSum / space;
|
|
|
|
for (let i = rowStart; i < rowEnd; i++) {
|
|
let child = children[i];
|
|
let size = graph.retainedSizeOf(child) * scale;
|
|
|
|
let childWidth;
|
|
let childHeight;
|
|
if (verticalSplit) {
|
|
childWidth = rowSpace;
|
|
childHeight = size / childWidth;
|
|
} else {
|
|
childHeight = rowSpace;
|
|
childWidth = size / childHeight;
|
|
}
|
|
|
|
let childDiv = createTreemapTile(graph, child, childWidth, childHeight, depth + 1);
|
|
childDiv.style.left = rowLeft + "px";
|
|
childDiv.style.top = rowTop + "px";
|
|
// Oversize the final div by kBorder to make the borders overlap.
|
|
childDiv.style.width = (childWidth + kBorder) + "px";
|
|
childDiv.style.height = (childHeight + kBorder) + "px";
|
|
div.appendChild(childDiv);
|
|
|
|
if (verticalSplit)
|
|
rowTop += childHeight;
|
|
else
|
|
rowLeft += childWidth;
|
|
}
|
|
|
|
if (verticalSplit) {
|
|
left += rowSpace;
|
|
width -= rowSpace;
|
|
} else {
|
|
top += rowSpace;
|
|
height -= rowSpace;
|
|
}
|
|
|
|
rowStart = rowEnd;
|
|
}
|
|
|
|
return div;
|
|
}
|
|
|
|
function showDominatorTree(graph, v) {
|
|
let title = document.createElement("span");
|
|
title.textContent = "Dominator Tree";
|
|
title.title =
|
|
"Click title to merge/unmerge by class.\n" +
|
|
"Double click a box to zoom in.\n" +
|
|
"Double click the outermost box to zoom out.\n" +
|
|
"Right click a box to view successor and predecessor tables.";
|
|
title.className = "nameCell actionCell";
|
|
title.onclick = function(event) { showDominatorTree(graph.toggleMerge(), v); };
|
|
|
|
let filler = document.createElement("span");
|
|
filler.style["flex-grow"] = 1;
|
|
|
|
let totalSize = document.createElement("span");
|
|
totalSize.className = "sizeCell";
|
|
totalSize.textContent = graph ? "" + graph.getTotalSize() : "0";
|
|
|
|
let totalPercent = document.createElement("span");
|
|
totalPercent.className = "sizePercentCell";
|
|
totalPercent.textContent = prettyPercent(1.0);
|
|
|
|
let header = document.createElement("div");
|
|
header.className = "headerRow";
|
|
header.style["border-bottom"] = "solid 1px";
|
|
header.appendChild(title);
|
|
header.appendChild(filler);
|
|
header.appendChild(totalSize);
|
|
header.appendChild(totalPercent);
|
|
|
|
let content = document.createElement("div");
|
|
content.style["flex-basis"] = 0;
|
|
content.style["flex-grow"] = 1;
|
|
|
|
let column = document.createElement("div");
|
|
column.style["width"] = "100%";
|
|
column.style["height"] = "100%";
|
|
column.style["border"] = "solid 2px";
|
|
column.style["display"] = "flex";
|
|
column.style["flex-direction"] = "column";
|
|
column.appendChild(header);
|
|
column.appendChild(content);
|
|
|
|
setBody(column);
|
|
|
|
// Add the content div to the document first so the browser will calculate
|
|
// the available width and height.
|
|
let w = content.offsetWidth;
|
|
let h = content.offsetHeight;
|
|
|
|
let topTile = createTreemapTile(graph, v, w, h, 0);
|
|
topTile.style.width = w + "px";
|
|
topTile.style.height = h + "px";
|
|
topTile.style.border = "none";
|
|
content.appendChild(topTile);
|
|
}
|
|
|
|
function Group(edge, cls) {
|
|
this.name = "";
|
|
this.edge = edge;
|
|
this.cls = cls;
|
|
this.shallowSize = 0;
|
|
this.ownedSize = 0;
|
|
this.nodes = new Array();
|
|
}
|
|
|
|
Group.prototype.add = function(v, edge, cls) {
|
|
this.nodes.push(v);
|
|
if (this.edge != edge) {
|
|
this.edge = "<multiple>";
|
|
}
|
|
if (this.cls != cls) {
|
|
this.cls = "<multiple>"
|
|
}
|
|
};
|
|
|
|
function asGroupsByClass(labeledNodes) {
|
|
let map = new Map();
|
|
let groups = new Array();
|
|
for (let i = 0; i < labeledNodes.length; i++) {
|
|
let v = labeledNodes[i].node;
|
|
let edge = labeledNodes[i].name;
|
|
let cls = graph.classOf(v);
|
|
let group = map.get(cls);
|
|
if (group === undefined) {
|
|
group = new Group(edge, cls);
|
|
groups.push(group);
|
|
map.set(cls, group);
|
|
}
|
|
group.add(v, edge, cls);
|
|
}
|
|
for (let i = 0; i < groups.length; i++) {
|
|
let group = groups[i];
|
|
group.shallowSize = graph.shallowSizeOfSet(group.nodes);
|
|
group.ownedSize = graph.ownedSizeOfSet(group.nodes);
|
|
group.name = group.nodes.length + " instances of " + group.cls;
|
|
}
|
|
return groups;
|
|
}
|
|
|
|
function LabeledNode(node, name) {
|
|
this.node = node;
|
|
this.name = name;
|
|
}
|
|
|
|
LabeledNode.prototype.addName = function(name) {
|
|
if (this.name != name) {
|
|
this.name = "<multiple>";
|
|
}
|
|
};
|
|
|
|
function Table(title, labeledNodes, byEdges) {
|
|
const self = this;
|
|
|
|
this.title = title;
|
|
this.labeledNodes = labeledNodes;
|
|
this.groupsByEdge = byEdges;
|
|
this.groupsByClass = asGroupsByClass(labeledNodes);
|
|
this.grouping = 0;
|
|
|
|
this.nom = document.createElement("span");
|
|
this.nom.onclick = function() {
|
|
self.grouping = (self.grouping + 1) % 3;
|
|
self.refreshRows();
|
|
};
|
|
this.nom.className = "nameCell actionCell"
|
|
this.nom.textContent = title;
|
|
this.nom.title = "Toggle grouping by object, class or edge";
|
|
|
|
let edge = document.createElement("span");
|
|
edge.onclick = function() { self.sortByEdge(); };
|
|
edge.className = "edgeCell actionCell";
|
|
edge.textContent = "Edge";
|
|
edge.title = "Sort by edge";
|
|
|
|
let cls = document.createElement("span");
|
|
cls.onclick = function() { self.sortByClass(); };
|
|
cls.className = "classCell actionCell";
|
|
cls.textContent = "Class";
|
|
cls.title = "Sort by class";
|
|
|
|
let shallowSize = document.createElement("span");
|
|
shallowSize.onclick = function() { self.sortByShallowSize(); };
|
|
shallowSize.className = "sizeCell actionCell";
|
|
shallowSize.textContent = "Size";
|
|
shallowSize.title = "Sort by shallow size";
|
|
|
|
let shallowPercent = document.createElement("span");
|
|
shallowPercent.className = "sizePercentCell"
|
|
|
|
this.retainedSize = document.createElement("span");
|
|
this.retainedSize.onclick = function() { self.sortByRetainedSize(); };
|
|
this.retainedSize.className = "sizeCell actionCell";
|
|
|
|
let retainedPercent = document.createElement("span");
|
|
retainedPercent.className = "sizePercentCell";
|
|
|
|
let header = document.createElement("div");
|
|
header.className = "headerRow";
|
|
header.style["border-bottom"] = "solid 1px";
|
|
header.appendChild(this.nom);
|
|
header.appendChild(edge);
|
|
header.appendChild(cls);
|
|
header.appendChild(shallowSize);
|
|
header.appendChild(shallowPercent);
|
|
header.appendChild(this.retainedSize);
|
|
header.appendChild(retainedPercent);
|
|
|
|
this.listDiv = document.createElement("div");
|
|
this.listDiv.style["overflow-y"] = "scroll";
|
|
this.listDiv.style["flex-basis"] = "0px";
|
|
this.listDiv.style["flex-grow"] = 1;
|
|
|
|
this.div = document.createElement("div");
|
|
this.div.style["border"] = "solid 2px";
|
|
this.div.style["display"] = "flex";
|
|
this.div.style["flex-direction"] = "column";
|
|
this.div.style["background-color"] = "#DDDDDD";
|
|
|
|
this.div.appendChild(header);
|
|
this.div.appendChild(this.listDiv);
|
|
|
|
this.refreshRows();
|
|
}
|
|
|
|
Table.prototype.sortByEdge = function() {
|
|
this.labeledNodes.sort(function (a, b) {
|
|
return a.name.localeCompare(b.name);
|
|
});
|
|
this.groupsByEdge.sort(function (a, b) {
|
|
return a.edge.localeCompare(b.edge);
|
|
});
|
|
this.groupsByClass.sort(function (a, b) {
|
|
return a.edge.localeCompare(b.edge);
|
|
});
|
|
this.refreshRows();
|
|
};
|
|
|
|
Table.prototype.sortByClass = function() {
|
|
this.labeledNodes.sort(function (a, b) {
|
|
return graph.classOf(a.node).localeCompare(graph.classOf(b.node));
|
|
});
|
|
this.groupsByEdge.sort(function (a, b) {
|
|
return a.cls.localeCompare(b.cls);
|
|
});
|
|
this.groupsByClass.sort(function (a, b) {
|
|
return a.cls.localeCompare(b.cls);
|
|
});
|
|
this.refreshRows();
|
|
};
|
|
|
|
Table.prototype.sortByShallowSize = function() {
|
|
this.labeledNodes.sort(function (a, b) {
|
|
return graph.shallowSizeOf(b.node) - graph.shallowSizeOf(a.node);
|
|
});
|
|
this.groupsByEdge.sort(function (a, b) {
|
|
return b.shallowSize - a.shallowSize;
|
|
});
|
|
this.groupsByClass.sort(function (a, b) {
|
|
return b.shallowSize - a.shallowSize;
|
|
});
|
|
this.refreshRows();
|
|
};
|
|
|
|
Table.prototype.sortByRetainedSize = function() {
|
|
this.labeledNodes.sort(function (a, b) {
|
|
return graph.retainedSizeOf(b.node) - graph.retainedSizeOf(a.node);
|
|
});
|
|
this.groupsByEdge.sort(function (a, b) {
|
|
return b.ownedSize - a.ownedSize;
|
|
});
|
|
this.groupsByClass.sort(function (a, b) {
|
|
return b.ownedSize - a.ownedSize;
|
|
});
|
|
this.refreshRows();
|
|
};
|
|
|
|
Table.prototype.refreshRows = function() {
|
|
while (this.listDiv.firstChild) {
|
|
this.listDiv.removeChild(this.listDiv.firstChild);
|
|
}
|
|
|
|
// To prevent rendering lag.
|
|
const MAX_TABLE_ROWS = 500;
|
|
|
|
if (this.grouping == 0) {
|
|
let labeledNodes = this.labeledNodes;
|
|
this.nom.textContent = this.title + " (" + labeledNodes.length + " objects)";
|
|
this.retainedSize.textContent = "Retained Size";
|
|
this.retainedSize.title = "Sort by retained size";
|
|
|
|
for (let i = 0; i < labeledNodes.length; i++) {
|
|
if (i > MAX_TABLE_ROWS) {
|
|
this.listDiv.appendChild(document.createTextNode((labeledNodes.length - i) + " more objects"));
|
|
break;
|
|
}
|
|
this.listDiv.appendChild(createObjectRow(labeledNodes[i]));
|
|
}
|
|
} else if (this.grouping == 1) {
|
|
let groups = this.groupsByClass;
|
|
this.nom.textContent = this.title + " (" + groups.length + " classes)";
|
|
this.retainedSize.textContent = "Owned Size";
|
|
this.retainedSize.title = "Sort by owned size";
|
|
|
|
for (let i = 0; i < groups.length; i++) {
|
|
if (i > MAX_TABLE_ROWS) {
|
|
// Prevent rendering lag.
|
|
this.listDiv.appendChild(document.createTextNode((groups.length - i) + " more classes"));
|
|
break;
|
|
}
|
|
this.listDiv.appendChild(createGroupRow(groups[i]));
|
|
}
|
|
} else {
|
|
let groups = this.groupsByEdge;
|
|
this.nom.textContent = this.title + " (" + groups.length + " edges)";
|
|
this.retainedSize.textContent = "Owned Size";
|
|
this.retainedSize.title = "Sort by owned size";
|
|
|
|
for (let i = 0; i < groups.length; i++) {
|
|
if (i > MAX_TABLE_ROWS) {
|
|
// Prevent rendering lag.
|
|
this.listDiv.appendChild(document.createTextNode((groups.length - i) + " more edges"));
|
|
break;
|
|
}
|
|
this.listDiv.appendChild(createGroupRow(groups[i]));
|
|
}
|
|
}
|
|
};
|
|
|
|
function createObjectRow(labeledNode) {
|
|
const v = labeledNode.node;
|
|
|
|
let nom = document.createElement("span");
|
|
nom.onclick = function() { showTables([v]); }
|
|
nom.className = "nameCell actionCell";
|
|
nom.textContent = graph.nameOf(v);
|
|
nom.title = "Select this object";
|
|
|
|
let edge = document.createElement("span");
|
|
edge.className = "edgeCell";
|
|
edge.textContent = labeledNode.name;
|
|
|
|
let cls = document.createElement("span");
|
|
cls.className = "classCell";
|
|
cls.textContent = graph.classOf(v);
|
|
|
|
let shallowSize = document.createElement("span");
|
|
shallowSize.className = "sizeCell";
|
|
shallowSize.textContent = graph.shallowSizeOf(v);
|
|
|
|
let shallowPercent = document.createElement("span");
|
|
shallowPercent.className = "sizePercentCell";
|
|
shallowPercent.textContent = prettyPercent(graph.shallowSizeOf(v) / graph.getTotalSize());
|
|
|
|
function onClickRetained(event) {
|
|
// For the root, default to the merged view for the sake of web browser layout performance.
|
|
showDominatorTree(v == 1 ? graph.toggleMerge() : graph, v);
|
|
}
|
|
|
|
let retainedSize = document.createElement("span");
|
|
retainedSize.onclick = onClickRetained;
|
|
retainedSize.className = "sizeCell actionCell";
|
|
retainedSize.textContent = graph.retainedSizeOf(v);
|
|
retainedSize.title = "Show dominator tree";
|
|
|
|
let retainedPercent = document.createElement("span");
|
|
retainedPercent.onclick = onClickRetained;
|
|
retainedPercent.className = "sizePercentCell actionCell";
|
|
retainedPercent.textContent = prettyPercent(graph.retainedSizeOf(v) / graph.getTotalSize());
|
|
retainedPercent.title = "Show dominator tree";
|
|
|
|
let row = document.createElement("div");
|
|
row.style["display"] = "flex";
|
|
row.style["flex-direction"] = "row";
|
|
row.style["border-bottom"] = "solid 1px";
|
|
row.appendChild(nom);
|
|
row.appendChild(edge);
|
|
row.appendChild(cls);
|
|
row.appendChild(shallowSize);
|
|
row.appendChild(shallowPercent);
|
|
row.appendChild(retainedSize);
|
|
row.appendChild(retainedPercent);
|
|
return row;
|
|
}
|
|
|
|
function createGroupRow(g) {
|
|
let nom = document.createElement("span");
|
|
nom.onclick = function() { showTables(g.nodes); }
|
|
nom.className = "nameCell actionCell";
|
|
nom.textContent = g.name;
|
|
nom.title = "Select these objects";
|
|
|
|
let cls = document.createElement("span");
|
|
cls.className = "classCell";
|
|
cls.textContent = g.cls;
|
|
|
|
let edge = document.createElement("span");
|
|
edge.className = "edgeCell";
|
|
edge.textContent = g.edge;
|
|
|
|
let shallowSize = document.createElement("span");
|
|
shallowSize.className = "sizeCell";
|
|
shallowSize.textContent = g.shallowSize;
|
|
|
|
let shallowPercent = document.createElement("span");
|
|
shallowPercent.className = "sizePercentCell";
|
|
shallowPercent.textContent = prettyPercent(g.shallowSize / graph.getTotalSize());
|
|
|
|
let ownedSize = document.createElement("span");
|
|
ownedSize.className = "sizeCell";
|
|
ownedSize.textContent = g.ownedSize;
|
|
|
|
let ownedPercent = document.createElement("span");
|
|
ownedPercent.className = "sizePercentCell";
|
|
ownedPercent.textContent = prettyPercent(g.ownedSize / graph.getTotalSize());
|
|
|
|
let row = document.createElement("div");
|
|
row.style["display"] = "flex";
|
|
row.style["flex-direction"] = "row";
|
|
row.style["border-bottom"] = "solid 1px";
|
|
row.appendChild(nom);
|
|
row.appendChild(edge);
|
|
row.appendChild(cls);
|
|
row.appendChild(shallowSize);
|
|
row.appendChild(shallowPercent);
|
|
row.appendChild(ownedSize);
|
|
row.appendChild(ownedPercent);
|
|
return row;
|
|
}
|
|
|
|
function mapValuesToArray(map) {
|
|
let array = new Array();
|
|
for (let element of map.values()) {
|
|
array.push(element);
|
|
}
|
|
return array;
|
|
}
|
|
|
|
function showTables(nodes) {
|
|
let labeledNodes = new Array();
|
|
let successors = new Map();
|
|
let successorEdges = new Map();
|
|
let successorEdgeGroups = new Array();
|
|
let predecessors = new Map();
|
|
let predecessorEdges = new Map();
|
|
let predecessorEdgeGroups = new Array();
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
let n = nodes[i];
|
|
labeledNodes.push(new LabeledNode(n, "-"));
|
|
graph.successorsOfDo(n, function (child, edgeName, edgeOwnedSize) {
|
|
let e = successors.get(child);
|
|
if (e) {
|
|
e.addName(edgeName);
|
|
} else {
|
|
successors.set(child, new LabeledNode(child, edgeName));
|
|
}
|
|
let cls = graph.classOf(child);
|
|
let g = successorEdges.get(edgeName);
|
|
if (!g) {
|
|
g = new Group(edgeName, cls);
|
|
successorEdgeGroups.push(g);
|
|
successorEdges.set(edgeName, g);
|
|
}
|
|
g.add(child, edgeName, cls);
|
|
g.ownedSize += edgeOwnedSize;
|
|
});
|
|
graph.predecessorsOfDo(n, function (parent, edgeName) {
|
|
let e = predecessors.get(parent);
|
|
if (e) {
|
|
e.addName(edgeName);
|
|
} else {
|
|
predecessors.set(parent, new LabeledNode(parent, edgeName));
|
|
}
|
|
let cls = graph.classOf(parent);
|
|
let g = predecessorEdges.get(edgeName);
|
|
if (!g) {
|
|
g = new Group(edgeName, cls);
|
|
predecessorEdgeGroups.push(g);
|
|
predecessorEdges.set(edgeName, g);
|
|
}
|
|
g.add(parent, edgeName, cls);
|
|
});
|
|
}
|
|
|
|
successors = mapValuesToArray(successors);
|
|
for (let i = 0; i < successorEdgeGroups.length; i++) {
|
|
let group = successorEdgeGroups[i];
|
|
group.nodes = removeDuplicates(group.nodes);
|
|
group.shallowSize = graph.shallowSizeOfSet(group.nodes);
|
|
// group.ownedSize is the flow through the edges
|
|
group.name = group.nodes.length + " targets of " + group.edge;
|
|
}
|
|
predecessors = mapValuesToArray(predecessors);
|
|
for (let i = 0; i < predecessorEdgeGroups.length; i++) {
|
|
let group = predecessorEdgeGroups[i];
|
|
group.nodes = removeDuplicates(group.nodes);
|
|
group.shallowSize = graph.shallowSizeOfSet(group.nodes);
|
|
group.ownedSize = graph.ownedSizeOfSet(group.nodes);
|
|
group.name = group.nodes.length + " sources of " + group.edge;
|
|
}
|
|
|
|
let rewrite = document.createElement("input");
|
|
rewrite.setAttribute("type", "checkbox");
|
|
|
|
let rewriteLabel = document.createElement("span");
|
|
rewriteLabel.textContent = "Owners ";
|
|
|
|
let input = document.createElement("input");
|
|
input.setAttribute("type", "file");
|
|
input.setAttribute("multiple", false);
|
|
input.onchange = function(event) {
|
|
let file = event.target.files[0];
|
|
document.title = file.name;
|
|
let reader = new FileReader();
|
|
reader.readAsText(file, 'UTF-8');
|
|
reader.onload = function(event) {
|
|
let data = JSON.parse(event.target.result);
|
|
let g = new Graph();
|
|
g.loadV8Profile(data);
|
|
data = null; // Release memory
|
|
graph = g;
|
|
g.compute(rewrite.checked);
|
|
showTables([graph.getRoot()]);
|
|
};
|
|
reader = new FileReader();
|
|
reader.readAsArrayBuffer(file);
|
|
reader.onload = function(event) {
|
|
let data = event.target.result;
|
|
let g = new Graph();
|
|
g.loadDartHeapSnapshot(data);
|
|
data = null; // Release memory
|
|
graph = g;
|
|
g.compute(rewrite.checked);
|
|
showTables([graph.getRoot()]);
|
|
};
|
|
};
|
|
|
|
let selectRoot = document.createElement("button");
|
|
selectRoot.textContent = "Select Root";
|
|
selectRoot.onclick = function(event) {
|
|
showTables([graph.getRoot()]);
|
|
};
|
|
|
|
let selectAll = document.createElement("button");
|
|
selectAll.textContent = "Select All";
|
|
selectAll.onclick = function(event) {
|
|
showTables(graph.getAll());
|
|
};
|
|
|
|
let filler = document.createElement("span");
|
|
filler.style["flex-grow"] = 1;
|
|
|
|
let totalSize = document.createElement("span");
|
|
totalSize.className = "sizeCell";
|
|
totalSize.textContent = graph ? "" + graph.getTotalSize() : "0";
|
|
|
|
let totalPercent = document.createElement("span");
|
|
totalPercent.className = "sizePercentCell";
|
|
totalPercent.textContent = prettyPercent(1.0);
|
|
|
|
let topBar = document.createElement("div");
|
|
topBar.className = "headerRow";
|
|
topBar.style["border"] = "solid 2px";
|
|
topBar.style["align-items"] = "center";
|
|
topBar.appendChild(rewrite);
|
|
topBar.appendChild(rewriteLabel);
|
|
topBar.appendChild(input);
|
|
if (graph) {
|
|
topBar.appendChild(selectRoot);
|
|
topBar.appendChild(selectAll);
|
|
topBar.appendChild(filler);
|
|
topBar.appendChild(totalSize);
|
|
topBar.appendChild(totalPercent);
|
|
}
|
|
|
|
let selectionTable = new Table("Selection", labeledNodes, []).div;
|
|
selectionTable.style["flex-basis"] = "0px";
|
|
selectionTable.style["flex-grow"] = 1;
|
|
|
|
let successorsTable = new Table("Successors", successors, successorEdgeGroups).div;
|
|
successorsTable.style["flex-basis"] = "0px";
|
|
successorsTable.style["flex-grow"] = 1;
|
|
|
|
let predecessorsTable = new Table("Predecessors", predecessors, predecessorEdgeGroups).div;
|
|
predecessorsTable.style["flex-basis"] = "0px";
|
|
predecessorsTable.style["flex-grow"] = 1;
|
|
|
|
let help1 = document.createElement("p");
|
|
help1.textContent =
|
|
"Create a snapshot profile by passing --write_v8_snapshot_profile_to=example.json to gen_snapshot."
|
|
let help2 = document.createElement("p");
|
|
help2.textContent =
|
|
"In Flutter, run flutter build aot --release --extra-gen-snapshot-options=--write-v8-snapshot-profile-to=example.json";
|
|
|
|
let column = document.createElement("div");
|
|
column.style["height"] = "100%";
|
|
column.style["display"] = "flex";
|
|
column.style["flex-direction"] = "column";
|
|
column.appendChild(topBar);
|
|
column.appendChild(document.createElement("br"));
|
|
if (graph) {
|
|
column.appendChild(selectionTable);
|
|
column.appendChild(document.createElement("br"));
|
|
column.appendChild(successorsTable);
|
|
column.appendChild(document.createElement("br"));
|
|
column.appendChild(predecessorsTable);
|
|
} else {
|
|
column.appendChild(help1);
|
|
column.appendChild(help2);
|
|
}
|
|
|
|
setBody(column);
|
|
}
|
|
|
|
function setBody(div) {
|
|
let body = document.body;
|
|
while (body.firstChild) {
|
|
body.removeChild(body.firstChild);
|
|
}
|
|
body.appendChild(div);
|
|
}
|
|
|
|
let graph = null;
|
|
showTables([]);
|