Commit 23cf67bb authored by timothy@apple.com's avatar timothy@apple.com

Implements the Profiles panel and Profile view.

Reviewed by Kevin McCullough.

* English.lproj/localizedStrings.js: Added new strings.
* page/inspector/Images/profileIcon.png: Added.
* page/inspector/Images/profilesIcon.png: Changed. New icon design
that fits in with the other toolbar icons.
* page/inspector/ProfileView.js:
(WebInspector.ProfileView): Remove custom table elements
and create a DataGrid. Sorts the profile by descending total time,
since the profiles aren't sorted by default.
(WebInspector.ProfileView.prototype.refresh): Clears the DataGrid
and recreates all the nodes. The selection is preserved.
(WebInspector.ProfileView.prototype.refreshShowAsPercents): Traverse
all the children and change showTotalTimeAsPercent and showSelfTimeAsPercent
to match the ProfileView values. Then call refresh on the child.
(WebInspector.ProfileView.prototype._sortData): Determine the sort
function to call on the head profile node. Call it and then call
refresh to rebuild the DataGrid.
(WebInspector.ProfileView.prototype._mouseDownInDataGrid): Return early
if the event is not a double-click. When it is a double-click, determine
the column that was targeted and if it was total or self toggle the
show as percent property. Call refreshShowAsPercents.
(WebInspector.ProfileDataGridNode):
(WebInspector.ProfileDataGridNode.prototype.get data):
(WebInspector.ProfileDataGridNode.prototype.expand):
(WebInspector.ProfileDataGridNode.prototype.collapse):
(WebInspector.ProfileDataGridNode.prototype._populate):
* page/inspector/ProfilesPanel.js:
(WebInspector.ProfilesPanel):
(WebInspector.ProfilesPanel.prototype.show): Populate the sidebar
with all profiles. This is a workaround until the Inspector
is told about new profiles.
(WebInspector.ProfilesPanel.prototype.reset): Clear the sidebar and
profile views.
(WebInspector.ProfilesPanel.prototype.handleKeyEvent): Pass the key
event to the sidebar.
(WebInspector.ProfilesPanel.prototype.addProfile): Create a
ProfileSidebarTreeElement object and add it to the sidebar.
(WebInspector.ProfilesPanel.prototype.showProfile): Create a ProfileView
and show it.
(WebInspector.ProfilesPanel.prototype.closeVisibleView): Hide the
visible view.
(WebInspector.ProfilesPanel.prototype._startSidebarDragging): Call
WebInspector.elementDragStart.
(WebInspector.ProfilesPanel.prototype._sidebarDragging): Call _updateSidebarWidth.
(WebInspector.ProfilesPanel.prototype._endSidebarDragging):
Call WebInspector.elementDragEnd.
(WebInspector.ProfilesPanel.prototype._updateSidebarWidth): Update the
sidebar width based on the passed in value.
(WebInspector.ProfileSidebarTreeElement): Subclass WebInspector.SidebarTreeElement.
(WebInspector.ProfileSidebarTreeElement.prototype.onselect): Call ProfilesPanel.showProfile.
(WebInspector.ProfileSidebarTreeElement.prototype.get mainTitle): Return profile.title.
(WebInspector.ProfileSidebarTreeElement.prototype.set mainTitle): Do nothing.
(WebInspector.ProfileSidebarTreeElement.prototype.get subtitle): Ditto.
(WebInspector.ProfileSidebarTreeElement.prototype.set subtitle): Ditto.
* page/inspector/inspector.css: New styles for the profile sidebar
item and profile data grid columns.
* page/inspector/inspector.js:
* page/inspector/utilities.js:
(Number.secondsToString): Added a higherResolution argument
that returns fractional milliseconds.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@33938 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 7783493e
2008-05-20 Timothy Hatcher <timothy@apple.com>
Implements the Profiles panel and Profile view.
Reviewed by Kevin McCullough.
* English.lproj/localizedStrings.js: Added new strings.
* page/inspector/Images/profileIcon.png: Added.
* page/inspector/Images/profilesIcon.png: Changed. New icon design
that fits in with the other toolbar icons.
* page/inspector/ProfileView.js:
(WebInspector.ProfileView): Remove custom table elements
and create a DataGrid. Sorts the profile by descending total time,
since the profiles aren't sorted by default.
(WebInspector.ProfileView.prototype.refresh): Clears the DataGrid
and recreates all the nodes. The selection is preserved.
(WebInspector.ProfileView.prototype.refreshShowAsPercents): Traverse
all the children and change showTotalTimeAsPercent and showSelfTimeAsPercent
to match the ProfileView values. Then call refresh on the child.
(WebInspector.ProfileView.prototype._sortData): Determine the sort
function to call on the head profile node. Call it and then call
refresh to rebuild the DataGrid.
(WebInspector.ProfileView.prototype._mouseDownInDataGrid): Return early
if the event is not a double-click. When it is a double-click, determine
the column that was targeted and if it was total or self toggle the
show as percent property. Call refreshShowAsPercents.
(WebInspector.ProfileDataGridNode):
(WebInspector.ProfileDataGridNode.prototype.get data):
(WebInspector.ProfileDataGridNode.prototype.expand):
(WebInspector.ProfileDataGridNode.prototype.collapse):
(WebInspector.ProfileDataGridNode.prototype._populate):
* page/inspector/ProfilesPanel.js:
(WebInspector.ProfilesPanel):
(WebInspector.ProfilesPanel.prototype.show): Populate the sidebar
with all profiles. This is a workaround until the Inspector
is told about new profiles.
(WebInspector.ProfilesPanel.prototype.reset): Clear the sidebar and
profile views.
(WebInspector.ProfilesPanel.prototype.handleKeyEvent): Pass the key
event to the sidebar.
(WebInspector.ProfilesPanel.prototype.addProfile): Create a
ProfileSidebarTreeElement object and add it to the sidebar.
(WebInspector.ProfilesPanel.prototype.showProfile): Create a ProfileView
and show it.
(WebInspector.ProfilesPanel.prototype.closeVisibleView): Hide the
visible view.
(WebInspector.ProfilesPanel.prototype._startSidebarDragging): Call
WebInspector.elementDragStart.
(WebInspector.ProfilesPanel.prototype._sidebarDragging): Call _updateSidebarWidth.
(WebInspector.ProfilesPanel.prototype._endSidebarDragging):
Call WebInspector.elementDragEnd.
(WebInspector.ProfilesPanel.prototype._updateSidebarWidth): Update the
sidebar width based on the passed in value.
(WebInspector.ProfileSidebarTreeElement): Subclass WebInspector.SidebarTreeElement.
(WebInspector.ProfileSidebarTreeElement.prototype.onselect): Call ProfilesPanel.showProfile.
(WebInspector.ProfileSidebarTreeElement.prototype.get mainTitle): Return profile.title.
(WebInspector.ProfileSidebarTreeElement.prototype.set mainTitle): Do nothing.
(WebInspector.ProfileSidebarTreeElement.prototype.get subtitle): Ditto.
(WebInspector.ProfileSidebarTreeElement.prototype.set subtitle): Ditto.
* page/inspector/inspector.css: New styles for the profile sidebar
item and profile data grid columns.
* page/inspector/inspector.js:
* page/inspector/utilities.js:
(Number.secondsToString): Added a higherResolution argument
that returns fractional milliseconds.
2008-05-20 chris fleizach <cfleizach@apple.com>
Reviewed by Darin Adler, Alice Liu
Bvar localizedStrings = new Object;
......
......@@ -30,62 +30,167 @@ WebInspector.ProfileView = function(profile)
this.element.addStyleClass("profile-view");
this.profile = profile;
// Create the header
var headerTable = document.createElement("table");
headerTable.className = "data-grid";
this.element.appendChild(headerTable);
var headerTableTR = document.createElement("tr");
headerTable.appendChild(headerTableTR);
var headerTableSelf = document.createElement("th");
headerTableSelf.className = "narrow sort-descending selected";
headerTableSelf.addEventListener("click", this._toggleSortOrder.bind(this), false);
headerTableSelf.textContent = WebInspector.UIString("Self");
headerTableTR.appendChild(headerTableSelf);
this.selectedHeaderColumn = headerTableSelf;
var headerTableTotal = document.createElement("th");
headerTableTotal.classname = "narrow";
headerTableTotal.addEventListener("click", this._toggleSortOrder.bind(this), false);
headerTableTotal.textContent = WebInspector.UIString("Total");
headerTableTR.appendChild(headerTableTotal);
var headerTableCalls = document.createElement("th");
headerTableCalls.classname = "narrow";
headerTableCalls.addEventListener("click", this._toggleSortOrder.bind(this), false);
headerTableCalls.textContent = WebInspector.UIString("Calls");
headerTableTR.appendChild(headerTableCalls);
var headerTableSymbol = document.createElement("th");
headerTableSymbol.addEventListener("click", this._toggleSortOrder.bind(this), false);
headerTableSymbol.textContent = WebInspector.UIString("Function");
headerTableTR.appendChild(headerTableSymbol);
this.showSelfTimeAsPercent = true;
this.showTotalTimeAsPercent = true;
var columns = { "self": { title: WebInspector.UIString("Self"), width: "72px", sortable: true },
"total": { title: WebInspector.UIString("Total"), width: "72px", sort: "descending", sortable: true },
"calls": { title: WebInspector.UIString("Calls"), width: "54px", sortable: true },
"function": { title: WebInspector.UIString("Function"), disclosure: true, sortable: true } };
this.dataGrid = new WebInspector.DataGrid(columns);
this.dataGrid.addEventListener("sorting changed", this._sortData, this);
this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
this.element.appendChild(this.dataGrid.element);
// By default the profile isn't sorted, so sort based on our default sort
// column and direction padded to the DataGrid above.
profile.head.sortTotalTimeDescending();
this.refresh();
}
WebInspector.ProfileView.prototype = {
refresh: function()
{
var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;
this.dataGrid.removeChildren();
_toggleSortOrder: function(event)
var children = this.profile.head.children;
var childrenLength = children.length;
for (var i = 0; i < childrenLength; ++i)
this.dataGrid.appendChild(new WebInspector.ProfileDataGridNode(children[i], this.showSelfTimeAsPercent, this.showTotalTimeAsPercent));
if (selectedProfileNode && selectedProfileNode._dataGridNode)
selectedProfileNode._dataGridNode.selected = true;
},
refreshShowAsPercents: function()
{
var headerElement = event.target;
if (headerElement.hasStyleClass("sort-descending")) {
headerElement.removeStyleClass("sort-descending");
headerElement.addStyleClass("sort-ascending");
} else if (headerElement.hasStyleClass("sort-ascending")) {
headerElement.removeStyleClass("sort-ascending");
headerElement.addStyleClass("sort-descending");
} else {
this.selectedHeaderColumn.removeStyleClass("sort-ascending");
this.selectedHeaderColumn.removeStyleClass("sort-descending");
this.selectedHeaderColumn.removeStyleClass("selected");
headerElement.addStyleClass("sort-descending");
headerElement.addStyleClass("selected");
this.selectedHeaderColumn = headerElement;
var child = this.dataGrid.children[0];
while (child) {
child.showTotalTimeAsPercent = this.showTotalTimeAsPercent;
child.showSelfTimeAsPercent = this.showSelfTimeAsPercent;
child.refresh();
child = child.traverseNextNode(false, null, true);
}
},
_sortData: function(event)
{
var sortOrder = this.dataGrid.sortOrder;
var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
var sortingFunctionName = "sort";
if (sortColumnIdentifier === "self")
sortingFunctionName += "SelfTime";
else if (sortColumnIdentifier === "total")
sortingFunctionName += "TotalTime";
else if (sortColumnIdentifier === "calls")
sortingFunctionName += "Calls";
else if (sortColumnIdentifier === "function")
sortingFunctionName += "FunctionName";
if (sortOrder === "ascending")
sortingFunctionName += "Ascending";
else
sortingFunctionName += "Descending";
if (!(sortingFunctionName in this.profile.head))
return;
this.profile.head[sortingFunctionName]();
this.refresh();
},
_mouseDownInDataGrid: function(event)
{
if (event.detail < 2)
return;
var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column")))
return;
if (cell.hasStyleClass("total-column"))
this.showTotalTimeAsPercent = !this.showTotalTimeAsPercent;
else if (cell.hasStyleClass("self-column"))
this.showSelfTimeAsPercent = !this.showSelfTimeAsPercent;
this.refreshShowAsPercents();
event.preventDefault();
event.stopPropagation();
}
}
WebInspector.ProfileView.prototype.__proto__ = WebInspector.View.prototype;
WebInspector.ProfileDataGridNode = function(profileNode, showSelfTimeAsPercent, showTotalTimeAsPercent)
{
this.profileNode = profileNode;
profileNode._dataGridNode = this;
this.showSelfTimeAsPercent = showSelfTimeAsPercent;
this.showTotalTimeAsPercent = showTotalTimeAsPercent;
var hasChildren = (profileNode.children.length ? true : false);
WebInspector.DataGridNode.call(this, null, hasChildren);
this.addEventListener("populate", this._populate, this);
this.expanded = profileNode._expanded;
}
WebInspector.ProfileDataGridNode.prototype = {
get data()
{
function formatMilliseconds(time)
{
return Number.secondsToString(time / 1000, WebInspector.UIString.bind(WebInspector), true);
}
var data = {};
data["function"] = this.profileNode.functionName;
data["calls"] = this.profileNode.numberOfCalls;
if (this.showSelfTimeAsPercent)
data["self"] = WebInspector.UIString("%.2f%%", this.profileNode.selfPercent);
else
data["self"] = formatMilliseconds(this.profileNode.selfTime);
if (this.showTotalTimeAsPercent)
data["total"] = WebInspector.UIString("%.2f%%", this.profileNode.totalPercent);
else
data["total"] = formatMilliseconds(this.profileNode.totalTime);
return data;
},
expand: function()
{
WebInspector.DataGridNode.prototype.expand.call(this);
this.profileNode._expanded = true;
},
collapse: function()
{
WebInspector.DataGridNode.prototype.collapse.call(this);
this.profileNode._expanded = false;
},
_populate: function(event)
{
var children = this.profileNode.children;
var childrenLength = children.length;
for (var i = 0; i < childrenLength; ++i)
this.appendChild(new WebInspector.ProfileDataGridNode(children[i], this.showSelfTimeAsPercent, this.showTotalTimeAsPercent));
this.removeEventListener("populate", this._populate, this);
}
}
WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
......@@ -34,11 +34,23 @@ WebInspector.ProfilesPanel = function()
this.sidebarElement.className = "sidebar";
this.element.appendChild(this.sidebarElement);
this.sidebarResizeElement = document.createElement("div");
this.sidebarResizeElement.className = "sidebar-resizer-vertical";
this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
this.element.appendChild(this.sidebarResizeElement);
this.sidebarTreeElement = document.createElement("ol");
this.sidebarTreeElement.className = "sidebar-tree";
this.sidebarElement.appendChild(this.sidebarTreeElement);
this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
this.profileViews = document.createElement("div");
this.profileViews.id = "profile-views";
this.element.appendChild(this.profileViews);
}
this.reset();
}
WebInspector.ProfilesPanel.prototype = {
toolbarItemClass: "profiles",
......@@ -47,6 +59,163 @@ WebInspector.ProfilesPanel.prototype = {
{
return WebInspector.UIString("Profiles");
},
show: function()
{
WebInspector.Panel.prototype.show.call(this);
this._updateSidebarWidth();
// FIXME: the only way to get profiles right now is to ask for the array of all profiles
// from the InspectorController. We need to add callback to the profiler that will notify
// the Inspector when a new profile is made. That way we can keep the UI updated.
this.sidebarTree.removeChildren();
var profiles = InspectorController.allProfiles();
var profilesLength = profiles.length;
for (var i = 0; i < profilesLength; ++i) {
var profile = profiles[i];
this.addProfile(profile);
}
if (profiles[0])
profiles[0]._profilesTreeElement.select();
},
reset: function()
{
if (this._profiles) {
var profiledLength = this._profiles.length;
for (var i = 0; i < profiledLength; ++i) {
var profile = this._profiles[i];
delete profile._profileView;
}
}
this._profiles = [];
this.sidebarTree.removeChildren();
this.profileViews.removeChildren();
},
handleKeyEvent: function(event)
{
this.sidebarTree.handleKeyEvent(event);
},
addProfile: function(profile)
{
this._profiles.push(profile);
var profileTreeElement = new WebInspector.ProfileSidebarTreeElement(profile);
profile._profilesTreeElement = profileTreeElement;
this.sidebarTree.appendChild(profileTreeElement);
},
showProfile: function(profile)
{
if (!profile)
return;
if (this.visibleProfileView)
this.visibleProfileView.hide();
var view = profile._profileView;
if (!view) {
view = new WebInspector.ProfileView(profile);
profile._profileView = view;
}
view.show(this.profileViews);
this.visibleProfileView = view;
},
closeVisibleView: function()
{
if (this.visibleProfileView)
this.visibleProfileView.hide();
delete this.visibleProfileView;
},
_startSidebarDragging: function(event)
{
WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
},
_sidebarDragging: function(event)
{
this._updateSidebarWidth(event.pageX);
event.preventDefault();
},
_endSidebarDragging: function(event)
{
WebInspector.elementDragEnd(event);
},
_updateSidebarWidth: function(width)
{
if (this.sidebarElement.offsetWidth <= 0) {
// The stylesheet hasn't loaded yet, so we need to update later.
setTimeout(this._updateSidebarWidth.bind(this), 0, width);
return;
}
if (!("_currentSidebarWidth" in this))
this._currentSidebarWidth = this.sidebarElement.offsetWidth;
if (typeof width === "undefined")
width = this._currentSidebarWidth;
width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
this._currentSidebarWidth = width;
this.sidebarElement.style.width = width + "px";
this.profileViews.style.left = width + "px";
this.sidebarResizeElement.style.left = (width - 3) + "px";
}
}
WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
WebInspector.ProfileSidebarTreeElement = function(profile)
{
this.profile = profile;
WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "", "", profile, false);
this.refreshTitles();
}
WebInspector.ProfileSidebarTreeElement.prototype = {
onselect: function()
{
WebInspector.panels.profiles.showProfile(this.profile);
},
get mainTitle()
{
return this.profile.title;
},
set mainTitle(x)
{
// Can't change mainTitle.
},
get subtitle()
{
// There is no subtitle.
},
set subtitle(x)
{
// Can't change subtitle.
}
}
WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
......@@ -2376,6 +2376,10 @@ body.inactive .sidebar-tree-item.selected {
bottom: 0;
}
.profile-sidebar-tree-item .icon {
content: url(Images/profileIcon.png);
}
.profile-view {
display: none;
overflow: hidden;
......@@ -2392,4 +2396,25 @@ body.inactive .sidebar-tree-item.selected {
.profile-view .data-grid {
border: none;
height: 100%;
}
.profile-view .data-grid th.self-column {
text-align: center;
}
.profile-view .data-grid td.self-column {
text-align: right;
}
.profile-view .data-grid th.total-column {
text-align: center;
}
.profile-view .data-grid td.total-column {
text-align: right;
}
.profile-view .data-grid .calls-column {
text-align: center;
}
......@@ -276,6 +276,7 @@ WebInspector.loaded = function()
elements: new WebInspector.ElementsPanel(),
resources: new WebInspector.ResourcesPanel(),
scripts: new WebInspector.ScriptsPanel(),
profiles: new WebInspector.ProfilesPanel(),
databases: new WebInspector.DatabasesPanel()
};
......
......@@ -692,13 +692,15 @@ function nodeTitleInfo(hasChildren, linkify)
return info;
}
Number.secondsToString = function(seconds, formatterFunction)
Number.secondsToString = function(seconds, formatterFunction, higherResolution)
{
if (!formatterFunction)
formatterFunction = String.sprintf;
var ms = seconds * 1000;
if (ms < 1000)
if (higherResolution && ms < 1000)
return formatterFunction("%.3fms", ms);
else if (ms < 1000)
return formatterFunction("%.0fms", ms);
if (seconds < 60)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment