Index: Source/devtools/front_end/timeline/TimelineTreeView.js |
diff --git a/Source/devtools/front_end/timeline/TimelineTreeView.js b/Source/devtools/front_end/timeline/TimelineTreeView.js |
index 24fd410b0f750689eb43b26c2500750794f21152..2c68e09c7ee4e58efe6eca5100fc2233aa926817 100644 |
--- a/Source/devtools/front_end/timeline/TimelineTreeView.js |
+++ b/Source/devtools/front_end/timeline/TimelineTreeView.js |
@@ -15,11 +15,17 @@ WebInspector.TimelineTreeView = function(model) |
this._model = model; |
var columns = []; |
columns.push({id: "self", title: WebInspector.UIString("Self Time"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true}); |
- columns.push({id: "total", title: WebInspector.UIString("Total Time"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true}); |
+ columns.push({id: "total", title: WebInspector.UIString("Total Time"), width: "120px", sortable: true}); |
columns.push({id: "activity", title: WebInspector.UIString("Activity"), disclosure: true, sortable: true}); |
+ var nonessentialEvents = [ |
+ WebInspector.TimelineModel.RecordType.EventDispatch, |
+ WebInspector.TimelineModel.RecordType.FunctionCall, |
+ WebInspector.TimelineModel.RecordType.TimerFire |
+ ]; |
this._filters = [ |
WebInspector.TimelineUIUtils.hiddenEventsFilter(), |
+ new WebInspector.ExclusiveTraceEventNameFilter(nonessentialEvents), |
new WebInspector.ExcludeTopLevelFilter() |
]; |
@@ -36,6 +42,14 @@ WebInspector.TimelineTreeView = function(model) |
/** |
* @enum {string} |
*/ |
+WebInspector.TimelineTreeView.Mode = { |
+ TopDown: "TopDown", |
+ BottomUp: "BottomUp" |
+} |
+ |
+/** |
+ * @enum {string} |
+ */ |
WebInspector.TimelineTreeView.GroupBy = { |
None: "None", |
Domain: "Domain", |
@@ -51,138 +65,169 @@ WebInspector.TimelineTreeView.prototype = { |
{ |
this._startTime = selection.startTime(); |
this._endTime = selection.endTime(); |
- this._refreshRecords(); |
+ this._refreshTree(); |
}, |
_createToolbar: function() |
{ |
- this._panelToolbar = new WebInspector.Toolbar(this.element); |
+ var panelToolbar = new WebInspector.Toolbar(this.element); |
+ panelToolbar.appendToolbarItem(new WebInspector.ToolbarText(WebInspector.UIString("View"))); |
+ |
+ this._modeCombobox = new WebInspector.ToolbarComboBox(this._onTreeModeChanged.bind(this)); |
+ this._modeCombobox.addOption(this._modeCombobox.createOption(WebInspector.UIString("Costly Functions"), "", WebInspector.TimelineTreeView.Mode.BottomUp)); |
+ this._modeCombobox.addOption(this._modeCombobox.createOption(WebInspector.UIString("Costly Entrypoints"), "", WebInspector.TimelineTreeView.Mode.TopDown)); |
+ panelToolbar.appendToolbarItem(this._modeCombobox); |
+ |
this._groupByCombobox = new WebInspector.ToolbarComboBox(this._onGroupByChanged.bind(this)); |
/** |
* @param {string} name |
* @param {string} id |
* @this {WebInspector.TimelineTreeView} |
*/ |
- function addOption(name, id) |
+ function addGroupingOption(name, id) |
{ |
var option = this._groupByCombobox.createOption(name, "", id); |
this._groupByCombobox.addOption(option); |
if (id === this._groupBySetting.get()) |
this._groupByCombobox.select(option); |
} |
- addOption.call(this, WebInspector.UIString("No Grouping"), WebInspector.TimelineTreeView.GroupBy.None); |
- addOption.call(this, WebInspector.UIString("Group by Domain"), WebInspector.TimelineTreeView.GroupBy.Domain); |
- addOption.call(this, WebInspector.UIString("Group by Domain (2nd Level)"), WebInspector.TimelineTreeView.GroupBy.DomainSecondLevel); |
- addOption.call(this, WebInspector.UIString("Group by URL"), WebInspector.TimelineTreeView.GroupBy.URL); |
- this._panelToolbar.appendToolbarItem(this._groupByCombobox); |
+ panelToolbar.appendToolbarItem(new WebInspector.ToolbarText(WebInspector.UIString("Group by"))); |
+ addGroupingOption.call(this, WebInspector.UIString("Function"), WebInspector.TimelineTreeView.GroupBy.None); |
+ addGroupingOption.call(this, WebInspector.UIString("Domain"), WebInspector.TimelineTreeView.GroupBy.Domain); |
+ addGroupingOption.call(this, WebInspector.UIString("Domain (2nd Level)"), WebInspector.TimelineTreeView.GroupBy.DomainSecondLevel); |
+ addGroupingOption.call(this, WebInspector.UIString("URL"), WebInspector.TimelineTreeView.GroupBy.URL); |
+ panelToolbar.appendToolbarItem(this._groupByCombobox); |
+ }, |
+ |
+ _onTreeModeChanged: function() |
+ { |
+ this._refreshTree(); |
}, |
_onGroupByChanged: function() |
{ |
this._groupBySetting.set(this._groupByCombobox.selectedOption().value); |
- this._refreshRecords(); |
+ this._refreshTree(); |
}, |
- _refreshRecords: function() |
+ _refreshTree: function() |
{ |
- var groupBy = this._groupBySetting.get(); |
- var groupNodes = new Map(); |
+ this.dataGrid.rootNode().removeChildren(); |
+ var topDown = WebInspector.TimelineModel.buildTopDownTree( |
+ this._model.mainThreadEvents(), this._startTime, this._endTime, this._filters, WebInspector.TimelineTreeView.eventId); |
+ var tree = this._modeCombobox.selectedOption().value === WebInspector.TimelineTreeView.Mode.TopDown |
+ ? this._preformTopDownTreeGrouping(topDown) |
+ : this._buildBottomUpTree(topDown); |
+ for (var child of tree.children.values()) { |
+ // Exclude the idle time off the total calculation. |
+ var gridNode = new WebInspector.TimelineTreeView.GridNode(child, topDown.totalTime); |
+ this.dataGrid.insertChild(gridNode); |
+ } |
+ this._sortingChanged(); |
+ }, |
- /** |
- * @param {string} id |
- * @return {!WebInspector.TimelineModel.ProfileTreeNode} |
- */ |
- function groupNodeById(id) |
- { |
- var node = groupNodes.get(id); |
- if (!node) { |
- node = new WebInspector.TimelineModel.ProfileTreeNode(); |
- node.name = id || WebInspector.UIString("(unknown)"); |
- node.selfTime = 0; |
- node.totalTime = 0; |
- groupNodes.set(id, node); |
+ /** |
+ * @param {!WebInspector.TimelineModel.ProfileTreeNode} topDownTree |
+ * @return {!WebInspector.TimelineModel.ProfileTreeNode} |
+ */ |
+ _preformTopDownTreeGrouping: function(topDownTree) |
+ { |
+ var nodeToGroupId = this._nodeToGroupIdFunction(); |
+ if (nodeToGroupId) { |
+ this._groupNodes = new Map(); |
+ for (var node of topDownTree.children.values()) { |
+ var groupNode = this._nodeToGroupNode(nodeToGroupId, node); |
+ groupNode.selfTime += node.selfTime; |
+ groupNode.totalTime += node.totalTime; |
+ groupNode.children.set(node.id, node); |
} |
- return node; |
+ topDownTree.children = this._groupNodes; |
+ this._groupNodes = null; |
} |
+ return topDownTree; |
+ }, |
- /** |
- * @return {?WebInspector.TimelineModel.ProfileTreeNode} |
- */ |
- function groupByNone() |
- { |
- return null; |
- } |
+ /** |
+ * @param {!WebInspector.TimelineModel.ProfileTreeNode} topDownTree |
+ * @return {!WebInspector.TimelineModel.ProfileTreeNode} |
+ */ |
+ _buildBottomUpTree: function(topDownTree) |
+ { |
+ this._groupNodes = new Map(); |
+ var nodeToGroupId = this._nodeToGroupIdFunction(); |
+ var nodeToGroupNode = nodeToGroupId ? this._nodeToGroupNode.bind(this, nodeToGroupId) : null; |
+ var bottomUpRoot = WebInspector.TimelineModel.buildBottomUpTree(topDownTree, nodeToGroupNode); |
+ for (var group of this._groupNodes) |
+ bottomUpRoot.children.set(group[0], group[1]); |
+ return bottomUpRoot; |
+ }, |
+ /** |
+ * @return {?function(!WebInspector.TimelineModel.ProfileTreeNode):string} |
+ */ |
+ _nodeToGroupIdFunction: function() |
+ { |
/** |
* @param {!WebInspector.TimelineModel.ProfileTreeNode} node |
- * @return {?WebInspector.TimelineModel.ProfileTreeNode} |
+ * @return {string} |
*/ |
function groupByURL(node) |
{ |
- return groupNodeById(WebInspector.TimelineTreeView.eventURL(node.event) || ""); |
+ return WebInspector.TimelineTreeView.eventURL(node.event) || ""; |
} |
/** |
* @param {!WebInspector.TimelineModel.ProfileTreeNode} node |
- * @return {?WebInspector.TimelineModel.ProfileTreeNode} |
+ * @return {string} |
*/ |
function groupByDomain(node) |
{ |
var parsedURL = (WebInspector.TimelineTreeView.eventURL(node.event) || "").asParsedURL(); |
- var domain = parsedURL && parsedURL.host || ""; |
- return groupNodeById(domain); |
+ return parsedURL && parsedURL.host || ""; |
} |
/** |
* @param {!WebInspector.TimelineModel.ProfileTreeNode} node |
- * @return {?WebInspector.TimelineModel.ProfileTreeNode} |
+ * @return {string} |
*/ |
function groupByDomainSecondLevel(node) |
{ |
var parsedURL = (WebInspector.TimelineTreeView.eventURL(node.event) || "").asParsedURL(); |
if (!parsedURL) |
- return groupNodeById(""); |
+ return ""; |
if (/^[.0-9]+$/.test(parsedURL.host)) |
- return groupNodeById(parsedURL.host) |
+ return parsedURL.host; |
var domainMatch = /([^.]*\.)?[^.]*$/.exec(parsedURL.host); |
- return groupNodeById(domainMatch && domainMatch[0] || ""); |
+ return domainMatch && domainMatch[0] || ""; |
} |
- /** |
- * @param {!WebInspector.TracingModel.Event} e |
- * @return {string} |
- */ |
- function eventId(e) |
- { |
- // Function call frames are always groupped by the URL |
- if (e.name === "JSFrame") { |
- var data = e.args["data"]; |
- return "f:" + (data["callUID"] || WebInspector.TimelineTreeView.eventURL(e)); |
- } |
- // While the rest of events are groupped by the event type |
- // unless the group by URL/Domain mode is on. |
- if (groupBy === WebInspector.TimelineTreeView.GroupBy.URL) |
- return e.name + ":@" + WebInspector.TimelineTreeView.eventURL(e); |
- return e.name; |
- } |
- |
- var groupByMapper = new Map([ |
- [WebInspector.TimelineTreeView.GroupBy.None, groupByNone], |
+ var groupByMap = /** @type {!Map<!WebInspector.TimelineTreeView.GroupBy,?function(!WebInspector.TimelineModel.ProfileTreeNode):string>} */ (new Map([ |
+ [WebInspector.TimelineTreeView.GroupBy.None, null], |
[WebInspector.TimelineTreeView.GroupBy.Domain, groupByDomain], |
[WebInspector.TimelineTreeView.GroupBy.DomainSecondLevel, groupByDomainSecondLevel], |
[WebInspector.TimelineTreeView.GroupBy.URL, groupByURL] |
- ]); |
- var topDown = WebInspector.TimelineModel.buildTopDownTree(this._model.mainThreadEvents(), this._startTime, this._endTime, this._filters, eventId); |
- var bottomUpRoot = WebInspector.TimelineModel.buildBottomUpTree(topDown, groupByMapper.get(groupBy)); |
- for (var group of groupNodes) |
- bottomUpRoot.children.set(group[0], group[1]); |
- this.dataGrid.rootNode().removeChildren(); |
- for (var child of bottomUpRoot.children.values()) { |
- // Exclude the idle time off the total calculation. |
- var gridNode = new WebInspector.TimelineTreeView.GridNode(child, topDown.totalTime); |
- this.dataGrid.insertChild(gridNode); |
+ ])); |
+ return groupByMap.get(this._groupBySetting.get()) || null; |
+ }, |
+ |
+ /** |
+ * @param {function(!WebInspector.TimelineModel.ProfileTreeNode):string} nodeToGroupId |
+ * @param {!WebInspector.TimelineModel.ProfileTreeNode} node |
+ * @return {!WebInspector.TimelineModel.ProfileTreeNode} |
+ */ |
+ _nodeToGroupNode: function(nodeToGroupId, node) |
+ { |
+ var id = nodeToGroupId(node); |
+ var groupNode = this._groupNodes.get(id); |
+ if (!groupNode) { |
+ groupNode = new WebInspector.TimelineModel.ProfileTreeNode(); |
+ groupNode.name = id || WebInspector.UIString("(unattributed)"); |
+ groupNode.selfTime = 0; |
+ groupNode.totalTime = 0; |
+ groupNode.children = new Map(); |
+ this._groupNodes.set(id, groupNode); |
} |
- this._sortingChanged(); |
+ return groupNode; |
}, |
_sortingChanged: function() |
@@ -215,6 +260,19 @@ WebInspector.TimelineTreeView.prototype = { |
/** |
* @param {!WebInspector.TracingModel.Event} event |
+ * @return {string} |
+ */ |
+WebInspector.TimelineTreeView.eventId = function(event) |
+{ |
+ if (event.name === WebInspector.TimelineModel.RecordType.JSFrame) { |
+ var data = event.args["data"]; |
+ return "f:" + (data["callUID"] || WebInspector.TimelineTreeView.eventURL(event)); |
+ } |
+ return event.name + ":@" + WebInspector.TimelineTreeView.eventURL(event); |
+} |
+ |
+/** |
+ * @param {!WebInspector.TracingModel.Event} event |
* @return {?string} |
*/ |
WebInspector.TimelineTreeView.eventURL = function(event) |