WebKit/chromium/src/js/ProfilerProcessor.js
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebKit/chromium/src/js/ProfilerProcessor.js	Fri Sep 17 09:02:29 2010 +0300
@@ -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)
+{
+    debugPrint(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)
+{
+    if (!this.currentHeapSnapshot_) return;
+    this.currentHeapSnapshot_.lowlevels[item] = {
+        type: item, count: number, size: size
+    };
+};
+
+
+devtools.profiler.Processor.prototype.processHeapJsConsItem_ = function(item, number, size)
+{
+    if (!this.currentHeapSnapshot_) return;
+    this.currentHeapSnapshot_.entries[item] = {
+        cons: item, count: number, size: size, retainers: {}
+    };
+};
+
+
+devtools.profiler.Processor.prototype.processHeapJsRetItem_ = function(item, retainersArray)
+{
+    if (!this.currentHeapSnapshot_) return;
+    var rawRetainers = {};
+    for (var i = 0, n = retainersArray.length; i < n; ++i) {
+        var entry = retainersArray[i].split(";");
+        rawRetainers[entry[0]] = parseInt(entry[1], 10);
+    }
+
+    function mergeRetainers(entry) {
+        for (var rawRetainer in rawRetainers) {
+            var consName = rawRetainer.indexOf(":") !== -1 ? rawRetainer.split(":")[0] : rawRetainer;
+            if (!(consName in entry.retainers))
+                entry.retainers[consName] = { cons: consName, count: 0, clusters: {} };
+            var retainer = entry.retainers[consName];
+            retainer.count += rawRetainers[rawRetainer];
+            if (consName !== rawRetainer)
+                retainer.clusters[rawRetainer] = true;
+        }
+    }
+
+    if (item.indexOf(":") !== -1) {
+      // Array, Function, or Object instances cluster case.
+      if (!(item in this.currentHeapSnapshot_.clusters)) {
+          this.currentHeapSnapshot_.clusters[item] = {
+              cons: item, retainers: {}
+          };
+      }
+      mergeRetainers(this.currentHeapSnapshot_.clusters[item]);
+      item = item.split(":")[0];
+    }
+    mergeRetainers(this.currentHeapSnapshot_.entries[item]);
+};
+
+
+devtools.profiler.Processor.prototype.processHeapSampleEnd_ = function(space, state)
+{
+    if (space !== "Heap") return;
+    var snapshot = this.currentHeapSnapshot_;
+    this.currentHeapSnapshot_ = null;
+    WebInspector.panels.profiles.addSnapshot(snapshot);
+};
+
+
+/**
+ * Creates a profile for further displaying in ProfileView.
+ */
+devtools.profiler.Processor.prototype.createProfileForView = function()
+{
+    var profile = this.viewBuilder_.buildView(this.currentProfile_.getTopDownProfile());
+    profile.uid = this.profileId_++;
+    profile.title = UserInitiatedProfileName + "." + profile.uid;
+    return profile;
+};