Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

began implementing heap snapshots

  • Loading branch information...
commit 28c3b6d906d801cc740598ce6e542e0c0e27a693 1 parent a7e32cb
@dannycoates dannycoates authored
Showing with 4,311 additions and 4 deletions.
  1. +3 −0  .gitignore
  2. +25 −0 bin/heap-helper.js
  3. BIN  bin/profiler.node
  4. +3 −1 bin/session.js
  5. BIN  front-end/Images/segmentChromium.png
  6. BIN  front-end/Images/segmentHoverChromium.png
  7. BIN  front-end/Images/segmentHoverEndChromium.png
  8. BIN  front-end/Images/segmentSelectedChromium.png
  9. BIN  front-end/Images/segmentSelectedEndChromium.png
  10. BIN  front-end/Images/statusbarBackgroundChromium.png
  11. BIN  front-end/Images/statusbarBottomBackgroundChromium.png
  12. BIN  front-end/Images/statusbarButtonsChromium.png
  13. BIN  front-end/Images/statusbarMenuButtonChromium.png
  14. BIN  front-end/Images/statusbarMenuButtonSelectedChromium.png
  15. +234 −0 front-end/devTools.css
  16. +12 −0 front-end/index.html
  17. +7 −1 front-end/node/Debugger.js
  18. +18 −1 front-end/node/InspectorBackendStub.js
  19. +5 −1 front-end/node/InspectorFrontendHostStub.js
  20. +251 −0 front-end/v8/DevTools.js
  21. +38 −0 front-end/v8/DevToolsHostStub.js
  22. +966 −0 front-end/v8/HeapProfilerPanel.js
  23. +138 −0 front-end/v8/ProfilerAgent.js
  24. +543 −0 front-end/v8/ProfilerProcessor.js
  25. +270 −0 front-end/v8/codemap.js
  26. +93 −0 front-end/v8/consarray.js
  27. +83 −0 front-end/v8/csvparser.js
  28. +338 −0 front-end/v8/logreader.js
  29. +691 −0 front-end/v8/profile.js
  30. +224 −0 front-end/v8/profile_view.js
  31. +322 −0 front-end/v8/splaytree.js
  32. +30 −0 native/profiler.cc
  33. +15 −0 native/wscript
  34. +2 −0  test/hello.js
View
3  .gitignore
@@ -1,2 +1,5 @@
._*
v8.log
+native/build
+bin/.*
+native/.*
View
25 bin/heap-helper.js
@@ -0,0 +1,25 @@
+var getLines = require('./profiler').getLogLines;
+
+
+var procProto = global.v8debug.DebugCommandProcessor.prototype;
+var oldReqHandler = procProto.processDebugJSONRequest;
+
+function newReqHandler(json_request) {
+ var req = JSON.parse(json_request);
+ if (req.command === 'getloglines') {
+ var res = response = this.createResponse(req);
+ try {
+ res.body = getLines(req.position);
+ }
+ catch (e) {
+ res.failed('profiler error');
+ }
+ res.running = this.running_;
+ return res.toJSONProtocol();
+ }
+ else {
+ return oldReqHandler.call(this, json_request);
+ }
+};
+
+procProto.processDebugJSONRequest = newReqHandler;
View
BIN  bin/profiler.node
Binary file not shown
View
4 bin/session.js
@@ -53,7 +53,9 @@ exports.createSession = function (options) {
'lookup',
'evaluate',
'backtrace',
- 'listbreakpoints'],
+ 'listbreakpoints',
+ 'profile',
+ 'getloglines'],
flag = '--debug=',
proc,
sendIo,
View
BIN  front-end/Images/segmentChromium.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  front-end/Images/segmentHoverChromium.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  front-end/Images/segmentHoverEndChromium.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  front-end/Images/segmentSelectedChromium.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  front-end/Images/segmentSelectedEndChromium.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  front-end/Images/statusbarBackgroundChromium.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  front-end/Images/statusbarBottomBackgroundChromium.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  front-end/Images/statusbarButtonsChromium.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  front-end/Images/statusbarMenuButtonChromium.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  front-end/Images/statusbarMenuButtonSelectedChromium.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
234 front-end/devTools.css
@@ -0,0 +1,234 @@
+.data-grid table {
+ line-height: 120%;
+}
+
+body.attached #toolbar {
+ height: 34px;
+ border-top: 1px solid rgb(100, 100, 100);
+ cursor: default; /* overriden */
+ padding-left: 0;
+}
+
+/* Chrome theme overrides */
+
+body.platform-windows #toolbar, body.platform-windows.inactive #toolbar {
+ background-image: none;
+}
+
+body.detached.platform-mac-leopard #toolbar {
+ background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(175, 175, 175)), to(rgb(151, 151, 151))) !important;
+}
+
+body.detached.platform-mac-leopard.inactive #toolbar {
+ background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(221, 221, 221)), to(rgb(207, 207, 207))) !important;
+}
+
+body.detached.platform-mac-snowleopard #toolbar {
+ background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(189, 189, 189)), to(rgb(151, 151, 151))) !important;
+}
+
+body.detached.platform-mac-snowleopard.inactive #toolbar {
+ background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(215, 215, 215)), to(rgb(207, 207, 207))) !important;
+}
+
+body.platform-linux #scripts-files {
+ font-size: 11px;
+ font-weight: normal;
+ line-height: 12px;
+}
+
+/* Heap Profiler Styles */
+
+.heap-snapshot-status-bar-item .glyph {
+ -webkit-mask-image: url(Images/focusButtonGlyph.png);
+}
+
+.heap-snapshot-sidebar-tree-item .icon {
+ content: url(Images/profileIcon.png);
+}
+
+.heap-snapshot-sidebar-tree-item.small .icon {
+ content: url(Images/profileSmallIcon.png);
+}
+
+.heap-snapshot-view {
+ display: none;
+ overflow: hidden;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+
+.heap-snapshot-view.visible {
+ display: block;
+}
+
+.heap-snapshot-view .data-grid {
+ border: none;
+ max-height: 100%;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 93px;
+}
+
+.heap-snapshot-view .data-grid th.count-column {
+ text-align: center;
+}
+
+.heap-snapshot-view .data-grid td.count-column {
+ text-align: right;
+}
+
+.heap-snapshot-view .data-grid th.size-column {
+ text-align: center;
+}
+
+.heap-snapshot-view .data-grid td.size-column {
+ text-align: right;
+}
+
+.heap-snapshot-view .data-grid th.countDelta-column {
+ text-align: center;
+}
+
+.heap-snapshot-view .data-grid td.countDelta-column {
+ text-align: right;
+}
+
+.heap-snapshot-view .data-grid th.sizeDelta-column {
+ text-align: center;
+}
+
+.heap-snapshot-view .data-grid td.sizeDelta-column {
+ text-align: right;
+}
+
+#heap-snapshot-summary-container {
+ position: absolute;
+ padding-top: 20px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 93px;
+ margin-left: -1px;
+ border-left: 1px solid rgb(102, 102, 102);
+ background-color: rgb(101, 111, 130);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0)));
+ background-repeat: repeat-x;
+ background-position: top;
+ text-align: center;
+ text-shadow: black 0 1px 1px;
+ white-space: nowrap;
+ color: white;
+ -webkit-background-size: 1px 6px;
+ -webkit-background-origin: padding;
+ -webkit-background-clip: padding;
+}
+
+.heap-snapshot-summary {
+ display: inline-block;
+ width: 50%;
+ min-width: 300px;
+ position: relative;
+}
+
+.heap-snapshot-summary canvas.summary-graph {
+ width: 225px;
+}
+
+.heap-snapshot-summary-label {
+ font-size: 12px;
+ font-weight: bold;
+ position: absolute;
+ top: 1px;
+ width: 50%;
+ left: 25%;
+}
+
+.section > .header {
+ border: 1px solid rgb(92, 116, 157);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(105, 133, 180)), to(rgb(92, 116, 157)));
+}
+
+.console-group-messages .section > .header {
+ padding: 0 8px 0 0;
+ background-image: none;
+ border: none;
+ min-height: 0;
+}
+
+#resources-filter {
+ background: -webkit-gradient(linear, left top, left bottom, from(rgb(233, 233, 233)), to(rgb(233, 233, 233)));
+}
+
+.crumbs .crumb {
+ -webkit-border-image: url(Images/segmentChromium.png) 0 12 0 2;
+ margin-right: -3px;
+ padding-left: 6px;
+}
+
+.crumbs .crumb.selected {
+ -webkit-border-image: url(Images/segmentSelectedChromium.png) 0 12 0 2;
+ color: white;
+ text-shadow: rgba(255, 255, 255, 0.5) 0 0px 0;
+}
+
+.crumbs .crumb.selected:hover {
+ -webkit-border-image: url(Images/segmentSelectedChromium.png) 0 12 0 2;
+}
+
+.crumbs .crumb.selected.end, .crumbs .crumb.selected.end:hover {
+ -webkit-border-image: url(Images/segmentSelectedEndChromium.png) 0 2 0 2;
+}
+
+.crumbs .crumb:hover {
+ -webkit-border-image: url(Images/segmentHoverChromium.png) 0 12 0 2;
+}
+
+.crumbs .crumb.dimmed:hover {
+ -webkit-border-image: url(Images/segmentHoverChromium.png) 0 12 0 2;
+}
+
+.crumbs .crumb.end:hover {
+ -webkit-border-image: url(Images/segmentHoverEndChromium.png) 0 2 0 2;
+}
+
+body.drawer-visible #main-status-bar {
+ background-image: url(Images/statusbarResizerVertical.png), url(Images/statusbarBackgroundChromium.png);
+}
+
+.status-bar {
+ background-image: url(Images/statusbarBackgroundChromium.png);
+}
+
+button.status-bar-item {
+ background-image: url(Images/statusbarButtonsChromium.png);
+}
+
+select.status-bar-item:active {
+ -webkit-border-image: url(Images/statusbarMenuButtonSelectedChromium.png) 0 17 0 2;
+}
+
+#drawer {
+ background-image: url(Images/statusbarBottomBackgroundChromium.png);
+}
+
+select.status-bar-item {
+ -webkit-border-image: url(Images/statusbarMenuButtonChromium.png) 0 17 0 2;
+}
+
+.scope-bar li.selected {
+ -webkit-box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.0);
+}
+
+.scope-bar li:active {
+ -webkit-box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.0);
+}
+
+.timeline-category-statusbar-item input {
+ vertical-align: middle;
+}
View
12 front-end/index.html
@@ -134,6 +134,18 @@
<script type="text/javascript" src="ShortcutsHelp.js"></script>
<script type="text/javascript" src="HAREntry.js"></script>
<script type="text/javascript" src="node/Overrides.js"></script>
+
+ <link rel="stylesheet" type="text/css" href="devTools.css">
+ <script type="text/javascript" src="v8/codemap.js"></script>
+ <script type="text/javascript" src="v8/consarray.js"></script>
+ <script type="text/javascript" src="v8/csvparser.js"></script>
+ <script type="text/javascript" src="v8/logreader.js"></script>
+ <script type="text/javascript" src="v8/profile.js"></script>
+ <script type="text/javascript" src="v8/profile_view.js"></script>
+ <script type="text/javascript" src="v8/splaytree.js"></script>
+ <script type="text/javascript" src="v8/ProfilerAgent.js"></script>
+ <script type="text/javascript" src="v8/ProfilerProcessor.js"></script>
+ <script type="text/javascript" src="v8/HeapProfilerPanel.js"></script>
</head>
<body class="attached">
<div id="toolbar">
View
8 front-end/node/Debugger.js
@@ -154,7 +154,7 @@ WebInspector.nodeDebugger = (function() {
sendRequest('evaluate', {arguments: args}, callId);
},
takeHeapSnapshot: function() {
- sendRequest('profile', {arguments: {command: 'resume', modules: 65542, tag: 0}});
+ sendRequest('profile', {arguments: {command: 'resume', modules: 65542, tag: 1}});
},
startProfiling: function() {
sendRequest('profile', {arguments: {command: 'resume', modules: 1, tag: 0}});
@@ -162,6 +162,12 @@ WebInspector.nodeDebugger = (function() {
stopProfiling: function() {
sendRequest('profile', {arguments: {command: 'pause', modules: 1, tag: 0}});
},
+ enableProfiler: function() {
+ //sendRequest('enableprof');
+ },
+ getLogLines: function(pos, callId) {
+ sendRequest('getloglines', {position: pos}, callId);
+ },
liveEdit: function(callId, sourceID, newContext) {
var args = {
script_id: sourceID,
View
19 front-end/node/InspectorBackendStub.js
@@ -223,6 +223,7 @@ WebInspector.InspectorBackendStub.prototype = {
enableProfiler: function()
{
+ WebInspector.nodeDebugger.enableProfiler();
WebInspector.profilerWasEnabled();
},
@@ -243,17 +244,33 @@ WebInspector.InspectorBackendStub.prototype = {
getProfileHeaders: function(callId)
{
- WebInspector.didGetProfileHeaders(callId, []);
+ console.log(callId);
+ WebInspector.didGetProfileHeaders(callId, [{uid:1337, typeId:"cpu", title:"elite profile" }]);
},
getProfile: function(callId, uid)
{
+ console.log(uid);
},
takeHeapSnapshot: function()
{
WebInspector.nodeDebugger.takeHeapSnapshot();
},
+
+ getProfilerLogLines: function(callId, pos)
+ {
+ WebInspector.nodeDebugger.getLogLines(pos, callId);
+ },
+
+ profilerAgent: null,
+ getProfilerAgent: function()
+ {
+ if (this.profilerAgent == null) {
+ this.profilerAgent = new devtools.ProfilerAgent();
+ }
+ return this.profilerAgent;
+ },
databaseTableNames: function(database)
{
View
6 front-end/node/InspectorFrontendHostStub.js
@@ -60,6 +60,7 @@ WebInspector.InspectorFrontendHostStub = function()
injectedScriptId: value.ref || value.handle,
type: 'object'
};
+
break;
case 'function':
p.value = {
@@ -258,6 +259,9 @@ WebInspector.InspectorFrontendHostStub = function()
debugr.on('profile', function(msg) {
});
+ debugr.on('getloglines', function(msg) {
+ WebInspector.didGetProfilerLogLines(msg.callId, msg.body.position, msg.body.lines);
+ });
// events
debugr.on('break', function(msg) {
@@ -364,7 +368,7 @@ WebInspector.InspectorFrontendHostStub.prototype = {
hiddenPanels: function()
{
- return "elements,resources,timeline,profiles,storage,audits";
+ return "elements,resources,timeline,storage,audits";
},
inspectedURLChanged: function(url)
View
251 front-end/v8/DevTools.js
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2010 Google 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:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+ * OWNER OR 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.
+ */
+
+/**
+ * FIXME: change field naming style to use trailing underscore.
+ * @fileoverview Tools is a main class that wires all components of the
+ * DevTools frontend together. It is also responsible for overriding existing
+ * WebInspector functionality while it is getting upstreamed into WebCore.
+ */
+
+/**
+ * Dispatches raw message from the host.
+ * @param {string} remoteName
+ * @prama {string} methodName
+ * @param {string} param1, param2, param3 Arguments to dispatch.
+ */
+devtools$$dispatch = function(message)
+{
+ var args = typeof message === "string" ? JSON.parse(message) : message;
+ var methodName = args[0];
+ var parameters = args.slice(1);
+ WebInspector[methodName].apply(WebInspector, parameters);
+};
+
+
+devtools.ToolsAgent = function()
+{
+ this.profilerAgent_ = new devtools.ProfilerAgent();
+};
+
+
+/**
+ * @return {devtools.ProfilerAgent} Profiler agent instance.
+ */
+devtools.ToolsAgent.prototype.getProfilerAgent = function()
+{
+ return this.profilerAgent_;
+};
+
+
+
+/**
+ * Global instance of the tools agent.
+ * @type {devtools.ToolsAgent}
+ */
+devtools.tools = null;
+
+
+var context = {}; // Used by WebCore's inspector routines.
+
+(function() {
+ WebInspector._paramsObject = {};
+
+ var queryParams = window.location.search;
+ if (queryParams) {
+ var params = queryParams.substring(1).split("&");
+ for (var i = 0; i < params.length; ++i) {
+ var pair = params[i].split("=");
+ WebInspector._paramsObject[pair[0]] = pair[1];
+ }
+ }
+ if ("page" in WebInspector._paramsObject) {
+ WebInspector.socket = new WebSocket("ws://" + window.location.host + "/devtools/page/" + WebInspector._paramsObject.page);
+ WebInspector.socket.onmessage = function(message) { eval(message.data); }
+ WebInspector.socket.onerror = function(error) { console.err(error); }
+ WebInspector.socket.onopen = function() {
+ WebInspector.socketOpened = true;
+ if (WebInspector.loadedDone)
+ WebInspector.doLoadedDone();
+ };
+ }
+})();
+///////////////////////////////////////////////////////////////////////////////
+// Here and below are overrides to existing WebInspector methods only.
+// TODO(pfeldman): Patch WebCore and upstream changes.
+var oldLoaded = WebInspector.loaded;
+WebInspector.loaded = function()
+{
+ devtools.tools = new devtools.ToolsAgent();
+
+ Preferences.ignoreWhitespace = false;
+ Preferences.samplingCPUProfiler = true;
+ Preferences.heapProfilerPresent = true;
+ Preferences.debuggerAlwaysEnabled = true;
+ Preferences.profilerAlwaysEnabled = true;
+ Preferences.canEditScriptSource = true;
+
+ if ("page" in WebInspector._paramsObject) {
+ WebInspector.loadedDone = true;
+ if (WebInspector.socketOpened)
+ WebInspector.doLoadedDone();
+ return;
+ }
+ WebInspector.doLoadedDone();
+}
+
+WebInspector.doLoadedDone = function() {
+ oldLoaded.call(this);
+ InspectorFrontendHost.loaded();
+}
+
+devtools.domContentLoaded = function()
+{
+ WebInspector.setAttachedWindow(WebInspector._paramsObject.docked === "true");
+ if (WebInspector._paramsObject.toolbar_color && WebInspector._paramsObject.text_color)
+ WebInspector.setToolbarColors(WebInspector._paramsObject.toolbar_color, WebInspector._paramsObject.text_color);
+}
+document.addEventListener("DOMContentLoaded", devtools.domContentLoaded, false);
+
+
+(function InterceptProfilesPanelEvents()
+{
+ var oldShow = WebInspector.ProfilesPanel.prototype.show;
+ WebInspector.ProfilesPanel.prototype.show = function()
+ {
+ devtools.tools.getProfilerAgent().initializeProfiling();
+ this.enableToggleButton.visible = false;
+ oldShow.call(this);
+ // Show is called on every show event of a panel, so
+ // we only need to intercept it once.
+ WebInspector.ProfilesPanel.prototype.show = oldShow;
+ };
+})();
+
+
+/*
+ * @override
+ * TODO(mnaganov): Restore l10n when it will be agreed that it is needed.
+ */
+WebInspector.UIString = function(string)
+{
+ return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
+};
+
+
+/** Pending WebKit upstream by apavlov). Fixes iframe vs drag problem. */
+(function()
+{
+ var originalDragStart = WebInspector.elementDragStart;
+ WebInspector.elementDragStart = function(element)
+ {
+ if (element) {
+ var glassPane = document.createElement("div");
+ glassPane.style.cssText = "position:absolute;width:100%;height:100%;opacity:0;z-index:1";
+ glassPane.id = "glass-pane-for-drag";
+ element.parentElement.appendChild(glassPane);
+ }
+
+ originalDragStart.apply(this, arguments);
+ };
+
+ var originalDragEnd = WebInspector.elementDragEnd;
+ WebInspector.elementDragEnd = function()
+ {
+ originalDragEnd.apply(this, arguments);
+
+ var glassPane = document.getElementById("glass-pane-for-drag");
+ if (glassPane)
+ glassPane.parentElement.removeChild(glassPane);
+ };
+})();
+
+
+
+///////////////////////////////////////////
+// Chromium layout test harness support. //
+///////////////////////////////////////////
+
+WebInspector.runAfterPendingDispatchesQueue = [];
+
+WebInspector.TestController.prototype.runAfterPendingDispatches = function(callback)
+{
+ WebInspector.runAfterPendingDispatchesQueue.push(callback);
+};
+
+WebInspector.queuesAreEmpty = function()
+{
+ var copy = this.runAfterPendingDispatchesQueue.slice();
+ this.runAfterPendingDispatchesQueue = [];
+ for (var i = 0; i < copy.length; ++i)
+ copy[i].call(this);
+};
+
+
+/////////////////////////////
+// Chromium theme support. //
+/////////////////////////////
+
+WebInspector.setToolbarColors = function(backgroundColor, color)
+{
+ if (!WebInspector._themeStyleElement) {
+ WebInspector._themeStyleElement = document.createElement("style");
+ document.head.appendChild(WebInspector._themeStyleElement);
+ }
+ WebInspector._themeStyleElement.textContent =
+ "#toolbar {\
+ background-image: none !important;\
+ background-color: " + backgroundColor + " !important;\
+ }\
+ \
+ .toolbar-label {\
+ color: " + color + " !important;\
+ text-shadow: none;\
+ }";
+}
+
+WebInspector.resetToolbarColors = function()
+{
+ if (WebInspector._themeStyleElement)
+ WebInspector._themeStyleElement.textContent = "";
+
+}
+
+// TODO(yurys): should be removed when eclipse debugger stops using it.
+if (window.RemoteDebuggerAgent) {
+ RemoteDebuggerAgent.setContextId = function() {};
+}
+
+
+// Support for pause while renderer is busy (is dispatched on IO thread).
+InspectorBackend.pause = function()
+{
+ RemoteDebuggerCommandExecutor.DebuggerPauseScript();
+};
View
38 front-end/v8/DevToolsHostStub.js
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 Google 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:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+ * OWNER OR 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.
+ */
+
+/**
+ * @fileoverview These stubs emulate backend functionality and allows
+ * DevTools frontend to function as a standalone web app.
+ */
+
+if (!window["RemoteDebuggerCommandExecutor"]) {
+ window["RemoteDebuggerCommandExecutor"] = {};
+}
View
966 front-end/v8/HeapProfilerPanel.js
@@ -0,0 +1,966 @@
+/*
+ * Copyright (C) 2010 Google 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:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+ * OWNER OR 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.
+ */
+
+/**
+ * @fileoverview Heap profiler panel implementation.
+ */
+
+WebInspector.ProfilesPanel.prototype.addSnapshot = function(snapshot) {
+ snapshot.title = WebInspector.UIString("Snapshot %d", snapshot.number);
+ snapshot.typeId = WebInspector.HeapSnapshotProfileType.TypeId;
+
+ var snapshots = WebInspector.HeapSnapshotProfileType.snapshots;
+ snapshots.push(snapshot);
+
+ snapshot.listIndex = snapshots.length - 1;
+
+ if (WebInspector.CPUProfile)
+ this.addProfileHeader(WebInspector.HeapSnapshotProfileType.TypeId, snapshot);
+ else
+ this.addProfileHeader(snapshot);
+
+ this.dispatchEventToListeners("snapshot added");
+}
+
+
+WebInspector.HeapSnapshotView = function(parent, profile)
+{
+ WebInspector.View.call(this);
+
+ this.element.addStyleClass("heap-snapshot-view");
+
+ this.parent = parent;
+ this.parent.addEventListener("snapshot added", this._updateBaseOptions, this);
+
+ this.showCountAsPercent = false;
+ this.showSizeAsPercent = false;
+ this.showCountDeltaAsPercent = false;
+ this.showSizeDeltaAsPercent = false;
+
+ this.categories = {
+ code: new WebInspector.ResourceCategory("code", WebInspector.UIString("Code"), "rgb(255,121,0)"),
+ data: new WebInspector.ResourceCategory("data", WebInspector.UIString("Objects"), "rgb(47,102,236)")
+ };
+
+ var summaryContainer = document.createElement("div");
+ summaryContainer.id = "heap-snapshot-summary-container";
+
+ this.countsSummaryBar = new WebInspector.SummaryBar(this.categories);
+ this.countsSummaryBar.element.className = "heap-snapshot-summary";
+ this.countsSummaryBar.calculator = new WebInspector.HeapSummaryCountCalculator();
+ var countsLabel = document.createElement("div");
+ countsLabel.className = "heap-snapshot-summary-label";
+ countsLabel.textContent = WebInspector.UIString("Count");
+ this.countsSummaryBar.element.appendChild(countsLabel);
+ summaryContainer.appendChild(this.countsSummaryBar.element);
+
+ this.sizesSummaryBar = new WebInspector.SummaryBar(this.categories);
+ this.sizesSummaryBar.element.className = "heap-snapshot-summary";
+ this.sizesSummaryBar.calculator = new WebInspector.HeapSummarySizeCalculator();
+ var sizesLabel = document.createElement("label");
+ sizesLabel.className = "heap-snapshot-summary-label";
+ sizesLabel.textContent = WebInspector.UIString("Size");
+ this.sizesSummaryBar.element.appendChild(sizesLabel);
+ summaryContainer.appendChild(this.sizesSummaryBar.element);
+
+ this.element.appendChild(summaryContainer);
+
+ var columns = { "cons": { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
+ "count": { title: WebInspector.UIString("Count"), width: "54px", sortable: true },
+ "size": { title: WebInspector.UIString("Size"), width: "72px", sort: "descending", sortable: true },
+ "countDelta": { title: WebInspector.UIString("\xb1 Count"), width: "72px", sortable: true },
+ "sizeDelta": { title: WebInspector.UIString("\xb1 Size"), width: "72px", 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);
+
+ this.profile = profile;
+
+ this.baseSelectElement = document.createElement("select");
+ this.baseSelectElement.className = "status-bar-item";
+ this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
+ this._updateBaseOptions();
+ if (this.profile.listIndex > 0)
+ this.baseSelectElement.selectedIndex = this.profile.listIndex - 1;
+ else
+ this.baseSelectElement.selectedIndex = this.profile.listIndex;
+ this._resetDataGridList();
+
+ this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
+ this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
+
+ this.refresh();
+
+ this._updatePercentButton();
+};
+
+WebInspector.HeapSnapshotView.prototype = {
+
+ get statusBarItems()
+ {
+ return [this.baseSelectElement, this.percentButton.element];
+ },
+
+ get profile()
+ {
+ return this._profile;
+ },
+
+ set profile(profile)
+ {
+ this._profile = profile;
+ },
+
+ show: function(parentElement)
+ {
+ WebInspector.View.prototype.show.call(this, parentElement);
+ this.dataGrid.updateWidths();
+ },
+
+ hide: function()
+ {
+ WebInspector.View.prototype.hide.call(this);
+ this._currentSearchResultIndex = -1;
+ },
+
+ resize: function()
+ {
+ if (this.dataGrid)
+ this.dataGrid.updateWidths();
+ },
+
+ refresh: function()
+ {
+ this.dataGrid.removeChildren();
+
+ var children = this.snapshotDataGridList.children;
+ var count = children.length;
+ for (var index = 0; index < count; ++index)
+ this.dataGrid.appendChild(children[index]);
+
+ this._updateSummaryGraph();
+ },
+
+ refreshShowAsPercents: function()
+ {
+ this._updatePercentButton();
+ this.refreshVisibleData();
+ },
+
+ _deleteSearchMatchedFlags: function(node)
+ {
+ delete node._searchMatchedConsColumn;
+ delete node._searchMatchedCountColumn;
+ delete node._searchMatchedSizeColumn;
+ delete node._searchMatchedCountDeltaColumn;
+ delete node._searchMatchedSizeDeltaColumn;
+ },
+
+ searchCanceled: function()
+ {
+ if (this._searchResults) {
+ for (var i = 0; i < this._searchResults.length; ++i) {
+ var profileNode = this._searchResults[i].profileNode;
+ this._deleteSearchMatchedFlags(profileNode);
+ profileNode.refresh();
+ }
+ }
+
+ delete this._searchFinishedCallback;
+ this._currentSearchResultIndex = -1;
+ this._searchResults = [];
+ },
+
+ performSearch: function(query, finishedCallback)
+ {
+ // Call searchCanceled since it will reset everything we need before doing a new search.
+ this.searchCanceled();
+
+ query = query.trim();
+
+ if (!query.length)
+ return;
+
+ this._searchFinishedCallback = finishedCallback;
+
+ var helper = WebInspector.HeapSnapshotView.SearchHelper;
+
+ var operationAndNumber = helper.parseOperationAndNumber(query);
+ var operation = operationAndNumber[0];
+ var queryNumber = operationAndNumber[1];
+
+ var percentUnits = helper.percents.test(query);
+ var megaBytesUnits = helper.megaBytes.test(query);
+ var kiloBytesUnits = helper.kiloBytes.test(query);
+ var bytesUnits = helper.bytes.test(query);
+
+ var queryNumberBytes = (megaBytesUnits ? (queryNumber * 1024 * 1024) : (kiloBytesUnits ? (queryNumber * 1024) : queryNumber));
+
+ function matchesQuery(heapSnapshotDataGridNode)
+ {
+ WebInspector.HeapSnapshotView.prototype._deleteSearchMatchedFlags(heapSnapshotDataGridNode);
+
+ if (percentUnits) {
+ heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.countPercent, queryNumber);
+ heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.sizePercent, queryNumber);
+ heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDeltaPercent, queryNumber);
+ heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDeltaPercent, queryNumber);
+ } else if (megaBytesUnits || kiloBytesUnits || bytesUnits) {
+ heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.size, queryNumberBytes);
+ heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDelta, queryNumberBytes);
+ } else {
+ heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.count, queryNumber);
+ heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDelta, queryNumber);
+ }
+
+ if (heapSnapshotDataGridNode.constructorName.hasSubstring(query, true))
+ heapSnapshotDataGridNode._searchMatchedConsColumn = true;
+
+ if (heapSnapshotDataGridNode._searchMatchedConsColumn ||
+ heapSnapshotDataGridNode._searchMatchedCountColumn ||
+ heapSnapshotDataGridNode._searchMatchedSizeColumn ||
+ heapSnapshotDataGridNode._searchMatchedCountDeltaColumn ||
+ heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn) {
+ heapSnapshotDataGridNode.refresh();
+ return true;
+ }
+
+ return false;
+ }
+
+ var current = this.snapshotDataGridList.children[0];
+ var depth = 0;
+ var info = {};
+
+ // The second and subsequent levels of heap snapshot nodes represent retainers,
+ // so recursive expansion will be infinite, since a graph is being traversed.
+ // So default to a recursion cap of 2 levels.
+ var maxDepth = 2;
+
+ while (current) {
+ if (matchesQuery(current))
+ this._searchResults.push({ profileNode: current });
+ current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
+ depth += info.depthChange;
+ }
+
+ finishedCallback(this, this._searchResults.length);
+ },
+
+ jumpToFirstSearchResult: WebInspector.CPUProfileView.prototype.jumpToFirstSearchResult,
+ jumpToLastSearchResult: WebInspector.CPUProfileView.prototype.jumpToLastSearchResult,
+ jumpToNextSearchResult: WebInspector.CPUProfileView.prototype.jumpToNextSearchResult,
+ jumpToPreviousSearchResult: WebInspector.CPUProfileView.prototype.jumpToPreviousSearchResult,
+ showingFirstSearchResult: WebInspector.CPUProfileView.prototype.showingFirstSearchResult,
+ showingLastSearchResult: WebInspector.CPUProfileView.prototype.showingLastSearchResult,
+ _jumpToSearchResult: WebInspector.CPUProfileView.prototype._jumpToSearchResult,
+
+ refreshVisibleData: function()
+ {
+ var child = this.dataGrid.children[0];
+ while (child) {
+ child.refresh();
+ child = child.traverseNextNode(false, null, true);
+ }
+ this._updateSummaryGraph();
+ },
+
+ _changeBase: function() {
+ if (this.baseSnapshot === WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex])
+ return;
+
+ this._resetDataGridList();
+ this.refresh();
+
+ if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
+ return;
+
+ // The current search needs to be performed again. First negate out previous match
+ // count by calling the search finished callback with a negative number of matches.
+ // Then perform the search again with the same query and callback.
+ this._searchFinishedCallback(this, -this._searchResults.length);
+ this.performSearch(this.currentQuery, this._searchFinishedCallback);
+ },
+
+ _createSnapshotDataGridList: function()
+ {
+ if (this._snapshotDataGridList)
+ delete this._snapshotDataGridList;
+
+ this._snapshotDataGridList = new WebInspector.HeapSnapshotDataGridList(this, this.baseSnapshot.entries, this.profile.entries);
+ return this._snapshotDataGridList;
+ },
+
+ _mouseDownInDataGrid: function(event)
+ {
+ if (event.detail < 2)
+ return;
+
+ var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
+ if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("size-column") && !cell.hasStyleClass("countDelta-column") && !cell.hasStyleClass("sizeDelta-column")))
+ return;
+
+ if (cell.hasStyleClass("count-column"))
+ this.showCountAsPercent = !this.showCountAsPercent;
+ else if (cell.hasStyleClass("size-column"))
+ this.showSizeAsPercent = !this.showSizeAsPercent;
+ else if (cell.hasStyleClass("countDelta-column"))
+ this.showCountDeltaAsPercent = !this.showCountDeltaAsPercent;
+ else if (cell.hasStyleClass("sizeDelta-column"))
+ this.showSizeDeltaAsPercent = !this.showSizeDeltaAsPercent;
+
+ this.refreshShowAsPercents();
+
+ event.preventDefault();
+ event.stopPropagation();
+ },
+
+ get _isShowingAsPercent()
+ {
+ return this.showCountAsPercent && this.showSizeAsPercent && this.showCountDeltaAsPercent && this.showSizeDeltaAsPercent;
+ },
+
+ _percentClicked: function(event)
+ {
+ var currentState = this._isShowingAsPercent;
+ this.showCountAsPercent = !currentState;
+ this.showSizeAsPercent = !currentState;
+ this.showCountDeltaAsPercent = !currentState;
+ this.showSizeDeltaAsPercent = !currentState;
+ this.refreshShowAsPercents();
+ },
+
+ _resetDataGridList: function()
+ {
+ this.baseSnapshot = WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex];
+ var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("size", false);
+ if (this.snapshotDataGridList)
+ lastComparator = this.snapshotDataGridList.lastComparator;
+ this.snapshotDataGridList = this._createSnapshotDataGridList();
+ this.snapshotDataGridList.sort(lastComparator, true);
+ },
+
+ _sortData: function()
+ {
+ var sortAscending = this.dataGrid.sortOrder === "ascending";
+ var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
+ var sortProperty = {
+ "cons": ["constructorName", null],
+ "count": ["count", null],
+ "size": ["size", "count"],
+ "countDelta": this.showCountDeltaAsPercent ? ["countDeltaPercent", null] : ["countDelta", null],
+ "sizeDelta": this.showSizeDeltaAsPercent ? ["sizeDeltaPercent", "countDeltaPercent"] : ["sizeDelta", "sizeDeltaPercent"]
+ }[sortColumnIdentifier];
+
+ this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty[0], sortProperty[1], sortAscending));
+
+ this.refresh();
+ },
+
+ _updateBaseOptions: function()
+ {
+ var list = WebInspector.HeapSnapshotProfileType.snapshots;
+ // We're assuming that snapshots can only be added.
+ if (this.baseSelectElement.length === list.length)
+ return;
+
+ for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
+ var baseOption = document.createElement("option");
+ baseOption.label = WebInspector.UIString("Compared to %s", list[i].title);
+ this.baseSelectElement.appendChild(baseOption);
+ }
+ },
+
+ _updatePercentButton: function()
+ {
+ if (this._isShowingAsPercent) {
+ this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
+ this.percentButton.toggled = true;
+ } else {
+ this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
+ this.percentButton.toggled = false;
+ }
+ },
+
+ _updateSummaryGraph: function()
+ {
+ this.countsSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
+ this.countsSummaryBar.update(this.profile.lowlevels);
+
+ this.sizesSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
+ this.sizesSummaryBar.update(this.profile.lowlevels);
+ }
+};
+
+WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype;
+
+WebInspector.HeapSnapshotView.SearchHelper = {
+ // In comparators, we assume that a value from a node is passed as the first parameter.
+ operations: { LESS: function (a, b) { return a !== null && a < b; },
+ LESS_OR_EQUAL: function (a, b) { return a !== null && a <= b; },
+ EQUAL: function (a, b) { return a !== null && a === b; },
+ GREATER_OR_EQUAL: function (a, b) { return a !== null && a >= b; },
+ GREATER: function (a, b) { return a !== null && a > b; } },
+
+ operationParsers: { LESS: /^<(\d+)/,
+ LESS_OR_EQUAL: /^<=(\d+)/,
+ GREATER_OR_EQUAL: /^>=(\d+)/,
+ GREATER: /^>(\d+)/ },
+
+ parseOperationAndNumber: function(query)
+ {
+ var operations = WebInspector.HeapSnapshotView.SearchHelper.operations;
+ var parsers = WebInspector.HeapSnapshotView.SearchHelper.operationParsers;
+ for (var operation in parsers) {
+ var match = query.match(parsers[operation]);
+ if (match !== null)
+ return [operations[operation], parseFloat(match[1])];
+ }
+ return [operations.EQUAL, parseFloat(query)];
+ },
+
+ percents: /%$/,
+
+ megaBytes: /MB$/i,
+
+ kiloBytes: /KB$/i,
+
+ bytes: /B$/i
+}
+
+WebInspector.HeapSummaryCalculator = function(lowLevelField)
+{
+ this.total = 1;
+ this.lowLevelField = lowLevelField;
+}
+
+WebInspector.HeapSummaryCalculator.prototype = {
+ computeSummaryValues: function(lowLevels)
+ {
+ var highLevels = {data: 0, code: 0};
+ this.total = 0;
+ for (var item in lowLevels) {
+ var highItem = this._highFromLow(item);
+ if (highItem) {
+ var value = lowLevels[item][this.lowLevelField];
+ highLevels[highItem] += value;
+ this.total += value;
+ }
+ }
+ var result = {categoryValues: highLevels};
+ if (!this.showAsPercent)
+ result.total = this.total;
+ return result;
+ },
+
+ formatValue: function(value)
+ {
+ if (this.showAsPercent)
+ return WebInspector.UIString("%.2f%%", value / this.total * 100.0);
+ else
+ return this._valueToString(value);
+ },
+
+ get showAsPercent()
+ {
+ return this._showAsPercent;
+ },
+
+ set showAsPercent(x)
+ {
+ this._showAsPercent = x;
+ }
+}
+
+WebInspector.HeapSummaryCountCalculator = function()
+{
+ WebInspector.HeapSummaryCalculator.call(this, "count");
+}
+
+WebInspector.HeapSummaryCountCalculator.prototype = {
+ _highFromLow: function(type) {
+ if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE") return "code";
+ if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/)) return "data";
+ return null;
+ },
+
+ _valueToString: function(value) {
+ return value.toString();
+ }
+}
+
+WebInspector.HeapSummaryCountCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
+
+WebInspector.HeapSummarySizeCalculator = function()
+{
+ WebInspector.HeapSummaryCalculator.call(this, "size");
+}
+
+WebInspector.HeapSummarySizeCalculator.prototype = {
+ _highFromLow: function(type) {
+ if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE") return "code";
+ if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/) || type.match(/_ARRAY_TYPE$/)) return "data";
+ return null;
+ },
+
+ _valueToString: Number.bytesToString
+}
+
+WebInspector.HeapSummarySizeCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
+
+WebInspector.HeapSnapshotSidebarTreeElement = function(snapshot)
+{
+ this.profile = snapshot;
+
+ WebInspector.SidebarTreeElement.call(this, "heap-snapshot-sidebar-tree-item", "", "", snapshot, false);
+
+ this.refreshTitles();
+};
+
+WebInspector.HeapSnapshotSidebarTreeElement.prototype = {
+ get mainTitle()
+ {
+ if (this._mainTitle)
+ return this._mainTitle;
+ return this.profile.title;
+ },
+
+ set mainTitle(x)
+ {
+ this._mainTitle = x;
+ this.refreshTitles();
+ }
+};
+
+WebInspector.HeapSnapshotSidebarTreeElement.prototype.__proto__ = WebInspector.ProfileSidebarTreeElement.prototype;
+
+WebInspector.HeapSnapshotDataGridNodeWithRetainers = function(owningTree)
+{
+ this.tree = owningTree;
+
+ WebInspector.DataGridNode.call(this, null, this._hasRetainers);
+
+ this.addEventListener("populate", this._populate, this);
+};
+
+WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype = {
+ isEmptySet: function(set)
+ {
+ for (var x in set)
+ return false;
+ return true;
+ },
+
+ get _hasRetainers()
+ {
+ return !this.isEmptySet(this.retainers);
+ },
+
+ get _parent()
+ {
+ // For top-level nodes, return owning tree as a parent, not data grid.
+ return this.parent !== this.dataGrid ? this.parent : this.tree;
+ },
+
+ _populate: function(event)
+ {
+ var self = this;
+ this.produceDiff(this.baseRetainers, this.retainers, function(baseItem, snapshotItem) {
+ self.appendChild(new WebInspector.HeapSnapshotDataGridRetainerNode(self.snapshotView, baseItem, snapshotItem, self.tree));
+ });
+
+ if (this._parent) {
+ var currentComparator = this._parent.lastComparator;
+ if (currentComparator)
+ this.sort(currentComparator, true);
+ }
+
+ this.removeEventListener("populate", this._populate, this);
+ },
+
+ produceDiff: function(baseEntries, currentEntries, callback)
+ {
+ for (var item in currentEntries)
+ callback(baseEntries[item], currentEntries[item]);
+
+ for (item in baseEntries) {
+ if (!(item in currentEntries))
+ callback(baseEntries[item], null);
+ }
+ },
+
+ sort: function(comparator, force) {
+ if (!force && this.lastComparator === comparator)
+ return;
+
+ this.children.sort(comparator);
+ var childCount = this.children.length;
+ for (var childIndex = 0; childIndex < childCount; ++childIndex)
+ this.children[childIndex]._recalculateSiblings(childIndex);
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this.children[i];
+ if (!force && (!child.expanded || child.lastComparator === comparator))
+ continue;
+ child.sort(comparator, force);
+ }
+ this.lastComparator = comparator;
+ },
+
+ signForDelta: function(delta) {
+ if (delta === 0)
+ return "";
+ if (delta > 0)
+ return "+";
+ else
+ // Math minus sign, same width as plus.
+ return "\u2212";
+ },
+
+ showDeltaAsPercent: function(value) {
+ if (value === Number.POSITIVE_INFINITY)
+ return WebInspector.UIString("new");
+ else if (value === Number.NEGATIVE_INFINITY)
+ return WebInspector.UIString("deleted");
+ if (value > 1000.0)
+ return WebInspector.UIString("%s >1000%%", this.signForDelta(value));
+ return WebInspector.UIString("%s%.2f%%", this.signForDelta(value), Math.abs(value));
+ },
+
+ getTotalCount: function() {
+ if (!this._count) {
+ this._count = 0;
+ for (var i = 0, n = this.children.length; i < n; ++i)
+ this._count += this.children[i].count;
+ }
+ return this._count;
+ },
+
+ getTotalSize: function() {
+ if (!this._size) {
+ this._size = 0;
+ for (var i = 0, n = this.children.length; i < n; ++i)
+ this._size += this.children[i].size;
+ }
+ return this._size;
+ },
+
+ get countPercent()
+ {
+ return this.count / this._parent.getTotalCount() * 100.0;
+ },
+
+ get sizePercent()
+ {
+ return this.size / this._parent.getTotalSize() * 100.0;
+ },
+
+ get countDeltaPercent()
+ {
+ if (this.baseCount > 0) {
+ if (this.count > 0)
+ return this.countDelta / this.baseCount * 100.0;
+ else
+ return Number.NEGATIVE_INFINITY;
+ } else
+ return Number.POSITIVE_INFINITY;
+ },
+
+ get sizeDeltaPercent()
+ {
+ if (this.baseSize > 0) {
+ if (this.size > 0)
+ return this.sizeDelta / this.baseSize * 100.0;
+ else
+ return Number.NEGATIVE_INFINITY;
+ } else
+ return Number.POSITIVE_INFINITY;
+ },
+
+ get data()
+ {
+ var data = {};
+
+ data["cons"] = this.constructorName;
+
+ if (this.snapshotView.showCountAsPercent)
+ data["count"] = WebInspector.UIString("%.2f%%", this.countPercent);
+ else
+ data["count"] = this.count;
+
+ if (this.size !== null) {
+ if (this.snapshotView.showSizeAsPercent)
+ data["size"] = WebInspector.UIString("%.2f%%", this.sizePercent);
+ else
+ data["size"] = Number.bytesToString(this.size);
+ } else
+ data["size"] = "";
+
+ if (this.snapshotView.showCountDeltaAsPercent)
+ data["countDelta"] = this.showDeltaAsPercent(this.countDeltaPercent);
+ else
+ data["countDelta"] = WebInspector.UIString("%s%d", this.signForDelta(this.countDelta), Math.abs(this.countDelta));
+
+ if (this.sizeDelta !== null) {
+ if (this.snapshotView.showSizeDeltaAsPercent)
+ data["sizeDelta"] = this.showDeltaAsPercent(this.sizeDeltaPercent);
+ else
+ data["sizeDelta"] = WebInspector.UIString("%s%s", this.signForDelta(this.sizeDelta), Number.bytesToString(Math.abs(this.sizeDelta)));
+ } else
+ data["sizeDelta"] = "";
+
+ return data;
+ },
+
+ createCell: function(columnIdentifier)
+ {
+ var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
+
+ if ((columnIdentifier === "cons" && this._searchMatchedConsColumn) ||
+ (columnIdentifier === "count" && this._searchMatchedCountColumn) ||
+ (columnIdentifier === "size" && this._searchMatchedSizeColumn) ||
+ (columnIdentifier === "countDelta" && this._searchMatchedCountDeltaColumn) ||
+ (columnIdentifier === "sizeDelta" && this._searchMatchedSizeDeltaColumn))
+ cell.addStyleClass("highlight");
+
+ return cell;
+ }
+};
+
+WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.__proto__ = WebInspector.DataGridNode.prototype;
+
+WebInspector.HeapSnapshotDataGridNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
+{
+ this.snapshotView = snapshotView;
+
+ if (!snapshotEntry)
+ snapshotEntry = { cons: baseEntry.cons, count: 0, size: 0, retainers: {} };
+ this.constructorName = snapshotEntry.cons;
+ this.count = snapshotEntry.count;
+ this.size = snapshotEntry.size;
+ this.retainers = snapshotEntry.retainers;
+
+ if (!baseEntry)
+ baseEntry = { count: 0, size: 0, retainers: {} };
+ this.baseCount = baseEntry.count;
+ this.countDelta = this.count - this.baseCount;
+ this.baseSize = baseEntry.size;
+ this.sizeDelta = this.size - this.baseSize;
+ this.baseRetainers = baseEntry.retainers;
+
+ WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
+};
+
+WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
+
+WebInspector.HeapSnapshotDataGridList = function(snapshotView, baseEntries, snapshotEntries)
+{
+ this.tree = this;
+ this.snapshotView = snapshotView;
+ this.children = [];
+ this.lastComparator = null;
+ this.populateChildren(baseEntries, snapshotEntries);
+};
+
+WebInspector.HeapSnapshotDataGridList.prototype = {
+ appendChild: function(child)
+ {
+ this.insertChild(child, this.children.length);
+ },
+
+ insertChild: function(child, index)
+ {
+ this.children.splice(index, 0, child);
+ },
+
+ removeChildren: function()
+ {
+ this.children = [];
+ },
+
+ populateChildren: function(baseEntries, snapshotEntries)
+ {
+ var self = this;
+ this.produceDiff(baseEntries, snapshotEntries, function(baseItem, snapshotItem) {
+ self.appendChild(new WebInspector.HeapSnapshotDataGridNode(self.snapshotView, baseItem, snapshotItem, self));
+ });
+ },
+
+ produceDiff: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.produceDiff,
+ sort: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.sort,
+ getTotalCount: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalCount,
+ getTotalSize: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalSize
+};
+
+WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}];
+
+WebInspector.HeapSnapshotDataGridList.propertyComparator = function(property, property2, isAscending)
+{
+ var propertyHash = property + "#" + property2;
+ var comparator = this.propertyComparators[(isAscending ? 1 : 0)][propertyHash];
+ if (!comparator) {
+ comparator = function(lhs, rhs) {
+ var l = lhs[property], r = rhs[property];
+ if ((l === null || r === null) && property2 !== null)
+ l = lhs[property2], r = rhs[property2];
+ var result = l < r ? -1 : (l > r ? 1 : 0);
+ return isAscending ? result : -result;
+ };
+ this.propertyComparators[(isAscending ? 1 : 0)][propertyHash] = comparator;
+ }
+ return comparator;
+};
+
+WebInspector.HeapSnapshotDataGridRetainerNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
+{
+ this.snapshotView = snapshotView;
+
+ if (!snapshotEntry)
+ snapshotEntry = { cons: baseEntry.cons, count: 0, clusters: {} };
+ this.constructorName = snapshotEntry.cons;
+ this.count = snapshotEntry.count;
+ this.retainers = this._calculateRetainers(this.snapshotView.profile, snapshotEntry.clusters);
+
+ if (!baseEntry)
+ baseEntry = { count: 0, clusters: {} };
+ this.baseCount = baseEntry.count;
+ this.countDelta = this.count - this.baseCount;
+ this.baseRetainers = this._calculateRetainers(this.snapshotView.baseSnapshot, baseEntry.clusters);
+
+ this.size = null;
+ this.sizeDelta = null;
+
+ WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
+}
+
+WebInspector.HeapSnapshotDataGridRetainerNode.prototype = {
+ get sizePercent()
+ {
+ return null;
+ },
+
+ get sizeDeltaPercent()
+ {
+ return null;
+ },
+
+ _calculateRetainers: function(snapshot, clusters) {
+ var retainers = {};
+ if (this.isEmptySet(clusters)) {
+ if (this.constructorName in snapshot.entries)
+ return snapshot.entries[this.constructorName].retainers;
+ } else {
+ // In case when an entry is retained by clusters, we need to gather up the list
+ // of retainers by merging retainers of every cluster.
+ // E.g. having such a tree:
+ // A
+ // Object:1 10
+ // X 3
+ // Y 4
+ // Object:2 5
+ // X 6
+ //
+ // will result in a following retainers list: X 9, Y 4.
+ for (var clusterName in clusters) {
+ if (clusterName in snapshot.clusters) {
+ var clusterRetainers = snapshot.clusters[clusterName].retainers;
+ for (var clusterRetainer in clusterRetainers) {
+ var clusterRetainerEntry = clusterRetainers[clusterRetainer];
+ if (!(clusterRetainer in retainers))
+ retainers[clusterRetainer] = { cons: clusterRetainerEntry.cons, count: 0, clusters: {} };
+ retainers[clusterRetainer].count += clusterRetainerEntry.count;
+ for (var clusterRetainerCluster in clusterRetainerEntry.clusters)
+ retainers[clusterRetainer].clusters[clusterRetainerCluster] = true;
+ }
+ }
+ }
+ }
+ return retainers;
+ }
+};
+
+WebInspector.HeapSnapshotDataGridRetainerNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
+
+
+WebInspector.HeapSnapshotProfileType = function()
+{
+ WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS"));
+}
+
+WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
+
+WebInspector.HeapSnapshotProfileType.snapshots = [];
+
+WebInspector.HeapSnapshotProfileType.prototype = {
+ get buttonTooltip()
+ {
+ return WebInspector.UIString("Take heap snapshot.");
+ },
+
+ get buttonStyle()
+ {
+ return "heap-snapshot-status-bar-item status-bar-item";
+ },
+
+ buttonClicked: function()
+ {
+ InspectorBackend.getProfilerAgent().startProfiling(devtools.ProfilerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT);
+ },
+
+ get welcomeMessage()
+ {
+ return WebInspector.UIString("Get a heap snapshot by pressing the %s button on the status bar.");
+ },
+
+ createSidebarTreeElementForProfile: function(profile)
+ {
+ var element = new WebInspector.HeapSnapshotSidebarTreeElement(profile);
+ element.small = false;
+ return element;
+ },
+
+ createView: function(profile)
+ {
+ return new WebInspector.HeapSnapshotView(WebInspector.panels.profiles, profile);
+ }
+}
+
+WebInspector.HeapSnapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;
+
+
+(function() {
+ var originalCreatePanels = WebInspector._createPanels;
+ WebInspector._createPanels = function() {
+ originalCreatePanels.apply(this, arguments);
+ if (WebInspector.panels.profiles)
+ WebInspector.panels.profiles.registerProfileType(new WebInspector.HeapSnapshotProfileType());
+ }
+})();
View
138 front-end/v8/ProfilerAgent.js
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2010 Google 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:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+ * OWNER OR 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.
+ */
+
+/**
+ * @fileoverview Provides communication interface to remote v8 profiler.
+ */
+
+/**
+ * @constructor
+ */
+devtools.ProfilerAgent = function()
+{
+
+ /**
+ * Profiler log position.
+ * @type {number}
+ */
+ this._logPosition = 0;
+
+ /**
+ * Last requested log position.
+ * @type {number}
+ */
+ this._lastRequestedLogPosition = -1;
+
+ /**
+ * Profiler processor instance.
+ * @type {devtools.profiler.Processor}
+ */
+ this._profilerProcessor = new devtools.profiler.Processor();
+};
+
+
+/**
+ * A copy of enum from include/v8.h
+ * @enum {number}
+ */
+devtools.ProfilerAgent.ProfilerModules = {
+ PROFILER_MODULE_NONE: 0,
+ PROFILER_MODULE_CPU: 1,
+ PROFILER_MODULE_HEAP_STATS: 1 << 1,
+ PROFILER_MODULE_JS_CONSTRUCTORS: 1 << 2,
+ PROFILER_MODULE_HEAP_SNAPSHOT: 1 << 16
+};
+
+
+/**
+ * Initializes profiling state.
+ */
+devtools.ProfilerAgent.prototype.initializeProfiling = function()
+{
+ this._getNextLogLines(false);
+};
+
+
+/**
+ * Requests the next chunk of log lines.
+ * @param {boolean} immediately Do not postpone the request.
+ * @private
+ */
+devtools.ProfilerAgent.prototype._getNextLogLines = function(immediately)
+{
+ if (this._lastRequestedLogPosition == this._logPosition)
+ return;
+ var pos = this._lastRequestedLogPosition = this._logPosition;
+
+ var callId = WebInspector.Callback.wrap(this._didGetProfilerLogLines.bind(this));
+ if (immediately)
+ InspectorBackend.getProfilerLogLines(callId, pos);
+ else {
+ function delayedRequest()
+ {
+ InspectorBackend.getProfilerLogLines(callId, pos);
+ }
+ setTimeout(delayedRequest, 500);
+ }
+};
+
+
+/**
+ * Starts profiling.
+ * @param {number} modules List of modules to enable.
+ */
+devtools.ProfilerAgent.prototype.startProfiling = function(modules)
+{
+ if (modules & devtools.ProfilerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT) {
+ InspectorBackend.takeHeapSnapshot();
+ // Active modules will not change, instead, a snapshot will be logged.
+ this._getNextLogLines();
+ }
+};
+
+
+/**
+ * Handles a portion of a profiler log retrieved by getLogLines call.
+ * @param {number} pos Current position in log.
+ * @param {string} log A portion of profiler log.
+ */
+devtools.ProfilerAgent.prototype._didGetProfilerLogLines = function(pos, log)
+{
+ this._logPosition = pos;
+ if (log.length > 0) {
+ this._profilerProcessor.processLogChunk(log);
+ this._getNextLogLines();
+ } else {
+ // Allow re-reading from the last position.
+ this._lastRequestedLogPosition = this._logPosition - 1;
+ }
+};
+
+WebInspector.didGetProfilerLogLines = WebInspector.Callback.processCallback;
View
543 front-end/v8/ProfilerProcessor.js
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2010 Google 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:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
+ * OWNER OR 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.
+ */
+
+/**
+ * @fileoverview Profiler processor is used to process log file produced
+ * by V8 and produce an internal profile representation which is used
+ * for building profile views in "Profiles" tab.
+ */
+
+
+/**
+ * Creates a Profile View builder object compatible with WebKit Profiler UI.
+ *
+ * @param {number} samplingRate Number of ms between profiler ticks.
+ * @constructor
+ */
+devtools.profiler.WebKitViewBuilder = function(samplingRate)
+{
+ devtools.profiler.ViewBuilder.call(this, samplingRate);
+};
+devtools.profiler.WebKitViewBuilder.prototype.__proto__ = devtools.profiler.ViewBuilder.prototype;
+
+
+/**
+ * @override
+ */
+devtools.profiler.WebKitViewBuilder.prototype.createViewNode = function(funcName, totalTime, selfTime, head)
+{
+ return new devtools.profiler.WebKitViewNode(funcName, totalTime, selfTime, head);
+};
+
+
+/**
+ * Constructs a Profile View node object for displaying in WebKit Profiler UI.
+ *
+ * @param {string} internalFuncName A fully qualified function name.
+ * @param {number} totalTime Amount of time that application spent in the
+ * corresponding function and its descendants (not that depending on
+ * profile they can be either callees or callers.)
+ * @param {number} selfTime Amount of time that application spent in the
+ * corresponding function only.
+ * @param {devtools.profiler.ProfileView.Node} head Profile view head.
+ * @constructor
+ */
+devtools.profiler.WebKitViewNode = function(internalFuncName, totalTime, selfTime, head)
+{
+ devtools.profiler.ProfileView.Node.call(this, internalFuncName, totalTime, selfTime, head);
+ this.initFuncInfo_();
+ this.callUID = internalFuncName;
+};
+devtools.profiler.WebKitViewNode.prototype.__proto__ = devtools.profiler.ProfileView.Node.prototype;
+
+
+/**
+ * RegEx for stripping V8's prefixes of compiled functions.
+ */
+devtools.profiler.WebKitViewNode.FUNC_NAME_STRIP_RE = /^(?:LazyCompile|Function|Callback): (.*)$/;
+
+
+/**
+ * RegEx for extracting script source URL and line number.
+ */
+devtools.profiler.WebKitViewNode.FUNC_NAME_PARSE_RE = /^((?:get | set )?[^ ]+) (.*):(\d+)( \{\d+\})?$/;
+
+
+/**
+ * Inits "functionName", "url", and "lineNumber" fields using "internalFuncName"
+ * field.
+ * @private
+ */
+devtools.profiler.WebKitViewNode.prototype.initFuncInfo_ = function()
+{
+ var nodeAlias = devtools.profiler.WebKitViewNode;
+ this.functionName = this.internalFuncName;
+
+ var strippedName = nodeAlias.FUNC_NAME_STRIP_RE.exec(this.functionName);
+ if (strippedName)
+ this.functionName = strippedName[1];
+
+ var parsedName = nodeAlias.FUNC_NAME_PARSE_RE.exec(this.functionName);
+ if (parsedName) {
+ this.functionName = parsedName[1];
+ if (parsedName[4])
+ this.functionName += parsedName[4];
+ this.url = parsedName[2];
+ this.lineNumber = parsedName[3];
+ } else {
+ this.url = '';
+ this.lineNumber = 0;
+ }
+};
+
+
+/**
+ * Ancestor of a profile object that leaves out only JS-related functions.
+ * @constructor
+ */
+devtools.profiler.JsProfile = function()
+{
+ devtools.profiler.Profile.call(this);
+};
+devtools.profiler.JsProfile.prototype.__proto__ = devtools.profiler.Profile.prototype;
+
+
+/**
+ * RegExp that leaves only non-native JS functions.
+ * @type {RegExp}
+ */
+devtools.profiler.JsProfile.JS_NON_NATIVE_RE = new RegExp(
+ "^" +
+ "(?:Callback:)|" +
+ "(?:Script: (?!native))|" +
+ "(?:(?:LazyCompile|Function): [^ ]*(?: (?!native )[^ ]+:\\d+)?$)");
+
+
+/**
+ * @override
+ */
+devtools.profiler.JsProfile.prototype.skipThisFunction = function(name)
+{
+ return !devtools.profiler.JsProfile.JS_NON_NATIVE_RE.test(name);
+};
+
+
+/**
+ * Profiler processor. Consumes profiler log and builds profile views.
+ * FIXME: change field naming style to use trailing underscore.
+ *
+ * @param {function(devtools.profiler.ProfileView)} newProfileCallback Callback
+ * that receives a new processed profile.
+ * @constructor
+ */
+devtools.profiler.Processor = function()
+{
+ var dispatches = {
+ "code-creation": {
+ parsers: [null, this.createAddressParser("code"), parseInt, null],
+ processor: this.processCodeCreation_, backrefs: true,
+ needsProfile: true },
+ "code-move": { parsers: [this.createAddressParser("code"),
+ this.createAddressParser("code-move-to")],
+ processor: this.processCodeMove_, backrefs: true,
+ needsProfile: true },
+ "code-delete": { parsers: [this.createAddressParser("code")],
+ processor: this.processCodeDelete_, backrefs: true,
+ needsProfile: true },
+ "function-creation": { parsers: [this.createAddressParser("code"),
+ this.createAddressParser("function-obj")],
+ processor: this.processFunctionCreation_, backrefs: true },
+ "function-move": { parsers: [this.createAddressParser("code"),
+ this.createAddressParser("code-move-to")],
+ processor: this.processFunctionMove_, backrefs: true },
+ "function-delete": { parsers: [this.createAddressParser("code")],
+ processor: this.processFunctionDelete_, backrefs: true },
+ "tick": { parsers: [this.createAddressParser("code"),
+ this.createAddressParser("stack"), parseInt, "var-args"],
+ processor: this.processTick_, backrefs: true, needProfile: true },
+ "profiler": { parsers: [null, "var-args"],
+ processor: this.processProfiler_, needsProfile: false },
+ "heap-sample-begin": { parsers: [null, null, parseInt],
+ processor: this.processHeapSampleBegin_ },
+ "heap-sample-stats": { parsers: [null, null, parseInt, parseInt],
+ processor: this.processHeapSampleStats_ },
+ "heap-sample-item": { parsers: [null, parseInt, parseInt],
+ processor: this.processHeapSampleItem_ },
+ "heap-js-cons-item": { parsers: [null, parseInt, parseInt],
+ processor: this.processHeapJsConsItem_ },
+ "heap-js-ret-item": { parsers: [null, "var-args"],
+ processor: this.processHeapJsRetItem_ },
+ "heap-sample-end": { parsers: [null, null],
+ processor: this.processHeapSampleEnd_ },
+ // Not used in DevTools Profiler.
+ "shared-library": null,
+ // Obsolete row types.
+ "code-allocate": null,
+ "begin-code-region": null,
+ "end-code-region": null};
+
+ if (devtools.profiler.Profile.VERSION === 2) {
+ dispatches["tick"] = { parsers: [this.createAddressParser("code"),
+ this.createAddressParser("stack"),
+ this.createAddressParser("func"), parseInt, "var-args"],
+ processor: this.processTickV2_, backrefs: true };
+ }
+
+ devtools.profiler.LogReader.call(this, dispatches);
+
+ /**
+ * Callback that is called when a new profile is encountered in the log.
+ * @type {function()}
+ */
+ this.startedProfileProcessing_ = null;
+
+ /**
+ * Callback that is called periodically to display processing status.
+ * @type {function()}
+ */
+ this.profileProcessingStatus_ = null;
+
+ /**
+ * Callback that is called when a profile has been processed and is ready
+ * to be shown.
+ * @type {function(devtools.profiler.ProfileView)}
+ */
+ this.finishedProfileProcessing_ = null;
+
+ /**
+ * The current profile.
+ * @type {devtools.profiler.JsProfile}
+ */
+ this.currentProfile_ = null;
+
+ /**
+ * Builder of profile views. Created during "profiler,begin" event processing.
+ * @type {devtools.profiler.WebKitViewBuilder}
+ */
+ this.viewBuilder_ = null;
+
+ /**
+ * Next profile id.
+ * @type {number}
+ */
+ this.profileId_ = 1;
+
+ /**
+ * Counter for processed ticks.
+ * @type {number}
+ */
+ this.ticksCount_ = 0;
+
+ /**
+ * Interval id for updating processing status.
+ * @type {number}
+ */
+ this.processingInterval_ = null;
+
+ /**
+ * The current heap snapshot.
+ * @type {string}
+ */
+ this.currentHeapSnapshot_ = null;
+
+ /**
+ * Next heap snapshot id.
+ * @type {number}
+ */
+ this.heapSnapshotId_ = 1;
+};
+devtools.profiler.Processor.prototype.__proto__ = devtools.profiler.LogReader.prototype;
+
+
+/**
+ * @override
+ */
+devtools.profiler.Processor.prototype.printError = function(str)
+{
+ WebInspector.log(str);
+};
+
+
+/**
+ * @override
+ */
+devtools.profiler.Processor.prototype.skipDispatch = function(dispatch)
+{
+ return dispatch.needsProfile && this.currentProfile_ === null;
+};
+
+
+/**
+ * Sets profile processing callbacks.
+ *
+ * @param {function()} started Started processing callback.
+ * @param {function(devtools.profiler.ProfileView)} finished Finished
+ * processing callback.
+ */
+devtools.profiler.Processor.prototype.setCallbacks = function(started, processing, finished)
+{
+ this.startedProfileProcessing_ = started;
+ this.profileProcessingStatus_ = processing;
+ this.finishedProfileProcessing_ = finished;
+};
+
+
+/**
+ * An address for the fake "(program)" entry. WebKit's visualisation
+ * has assumptions on how the top of the call tree should look like,
+ * and we need to add a fake entry as the topmost function. This
+ * address is chosen because it's the end address of the first memory
+ * page, which is never used for code or data, but only as a guard
+ * page for catching AV errors.
+ *
+ * @type {number}
+ */
+devtools.profiler.Processor.PROGRAM_ENTRY = 0xffff;
+/**
+ * @type {string}
+ */
+devtools.profiler.Processor.PROGRAM_ENTRY_STR = "0xffff";
+
+
+/**
+ * Sets new profile callback.
+ * @param {function(devtools.profiler.ProfileView)} callback Callback function.
+ */
+devtools.profiler.Processor.prototype.setNewProfileCallback = function(callback)
+{
+ this.newProfileCallback_ = callback;
+};
+
+
+devtools.profiler.Processor.prototype.processProfiler_ = function(state, params)
+{
+ switch (state) {
+ case "resume":
+ if (this.currentProfile_ === null) {
+ this.currentProfile_ = new devtools.profiler.JsProfile();
+ // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
+ this.currentProfile_.addCode("Function", "(program)", devtools.profiler.Processor.PROGRAM_ENTRY, 1);
+ if (this.startedProfileProcessing_)
+ this.startedProfileProcessing_();
+ this.ticksCount_ = 0;
+ var self = this;
+ if (this.profileProcessingStatus_) {
+ this.processingInterval_ = window.setInterval(
+ function() { self.profileProcessingStatus_(self.ticksCount_); },
+ 1000);
+ }
+ }
+ break;
+ case "pause":
+ if (this.currentProfile_ !== null) {
+ window.clearInterval(this.processingInterval_);
+ this.processingInterval_ = null;
+ if (this.finishedProfileProcessing_)
+ this.finishedProfileProcessing_(this.createProfileForView());
+ this.currentProfile_ = null;
+ }
+ break;
+ case "begin":
+ var samplingRate = NaN;
+ if (params.length > 0)
+ samplingRate = parseInt(params[0]);
+ if (isNaN(samplingRate))
+ samplingRate = 1;
+ this.viewBuilder_ = new devtools.profiler.WebKitViewBuilder(samplingRate);
+ break;
+ // These events are valid but aren't used.
+ case "compression":
+ case "end": break;
+ default:
+ throw new Error("unknown profiler state: " + state);
+ }
+};
+
+
+devtools.profiler.Processor.prototype.processCodeCreation_ = function(type, start, size, name)
+{
+ this.currentProfile_.addCode(this.expandAlias(type), name, start, size);
+};
+
+
+devtools.profiler.Processor.prototype.processCodeMove_ = function(from, to)
+{
+ this.currentProfile_.moveCode(from, to);
+};
+
+
+devtools.profiler.Processor.prototype.processCodeDelete_ = function(start)
+{
+ this.currentProfile_.deleteCode(start);
+};
+
+
+devtools.profiler.Processor.prototype.processFunctionCreation_ = function(functionAddr, codeAddr)
+{
+ this.currentProfile_.addCodeAlias(functionAddr, codeAddr);
+};
+
+
+devtools.profiler.Processor.prototype.processFunctionMove_ = function(from, to)
+{
+ this.currentProfile_.safeMoveDynamicCode(from, to);
+};
+
+
+devtools.profiler.Processor.prototype.processFunctionDelete_ = function(start)
+{
+ this.currentProfile_.safeDeleteDynamicCode(start);
+};
+
+
+// TODO(mnaganov): Remove after next V8 roll.
+devtools.profiler.Processor.prototype.processTick_ = function(pc, sp, vmState, stack)
+{
+ // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
+ stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR);
+ this.currentProfile_.recordTick(this.processStack(pc, stack));
+ this.ticksCount_++;
+};
+
+
+devtools.profiler.Processor.prototype.processTickV2_ = function(pc, sp, func, vmState, stack)
+{
+ // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
+ stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR);
+
+
+ if (func) {
+ var funcEntry = this.currentProfile_.findEntry(func);
+ if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction())
+ func = 0;
+ else {
+ var currEntry = this.currentProfile_.findEntry(pc);
+ if (!currEntry || !currEntry.isJSFunction || currEntry.isJSFunction()) {
+ func = 0;
+ }
+ }
+ }
+
+ this.currentProfile_.recordTick(this.processStack(pc, func, stack));
+ this.ticksCount_++;
+};
+
+
+devtools.profiler.Processor.prototype.processHeapSampleBegin_ = function(space, state, ticks)
+{
+ if (space !== "Heap") return;
+ this.currentHeapSnapshot_ = {
+ number: this.heapSnapshotId_++,
+ entries: {},
+ clusters: {},
+ lowlevels: {},
+ ticks: ticks
+ };
+};
+
+
+devtools.profiler.Processor.prototype.processHeapSampleStats_ = function(space, state, capacity, used)
+{
+ if (space !== "Heap") return;
+};
+
+
+devtools.profiler.Processor.prototype.processHeapSampleItem_ = function(item, number, size)