Add a faster and more accurate level size calculation to the tree. This (hopefully) fixes the problems with super wide trees in prod.

This commit is contained in:
Joseph Schorr 2014-10-27 14:54:10 -04:00
parent c06f57a6e7
commit 6e25eaaa99

View file

@ -377,6 +377,23 @@ ImageHistoryTree.prototype.expandCollapsed_ = function(imageNode) {
}; };
/**
* Returns the level of the node in the tree. Recursively computes and updates
* if necessary.
*/
ImageHistoryTree.prototype.calculateLevel_ = function(node) {
if (node['level'] != null) {
return node['level'];
}
if (node['parent'] == null) {
return node['level'] = 0;
}
return node['level'] = (this.calculateLevel_(node['parent']) + 1);
};
/** /**
* Builds the root node for the tree. * Builds the root node for the tree.
*/ */
@ -392,11 +409,16 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
var imageByDockerId = {}; var imageByDockerId = {};
for (var i = 0; i < this.images_.length; ++i) { for (var i = 0; i < this.images_.length; ++i) {
var image = this.images_[i]; var image = this.images_[i];
// Skip images that are currently uploading.
if (image.uploading) { continue; }
var imageNode = { var imageNode = {
"name": image.id.substr(0, 12), "name": image.id.substr(0, 12),
"children": [], "children": [],
"image": image, "image": image,
"tags": image.tags "tags": image.tags,
"level": null
}; };
imageByDockerId[image.id] = imageNode; imageByDockerId[image.id] = imageNode;
} }
@ -405,6 +427,7 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
// For each node, attach it to its immediate parent. If there is no immediate parent, // For each node, attach it to its immediate parent. If there is no immediate parent,
// then the node is the root. // then the node is the root.
var roots = []; var roots = [];
var nodeCountsByLevel = {};
for (var i = 0; i < this.images_.length; ++i) { for (var i = 0; i < this.images_.length; ++i) {
var image = this.images_[i]; var image = this.images_[i];
@ -420,10 +443,27 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
imageNode.parent = parent; imageNode.parent = parent;
parent.children.push(imageNode); parent.children.push(imageNode);
} else { } else {
imageNode['level'] = 0;
roots.push(imageNode); roots.push(imageNode);
} }
} }
// Calculate each node's level.
for (var i = 0; i < this.images_.length; ++i) {
var image = this.images_[i];
// Skip images that are currently uploading.
if (image.uploading) { continue; }
var imageNode = imageByDockerId[image.id];
var level = this.calculateLevel_(imageNode);
if (nodeCountsByLevel[level] == null) {
nodeCountsByLevel[level] = 1;
} else {
nodeCountsByLevel[level]++;
}
}
// If there are multiple root nodes, then there is at least one branch without shared // If there are multiple root nodes, then there is at least one branch without shared
// ancestry and we use the virtual node. Otherwise, we use the root node found. // ancestry and we use the virtual node. Otherwise, we use the root node found.
var root = { var root = {
@ -438,16 +478,12 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
// Determine the maximum number of nodes at a particular level. This is used to size // Determine the maximum number of nodes at a particular level. This is used to size
// the width of the tree properly. // the width of the tree properly.
var maxChildCount = roots.length; var maxChildCount = 0;
for (var i = 0; i < this.images_.length; ++i) { var maxChildHeight = 0;
var image = this.images_[i]; Object.keys(nodeCountsByLevel).forEach(function(key){
maxChildCount = Math.max(maxChildCount, nodeCountsByLevel[key]);
// Skip images that are currently uploading. maxChildHeight = Math.max(maxChildHeight, key);
if (image.uploading) { continue; } });
var imageNode = imageByDockerId[image.id];
maxChildCount = Math.max(maxChildCount, this.determineMaximumChildCount_(imageNode));
}
// Compact the graph so that any single chain of three (or more) images becomes a collapsed // Compact the graph so that any single chain of three (or more) images becomes a collapsed
// section. We only do this if the max width is > 1 (since for a single width tree, no long // section. We only do this if the max width is > 1 (since for a single width tree, no long
@ -456,22 +492,21 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
this.collapseNodes_(root); this.collapseNodes_(root);
} }
// Determine the maximum height of the tree. // Determine the maximum height of the tree, with collapsed nodes.
var maxHeight = this.determineMaximumHeight_(root); var maxCollapsedHeight = this.determineMaximumHeight_(root);
// Finally, set the root node and return. // Finally, set the root node and return.
this.root_ = root; this.root_ = root;
return { return {
'maxWidth': maxChildCount + 1, 'maxWidth': maxChildCount + 1,
'maxHeight': maxHeight 'maxHeight': maxCollapsedHeight
}; };
}; };
/** /**
* Collapses long single chains of nodes (3 or more) into single nodes to make the graph more * Determines the height of the tree at its longest chain.
* compact.
*/ */
ImageHistoryTree.prototype.determineMaximumHeight_ = function(node) { ImageHistoryTree.prototype.determineMaximumHeight_ = function(node) {
var maxHeight = 0; var maxHeight = 0;