Commit bf077ecd authored by aroben@apple.com's avatar aroben@apple.com

Make it possible to view all leaks from a build at once in Leaks Viewer

You can now give Leaks Viewer the URL of a build results page (e.g.,
<http://build.webkit.org/results/SnowLeopard%20Intel%20Leaks/r80847%20(15493)/>), and it
will load all the leaks files from that build.

Fixes <http://webkit.org/b/56030> Leaks Viewer: Would like to be able to look at all leaks
files from a particular build at once, rather than one at a time

Reviewed by David Kilzer.

* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/LeaksLoader.js: Added.
(LeaksLoader): Initialize our properties.
(LeaksLoader.prototype.start): If the URL ends in .txt, assume it's a single leaks file and
load it. Otherwise assume it's a build results page URL and start fetching the leaks files
from that page.
(LeaksLoader.prototype._loadLeaksFiles): Tell our client how many leaks files we're loading
so it can provide feedback. Then load each one and pass it to the client.
(LeaksLoader.prototype._loadLeaksFromResultsPage): Load the HTML of the results page, parse
it into a DOM, pull out all the links to leaks files, and load each one.

* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/LeaksParser.js: Added.
(LeaksParser): Initialize our worker.
(LeaksParser.prototype.addLeaksFile): Tell the worker about the leaks file's contents.

* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/LeaksParserWorker.js: Added.
This code mostly came from Worker.js.
(LeaksParserWorker): Initialize our profile.
(LeaksParserWorker.prototype.addLeaksFile): Parse the leaks file and incorporate it into our
profile.
(LeaksParserWorker.prototype._parseLeaks):
(LeaksParserWorker.prototype._createNode):
(LeaksParserWorker.prototype._incorporateLeaks):
Moved these functions here from LeaksViewer. _incorporateLeaks is essentially just a renamed
version of createProfile which adds to this.profile instead of making a new profile each
time.
(onmessage): Parse the file and send back the new profile.

* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/LeaksViewer.css:
(#loading-indicator):
(#spinner):
(#loading-indicator-label):
Tweaked styles to accomodate a longer label.

* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/LeaksViewer.js:
(LeaksViewer.loaded): Set up a loader and parser.

(LeaksViewer.get filesLeftToParse):
(LeaksViewer.set filesLeftToParse):
Added these simple accessors.

(LeaksViewer._didCountLeaksFiles): Callback from LeaksLoader. Stores the count.
(LeaksViewer._didLoadLeaksFile): Callback from LeaksLoader. Passes the file contents off to
the parser.
(LeaksViewer._didParseLeaksFile): Callback from LeaksParser. If all files have been parsed,
tell the ProfilerAgent and mark that we're done loading. (Code came from the old
_loadLeaksFromURL function).
(LeaksViewer._loadLeaksFromURL): Now just calls through to the loader.
(LeaksViewer._loadingIndicatorText): Added. Returns the text that should show up in the
loading indicator, including the number of files being loaded.
(LeaksViewer._loadingStatusChanged): Update the loading indicator's label, too.
(LeaksViewer._updateLoadingIndicatorLabel): Added. Just updates the label!
(LeaksViewer._updateTitle): Moved code to compute the "Loading" text to
_loadingIndicatorText.

* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/Utilities.js: Added.
(getResource): Moved here from LeaksViewer.js.

* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/Worker.js: Subsumed by
LeaksParserWorker.js.

* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/index.html: Added new JS
files, added a #loading-indicator-label element, and tweaked the prompt wording.

git-svn-id: svn://svn.chromium.org/blink/trunk@80864 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 5f2e8030
......@@ -23,56 +23,43 @@
* THE POSSIBILITY OF SUCH DAMAGE.
*/
onmessage = function(e) {
postMessage(createProfile(parseLeaks(e.data)));
function LeaksLoader(didCountLeaksFilesCallback, didLoadLeaksFileCallback) {
this._didCountLeaksFilesCallback = didCountLeaksFilesCallback;
this._didLoadLeaksFileCallback = didLoadLeaksFileCallback;
}
function parseLeaks(text) {
var leaks = [];
text.split("\n").forEach(function(line) {
if (!/^\s+Call stack:/.test(line))
return;
LeaksLoader.prototype = {
start: function(url) {
if (/\.txt$/.test(url))
this._loadLeaksFiles([url]);
else
this._loadLeaksFromResultsPage(url);
},
// The first frame is not really a frame at all ("Call stack: thread 0xNNNNN:"), so we omit it.
leaks.push(line.split(" | ").slice(1).map(function(str) { return str.trim(); }));
});
return leaks;
}
_loadLeaksFiles: function(urls) {
this._didCountLeaksFilesCallback(urls.length);
function createNode(functionName) {
return {
functionName: functionName,
selfTime: 0,
totalTime: 0,
children: [],
childrenByName: {},
callUID: functionName,
};
}
var self = this;
var pendingURLs = urls.length;
urls.forEach(function(url) {
getResource(url, function(xhr) {
self._didLoadLeaksFileCallback(xhr.responseText);
});
});
},
// This function creates a fake "profile" from a set of leak stacks. "selfTime" is the number of
// stacks in which this function was at the top (in theory, only functions like malloc should have a
// non-zero selfTime). "totalTime" is the number of stacks which contain this function (and thus is
// the number of leaks that occurred in or beneath this function).
// FIXME: This is expensive! Can we parallelize it?
function createProfile(leaks) {
var head = createNode("top level");
leaks.forEach(function(leak) {
leak.reduce(function(node, frame, index, array) {
var childNode;
if (frame in node.childrenByName)
childNode = node.childrenByName[frame];
else {
childNode = createNode(frame);
childNode.head = head;
node.childrenByName[frame] = childNode;
node.children.push(childNode);
}
if (index === array.length - 1)
++childNode.selfTime;
++childNode.totalTime;
return childNode;
}, head);
});
return head;
}
_loadLeaksFromResultsPage: function(url) {
var self = this;
getResource(url, function(xhr) {
var root = document.createElement("html");
root.innerHTML = xhr.responseText;
// Strip off everything after the last /.
var baseURL = url.substring(0, url.lastIndexOf("/") + 1);
var urls = Array.prototype.map.call(root.querySelectorAll("tr.file > td > a[href$='-leaks.txt']"), function(link) { return baseURL + link.getAttribute("href"); });
self._loadLeaksFiles(urls);
});
},
};
/*
* Copyright (C) 2011 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
function LeaksParser(didParseLeaksFileCallback) {
this._didParseLeaksFileCallback = didParseLeaksFileCallback;
this._worker = new Worker("LeaksParserWorker.js");
var self = this;
this._worker.onmessage = function(e) {
self._didParseLeaksFileCallback(e.data);
};
}
LeaksParser.prototype = {
addLeaksFile: function(leaksText) {
this._worker.postMessage(leaksText);
},
};
/*
* Copyright (C) 2011 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
function LeaksParserWorker() {
this.profile = this._createNode("top level");
}
LeaksParserWorker.prototype = {
addLeaksFile: function(leaksText) {
this._incorporateLeaks(this._parseLeaks(leaksText));
},
_parseLeaks: function(text) {
var leaks = [];
text.split("\n").forEach(function(line) {
if (!/^\s+Call stack:/.test(line))
return;
// The first frame is not really a frame at all ("Call stack: thread 0xNNNNN:"), so we omit it.
leaks.push(line.split(" | ").slice(1).map(function(str) { return str.trim(); }));
});
return leaks;
},
_createNode: function(functionName) {
return {
functionName: functionName,
selfTime: 0,
totalTime: 0,
children: [],
childrenByName: {},
callUID: functionName,
};
},
// This function creates a fake "profile" from a set of leak stacks. "selfTime" is the number of
// stacks in which this function was at the top (in theory, only functions like malloc should have a
// non-zero selfTime). "totalTime" is the number of stacks which contain this function (and thus is
// the number of leaks that occurred in or beneath this function).
// FIXME: This is expensive! Can we parallelize it?
_incorporateLeaks: function(leaks) {
var self = this;
leaks.forEach(function(leak) {
leak.reduce(function(node, frame, index, array) {
var childNode;
if (frame in node.childrenByName)
childNode = node.childrenByName[frame];
else {
childNode = self._createNode(frame);
childNode.head = self.profile;
node.childrenByName[frame] = childNode;
node.children.push(childNode);
}
if (index === array.length - 1)
++childNode.selfTime;
++childNode.totalTime;
return childNode;
}, self.profile);
});
},
};
var parser = new LeaksParserWorker();
onmessage = function(e) {
parser.addLeaksFile(e.data);
postMessage(parser.profile);
}
......@@ -51,13 +51,19 @@
#loading-indicator {
position: absolute;
right: 20px;
width: 150px;
margin-top: 5px;
}
#loading-indicator > img {
#spinner {
float: left;
margin-top: -1px;
}
#loading-indicator-label {
margin-left: 5px;
}
.percent-time-status-bar-item {
/* We always show leak counts as real values, not percentages, so this button isn't useful. */
display: none !important;
......
......@@ -25,7 +25,11 @@
var LeaksViewer = {
loaded: function() {
this._loader = new LeaksLoader(this._didCountLeaksFiles.bind(this), this._didLoadLeaksFile.bind(this));
this._parser = new LeaksParser(this._didParseLeaksFile.bind(this));
this._loadingIndicator = document.getElementById("loading-indicator");
this._loadingIndicatorLabel = document.getElementById("loading-indicator-label");
this._profileView = new WebInspector.CPUProfileView({});
document.getElementById("main-panels").appendChild(this._profileView.element);
......@@ -51,6 +55,19 @@ var LeaksViewer = {
this._displayURLPrompt();
},
get filesLeftToParse() {
if (!('_filesLeftToParse' in this))
this._filesLeftToParse = 0;
return this._filesLeftToParse;
},
set filesLeftToParse(x) {
if (this._filesLeftToParse === x)
return;
this._filesLeftToParse = x;
this._loadingStatusChanged();
},
get loading() {
return this._isLoading;
},
......@@ -79,6 +96,22 @@ var LeaksViewer = {
document.getElementById("url-prompt-container").addStyleClass("hidden");
},
_didCountLeaksFiles: function(fileCount) {
this._fileCount = fileCount;
this.filesLeftToParse = fileCount;
},
_didLoadLeaksFile: function(leaksText) {
this._parser.addLeaksFile(leaksText);
},
_didParseLeaksFile: function(profile) {
if (--this.filesLeftToParse)
return;
ProfilerAgent.profileReady(profile);
this.loading = false;
},
_displayURLPrompt: function() {
document.getElementById("url-prompt-container").removeStyleClass("hidden");
document.getElementById("url").focus();
......@@ -88,19 +121,20 @@ var LeaksViewer = {
this.url = url;
this.loading = true;
var self = this;
getResource(url, function(xhr) {
var worker = new Worker("Worker.js");
worker.onmessage = function(e) {
ProfilerAgent.profileReady(e.data);
self.loading = false;
};
worker.postMessage(xhr.responseText);
});
this._loader.start(this.url);
},
_loadingIndicatorText: function() {
var text = "Loading";
if (this.filesLeftToParse)
text += " " + (this._fileCount - this.filesLeftToParse + 1) + "/" + this._fileCount + " files";
text += "\u2026";
return text;
},
_loadingStatusChanged: function() {
this._setLoadingIndicatorHidden(!this.loading);
this._updateLoadingIndicatorLabel();
this._updateTitle();
},
......@@ -111,24 +145,17 @@ var LeaksViewer = {
this._loadingIndicator.removeStyleClass("hidden");
},
_updateLoadingIndicatorLabel: function() {
this._loadingIndicatorLabel.innerText = this._loadingIndicatorText();
},
_updateTitle: function() {
var title = "Leaks Viewer \u2014 ";
if (this.loading)
title += "(Loading\u2026) ";
title += "(" + this._loadingIndicatorText() + ") ";
title += this.url;
document.title = title;
},
};
function getResource(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
// Allow a status of 0 for easier testing with local files.
if (this.readyState == 4 && (!this.status || this.status == 200))
callback(this);
};
xhr.open("GET", url);
xhr.send();
}
addEventListener("load", LeaksViewer.loaded.bind(LeaksViewer));
/*
* Copyright (C) 2011 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
function getResource(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
// Allow a status of 0 for easier testing with local files.
if (this.readyState == 4 && (!this.status || this.status == 200))
callback(this);
};
xhr.open("GET", url);
xhr.send();
}
......@@ -52,19 +52,22 @@ THE POSSIBILITY OF SUCH DAMAGE.
monkeyPatchInspectorObjects();
</script>
<script src=LeaksLoader.js></script>
<script src=LeaksParser.js></script>
<script src=LeaksViewer.js></script>
<script src=Utilities.js></script>
</head>
<body>
<div id=main-panels></div>
<div id=main-status-bar class=status-bar>
<div id=loading-indicator class=hidden>
<img id=spinner src=http://svn.webkit.org/repository/webkit/!svn/bc/80565/trunk/Source/WebCore/inspector/front-end/Images/spinner.gif>
Loading&hellip;
<span id=loading-indicator-label>Loading&hellip;</span>
</div>
</div>
<div id=url-prompt-container class=hidden>
<div id=url-prompt>
<p>Please enter the URL of a leaks file:</p>
<p>Please enter the URL of a build results page or leaks file:</p>
<input id=url type=url><button onclick='LeaksViewer.urlPromptButtonClicked(event)'>Fetch leaks</button>
</div>
</div>
......
2011-03-11 Adam Roben <aroben@apple.com>
Make it possible to view all leaks from a build at once in Leaks Viewer
You can now give Leaks Viewer the URL of a build results page (e.g.,
<http://build.webkit.org/results/SnowLeopard%20Intel%20Leaks/r80847%20(15493)/>), and it
will load all the leaks files from that build.
Fixes <http://webkit.org/b/56030> Leaks Viewer: Would like to be able to look at all leaks
files from a particular build at once, rather than one at a time
Reviewed by David Kilzer.
* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/LeaksLoader.js: Added.
(LeaksLoader): Initialize our properties.
(LeaksLoader.prototype.start): If the URL ends in .txt, assume it's a single leaks file and
load it. Otherwise assume it's a build results page URL and start fetching the leaks files
from that page.
(LeaksLoader.prototype._loadLeaksFiles): Tell our client how many leaks files we're loading
so it can provide feedback. Then load each one and pass it to the client.
(LeaksLoader.prototype._loadLeaksFromResultsPage): Load the HTML of the results page, parse
it into a DOM, pull out all the links to leaks files, and load each one.
* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/LeaksParser.js: Added.
(LeaksParser): Initialize our worker.
(LeaksParser.prototype.addLeaksFile): Tell the worker about the leaks file's contents.
* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/LeaksParserWorker.js: Added.
This code mostly came from Worker.js.
(LeaksParserWorker): Initialize our profile.
(LeaksParserWorker.prototype.addLeaksFile): Parse the leaks file and incorporate it into our
profile.
(LeaksParserWorker.prototype._parseLeaks):
(LeaksParserWorker.prototype._createNode):
(LeaksParserWorker.prototype._incorporateLeaks):
Moved these functions here from LeaksViewer. _incorporateLeaks is essentially just a renamed
version of createProfile which adds to this.profile instead of making a new profile each
time.
(onmessage): Parse the file and send back the new profile.
* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/LeaksViewer.css:
(#loading-indicator):
(#spinner):
(#loading-indicator-label):
Tweaked styles to accomodate a longer label.
* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/LeaksViewer.js:
(LeaksViewer.loaded): Set up a loader and parser.
(LeaksViewer.get filesLeftToParse):
(LeaksViewer.set filesLeftToParse):
Added these simple accessors.
(LeaksViewer._didCountLeaksFiles): Callback from LeaksLoader. Stores the count.
(LeaksViewer._didLoadLeaksFile): Callback from LeaksLoader. Passes the file contents off to
the parser.
(LeaksViewer._didParseLeaksFile): Callback from LeaksParser. If all files have been parsed,
tell the ProfilerAgent and mark that we're done loading. (Code came from the old
_loadLeaksFromURL function).
(LeaksViewer._loadLeaksFromURL): Now just calls through to the loader.
(LeaksViewer._loadingIndicatorText): Added. Returns the text that should show up in the
loading indicator, including the number of files being loaded.
(LeaksViewer._loadingStatusChanged): Update the loading indicator's label, too.
(LeaksViewer._updateLoadingIndicatorLabel): Added. Just updates the label!
(LeaksViewer._updateTitle): Moved code to compute the "Loading" text to
_loadingIndicatorText.
* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/Utilities.js: Added.
(getResource): Moved here from LeaksViewer.js.
* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/Worker.js: Subsumed by
LeaksParserWorker.js.
* BuildSlaveSupport/build.webkit.org-config/public_html/LeaksViewer/index.html: Added new JS
files, added a #loading-indicator-label element, and tweaked the prompt wording.
2011-03-11 Adam Roben <aroben@apple.com>
Don't trigger a build when build.webkit.org's HTML files are modified
......
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