diff -r 000000000000 -r 4f2f89ce4247 WebKit/chromium/src/js/ProfilerProcessor.js --- /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; +};