Index: chrome/tools/test/reference_build/chrome_linux/resources/inspector/profiler_processor.js |
diff --git a/chrome/tools/test/reference_build/chrome_linux/resources/inspector/profiler_processor.js b/chrome/tools/test/reference_build/chrome_linux/resources/inspector/profiler_processor.js |
index e2903501f957d0bf21c81ed49ebee447f1fcb09a..65cbeee899c698b2fed7f372a67e185bc6f32d54 100644 |
--- a/chrome/tools/test/reference_build/chrome_linux/resources/inspector/profiler_processor.js |
+++ b/chrome/tools/test/reference_build/chrome_linux/resources/inspector/profiler_processor.js |
@@ -11,6 +11,95 @@ goog.provide('devtools.profiler.Processor'); |
/** |
+ * 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); |
+}; |
+goog.inherits(devtools.profiler.WebKitViewBuilder, |
+ devtools.profiler.ViewBuilder); |
+ |
+ |
+/** |
+ * @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; |
+}; |
+goog.inherits(devtools.profiler.WebKitViewNode, |
+ devtools.profiler.ProfileView.Node); |
+ |
+ |
+/** |
+ * RegEx for stripping V8's prefixes of compiled functions. |
+ */ |
+devtools.profiler.WebKitViewNode.FUNC_NAME_STRIP_RE = |
+ /^(?:LazyCompile|Function): (.*)$/; |
+ |
+ |
+/** |
+ * RegEx for extracting script source URL and line number. |
+ */ |
+devtools.profiler.WebKitViewNode.FUNC_NAME_PARSE_RE = |
+ /^([^ ]+) (.*):(\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 |
*/ |
@@ -21,165 +110,329 @@ goog.inherits(devtools.profiler.JsProfile, devtools.profiler.Profile); |
/** |
+ * RegExp that leaves only JS functions. |
* @type {RegExp} |
*/ |
devtools.profiler.JsProfile.JS_FUNC_RE = /^(LazyCompile|Function|Script):/; |
+/** |
+ * RegExp that filters out native code (ending with "native src.js:xxx"). |
+ * @type {RegExp} |
+ */ |
+devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE = /\ native\ \w+\.js:\d+$/; |
+ |
+/** |
+ * RegExp that filters out native scripts. |
+ * @type {RegExp} |
+ */ |
+devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE = /^Script:\ native/; |
+ |
+/** |
+ * RegExp that filters out devtools functions. See inject.js and |
+ * inject_dispatch.js. |
+ * @type {RegExp} |
+ */ |
+devtools.profiler.JsProfile.JS_DEVTOOLS_FUNC_RE = |
+ /^\w+:\ devtools(\$\$|\.Injected)/; |
+ |
/** |
* @override |
*/ |
devtools.profiler.JsProfile.prototype.skipThisFunction = function(name) { |
- return !devtools.profiler.JsProfile.JS_FUNC_RE.test(name); |
+ return !devtools.profiler.JsProfile.JS_FUNC_RE.test(name) || |
+ // To profile V8's natives comment out two lines below and '||' above. |
+ devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE.test(name) || |
+ devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE.test(name) || |
+ devtools.profiler.JsProfile.JS_DEVTOOLS_FUNC_RE.test(name); |
}; |
/** |
* Profiler processor. Consumes profiler log and builds profile views. |
+ * |
+ * @param {function(devtools.profiler.ProfileView)} newProfileCallback Callback |
+ * that receives a new processed profile. |
* @constructor |
*/ |
devtools.profiler.Processor = function() { |
+ devtools.profiler.LogReader.call(this, { |
+ '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 }, |
+ '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-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}); |
+ |
+ |
+ /** |
+ * 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; |
+ |
/** |
- * Current profile. |
+ * The current profile. |
* @type {devtools.profiler.JsProfile} |
*/ |
- this.profile_ = new devtools.profiler.JsProfile(); |
+ this.currentProfile_ = null; |
/** |
- * Builder of profile views. |
- * @type {devtools.profiler.ViewBuilder} |
+ * Builder of profile views. Created during "profiler,begin" event processing. |
+ * @type {devtools.profiler.WebKitViewBuilder} |
*/ |
- this.viewBuilder_ = new devtools.profiler.ViewBuilder(1); |
+ this.viewBuilder_ = null; |
/** |
* Next profile id. |
* @type {number} |
*/ |
this.profileId_ = 1; |
+ |
+ /** |
+ * Counter for processed ticks. |
+ * @type {number} |
+ */ |
+ this.ticksCount_ = 0; |
+ |
+ /** |
+ * The current heap snapshot. |
+ * @type {string} |
+ */ |
+ this.currentHeapSnapshot_ = null; |
+ |
+ /** |
+ * Next heap snapshot id. |
+ * @type {number} |
+ */ |
+ this.heapSnapshotId_ = 1; |
}; |
+goog.inherits(devtools.profiler.Processor, devtools.profiler.LogReader); |
/** |
- * A dispatch table for V8 profiler event log records. |
- * @private |
+ * @override |
*/ |
-devtools.profiler.Processor.RecordsDispatch_ = { |
- 'code-creation': { parsers: [null, parseInt, parseInt, null], |
- processor: 'processCodeCreation_' }, |
- 'code-move': { parsers: [parseInt, parseInt], |
- processor: 'processCodeMove_' }, |
- 'code-delete': { parsers: [parseInt], processor: 'processCodeDelete_' }, |
- 'tick': { parsers: [parseInt, parseInt, parseInt, 'var-args'], |
- processor: 'processTick_' }, |
- // Not used in DevTools Profiler. |
- 'profiler': null, |
- 'shared-library': null, |
- // Obsolete row types. |
- 'code-allocate': null, |
- 'begin-code-region': null, |
- 'end-code-region': null |
+devtools.profiler.Processor.prototype.printError = function(str) { |
+ debugPrint(str); |
}; |
/** |
- * Processes a portion of V8 profiler event log. |
- * |
- * @param {string} chunk A portion of log. |
+ * @override |
*/ |
-devtools.profiler.Processor.prototype.processLogChunk = function(chunk) { |
- this.processLog_(chunk.split('\n')); |
+devtools.profiler.Processor.prototype.skipDispatch = function(dispatch) { |
+ return dispatch.needsProfile && this.currentProfile_ == null; |
}; |
/** |
- * Processes a log lines. |
+ * Sets profile processing callbacks. |
* |
- * @param {Array<string>} lines Log lines. |
- * @private |
+ * @param {function()} started Started processing callback. |
+ * @param {function(devtools.profiler.ProfileView)} finished Finished |
+ * processing callback. |
*/ |
-devtools.profiler.Processor.prototype.processLog_ = function(lines) { |
- var csvParser = new devtools.profiler.CsvParser(); |
- try { |
- for (var i = 0, n = lines.length; i < n; ++i) { |
- var line = lines[i]; |
- if (!line) { |
- continue; |
- } |
- var fields = csvParser.parseLine(line); |
- this.dispatchLogRow_(fields); |
- } |
- } catch (e) { |
- debugPrint('line ' + (i + 1) + ': ' + (e.message || e)); |
- throw e; |
- } |
+devtools.profiler.Processor.prototype.setCallbacks = function( |
+ started, processing, finished) { |
+ this.startedProfileProcessing_ = started; |
+ this.profileProcessingStatus_ = processing; |
+ this.finishedProfileProcessing_ = finished; |
}; |
/** |
- * Does a dispatch of a log record. |
+ * 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. |
* |
- * @param {Array<string>} fields Log record. |
- * @private |
+ * @type {number} |
*/ |
-devtools.profiler.Processor.prototype.dispatchLogRow_ = function(fields) { |
- // Obtain the dispatch. |
- var command = fields[0]; |
- if (!(command in devtools.profiler.Processor.RecordsDispatch_)) { |
- throw new Error('unknown command: ' + command); |
- } |
- var dispatch = devtools.profiler.Processor.RecordsDispatch_[command]; |
+devtools.profiler.Processor.PROGRAM_ENTRY = 0xffff; |
+/** |
+ * @type {string} |
+ */ |
+devtools.profiler.Processor.PROGRAM_ENTRY_STR = '0xffff'; |
- if (dispatch === null) { |
- return; |
- } |
- // Parse fields. |
- var parsedFields = []; |
- for (var i = 0; i < dispatch.parsers.length; ++i) { |
- var parser = dispatch.parsers[i]; |
- if (parser === null) { |
- parsedFields.push(fields[1 + i]); |
- } else if (typeof parser == 'function') { |
- parsedFields.push(parser(fields[1 + i])); |
- } else { |
- // var-args |
- parsedFields.push(fields.slice(1 + i)); |
+/** |
+ * 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) { |
+ var processingInterval = null; |
+ 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_) { |
+ processingInterval = window.setInterval( |
+ function() { self.profileProcessingStatus_(self.ticksCount_); }, |
+ 1000); |
+ } |
+ } |
break; |
- } |
+ case 'pause': |
+ if (this.currentProfile_ != null) { |
+ window.clearInterval(processingInterval); |
+ 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); |
} |
- |
- // Run the processor. |
- this[dispatch.processor].apply(this, parsedFields); |
}; |
devtools.profiler.Processor.prototype.processCodeCreation_ = function( |
type, start, size, name) { |
- this.profile_.addCode(type, name, start, size); |
+ this.currentProfile_.addCode(this.expandAlias(type), name, start, size); |
}; |
devtools.profiler.Processor.prototype.processCodeMove_ = function(from, to) { |
- this.profile_.moveCode(from, to); |
+ this.currentProfile_.moveCode(from, to); |
}; |
devtools.profiler.Processor.prototype.processCodeDelete_ = function(start) { |
- this.profile_.deleteCode(start); |
+ this.currentProfile_.deleteCode(start); |
}; |
devtools.profiler.Processor.prototype.processTick_ = function( |
pc, sp, vmState, stack) { |
- var fullStack = [pc]; |
- for (var i = 0, n = stack.length; i < n; ++i) { |
- var frame = stack[i]; |
- // Leave only numbers starting with 0x. Filter possible 'overflow' string. |
- if (frame.charAt(0) == '0') { |
- fullStack.push(parseInt(frame, 16)); |
- } |
+ // 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.processHeapSampleBegin_ = function( |
+ space, state, ticks) { |
+ if (space != 'Heap') return; |
+ this.currentHeapSnapshot_ = { |
+ number: this.heapSnapshotId_++, |
+ entries: {}, |
+ lowlevels: {}, |
+ ticks: ticks |
+ }; |
+}; |
+ |
+ |
+devtools.profiler.Processor.prototype.processHeapSampleStats_ = function( |
+ space, state, capacity, used) { |
+ if (space != 'Heap') return; |
+ this.currentHeapSnapshot_.capacity = capacity; |
+ this.currentHeapSnapshot_.used = used; |
+}; |
+ |
+ |
+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 |
+ }; |
+}; |
+ |
+ |
+devtools.profiler.Processor.prototype.processHeapSampleEnd_ = function( |
+ space, state) { |
+ if (space != 'Heap') return; |
+ var snapshot = this.currentHeapSnapshot_; |
+ this.currentHeapSnapshot_ = null; |
+ // For some reason, 'used' from 'heap-sample-stats' sometimes differ from |
+ // the sum of objects sizes. To avoid discrepancy, we re-calculate 'used'. |
+ snapshot.used = 0; |
+ for (var item in snapshot.lowlevels) { |
+ snapshot.used += snapshot.lowlevels[item].size; |
} |
- this.profile_.recordTick(fullStack); |
+ WebInspector.panels.heap.addSnapshot(snapshot); |
}; |
@@ -187,15 +440,9 @@ devtools.profiler.Processor.prototype.processTick_ = function( |
* Creates a profile for further displaying in ProfileView. |
*/ |
devtools.profiler.Processor.prototype.createProfileForView = function() { |
- var profile = new devtools.profiler.ProfileView(); |
+ var profile = this.viewBuilder_.buildView( |
+ this.currentProfile_.getTopDownProfile()); |
profile.uid = this.profileId_++; |
profile.title = UserInitiatedProfileName + '.' + profile.uid; |
- // A trick to cope with ProfileView.bottomUpProfileDataGridTree and |
- // ProfileView.topDownProfileDataGridTree behavior. |
- profile.head = profile; |
- profile.heavyProfile = this.viewBuilder_.buildView( |
- this.profile_.getBottomUpProfile(), true); |
- profile.treeProfile = this.viewBuilder_.buildView( |
- this.profile_.getTopDownProfile()); |
return profile; |
}; |