Chromium Code Reviews| Index: tools/tickprocessor.js |
| diff --git a/tools/tickprocessor.js b/tools/tickprocessor.js |
| index f1a11ccc948808d92eed172a4072b31d824957a6..66b0e68ed1fd1a59f104048a2b061c6d4b4c7094 100644 |
| --- a/tools/tickprocessor.js |
| +++ b/tools/tickprocessor.js |
| @@ -30,7 +30,6 @@ function inherits(childCtor, parentCtor) { |
| childCtor.prototype.__proto__ = parentCtor.prototype; |
| }; |
| - |
|
Jakob Kummerow
2013/08/23 13:08:35
nit: leave this line in please.
Daniel Kurka
2013/08/23 13:31:43
Done.
|
| function V8Profile(separateIc) { |
| Profile.call(this); |
| if (!separateIc) { |
| @@ -153,7 +152,8 @@ function TickProcessor( |
| stateFilter, |
| snapshotLogProcessor, |
| distortion, |
| - range) { |
| + range, |
| + sourceMap) { |
| LogReader.call(this, { |
| 'shared-library': { parsers: [null, parseInt, parseInt], |
| processor: this.processSharedLibrary }, |
| @@ -196,6 +196,7 @@ function TickProcessor( |
| this.ignoreUnknown_ = ignoreUnknown; |
| this.stateFilter_ = stateFilter; |
| this.snapshotLogProcessor_ = snapshotLogProcessor; |
| + this.sourceMap = sourceMap; |
| this.deserializedEntriesNames_ = []; |
| var ticks = this.ticks_ = |
| { total: 0, unaccounted: 0, excluded: 0, gc: 0 }; |
| @@ -544,17 +545,52 @@ TickProcessor.prototype.processProfile = function( |
| } |
| }; |
| +TickProcessor.prototype.getLineAndColumn = function(name) { |
| + var re = /:([0-9]+):([0-9]+)$/; |
| + var array = re.exec(name); |
| + if (!array) { |
| + return null; |
|
Jakob Kummerow
2013/08/23 13:08:35
nit: indentation
Daniel Kurka
2013/08/23 13:31:43
Done.
|
| + } |
| + return {line: array[1], column: array[2]}; |
| +} |
| + |
| +TickProcessor.prototype.hasSourceMap = function() { |
| + return this.sourceMap != null; |
| +}; |
| + |
| + |
| +TickProcessor.prototype.formatFunctionName = function(funcName) { |
| + if (!this.hasSourceMap()) { |
| + return funcName; |
| + } |
| + var lc = this.getLineAndColumn(funcName); |
| + if (lc == null) { |
| + return funcName; |
| + } |
| + // in source maps lines and columns are zero based |
| + var lineNumber = lc.line - 1; |
| + var column = lc.column - 1; |
| + var entry = this.sourceMap.findEntry(lineNumber, column); |
| + var sourceFile = entry[2]; |
| + var sourceLine = entry[3] + 1; |
| + var sourceColumn = entry[4] + 1; |
| + |
| + return sourceFile + ':' + sourceLine + ':' + sourceColumn + ' -> ' + funcName; |
| +}; |
| TickProcessor.prototype.printEntries = function( |
| profile, nonLibTicks, filterP) { |
| + var that = this; |
| this.processProfile(profile, filterP, function (rec) { |
| if (rec.selfTime == 0) return; |
| var nonLibPct = nonLibTicks != null ? |
| rec.selfTime * 100.0 / nonLibTicks : 0.0; |
| + var funcName = that.formatFunctionName(rec.internalFuncName); |
| + |
| print(' ' + padLeft(rec.selfTime, 5) + ' ' + |
| padLeft(rec.selfPercent.toFixed(1), 5) + '% ' + |
| padLeft(nonLibPct.toFixed(1), 5) + '% ' + |
| - rec.internalFuncName); |
| + funcName); |
| }); |
| }; |
| @@ -566,9 +602,10 @@ TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) { |
| this.processProfile(profile, function() { return true; }, function (rec) { |
| // Cut off too infrequent callers. |
| if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return; |
| + var funcName = self.formatFunctionName(rec.internalFuncName); |
| print(' ' + padLeft(rec.totalTime, 5) + ' ' + |
| padLeft(rec.parentTotalPercent.toFixed(1), 5) + '% ' + |
| - indentStr + rec.internalFuncName); |
| + indentStr + funcName); |
| // Limit backtrace depth. |
| if (indent < 2 * self.callGraphSize_) { |
| self.printHeavyProfile(rec.children, indent + 2); |
| @@ -825,7 +862,9 @@ function ArgumentsProcessor(args) { |
| '--range': ['range', 'auto,auto', |
| 'Specify the range limit as [start],[end]'], |
| '--distortion': ['distortion', 0, |
| - 'Specify the logging overhead in picoseconds'] |
| + 'Specify the logging overhead in picoseconds'], |
| + '--sourceMap': ['sourceMap', null, |
|
Jakob Kummerow
2013/08/23 13:08:35
For consistency with other arg names, let's call t
Daniel Kurka
2013/08/23 13:31:43
Done.
|
| + 'Specify the source map that should be used for output'] |
|
Jakob Kummerow
2013/08/23 13:08:35
nit: indentation
Daniel Kurka
2013/08/23 13:31:43
Done.
|
| }; |
| this.argsDispatch_['--js'] = this.argsDispatch_['-j']; |
| this.argsDispatch_['--gc'] = this.argsDispatch_['-g']; |
| @@ -911,3 +950,241 @@ ArgumentsProcessor.prototype.printUsageAndExit = function() { |
| quit(2); |
| }; |
| + |
| +/** |
| + * Implements Source Map V3 model. See http://code.google.com/p/closure-compiler/wiki/SourceMaps |
|
Jakob Kummerow
2013/08/23 13:08:35
This section looks a lot like http://src.chromium.
Daniel Kurka
2013/08/23 13:31:43
Actually we only need a modified load method
The
|
| + * for format description. |
| + * @constructor |
| + * @param {string} sourceMappingURL |
| + * @param {SourceMapV3} payload |
| + */ |
| +SourceMap = function(sourceMappingURL, payload) |
| +{ |
| + if (!SourceMap.prototype._base64Map) { |
| + const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| + SourceMap.prototype._base64Map = {}; |
| + for (var i = 0; i < base64Digits.length; ++i) |
| + SourceMap.prototype._base64Map[base64Digits.charAt(i)] = i; |
| + } |
| + |
| + this._sourceMappingURL = sourceMappingURL; |
| + this._reverseMappingsBySourceURL = {}; |
| + this._mappings = []; |
| + this._sources = {}; |
| + this._sourceContentByURL = {}; |
| + this._parseMappingPayload(payload); |
| +} |
| + |
| +/** |
| + * @param {string} sourceMapURL |
| + */ |
| +SourceMap.load = function(sourceMapURL) |
| +{ |
| + var content = readFile(sourceMapURL); |
| + var sourceMapObject = /** @type {SourceMapV3} */ (JSON.parse(content)); |
| + return new SourceMap(sourceMapURL, sourceMapObject); |
| +} |
| + |
| +SourceMap.prototype = { |
| + /** |
| + * @return {Array.<string>} |
| + */ |
| + sources: function() |
| + { |
| + return Object.keys(this._sources); |
| + }, |
| + |
| + /** |
| + * @param {SourceMapV3} mappingPayload |
| + */ |
| + _parseMappingPayload: function(mappingPayload) |
| + { |
| + if (mappingPayload.sections) |
| + this._parseSections(mappingPayload.sections); |
| + else |
| + this._parseMap(mappingPayload, 0, 0); |
| + }, |
| + |
| + /** |
| + * @param {Array.<SourceMapV3.Section>} sections |
| + */ |
| + _parseSections: function(sections) |
| + { |
| + for (var i = 0; i < sections.length; ++i) { |
| + var section = sections[i]; |
| + this._parseMap(section.map, section.offset.line, section.offset.column); |
| + } |
| + }, |
| + |
| + /** |
| + * @param {number} lineNumber in compiled resource |
| + * @param {number} columnNumber in compiled resource |
| + * @return {?Array} |
| + */ |
| + findEntry: function(lineNumber, columnNumber) |
| + { |
| + var first = 0; |
| + var count = this._mappings.length; |
| + while (count > 1) { |
| + var step = count >> 1; |
| + var middle = first + step; |
| + var mapping = this._mappings[middle]; |
| + if (lineNumber < mapping[0] || (lineNumber === mapping[0] && columnNumber < mapping[1])) |
| + count = step; |
| + else { |
| + first = middle; |
| + count -= step; |
| + } |
| + } |
| + var entry = this._mappings[first]; |
| + if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1]))) |
| + return null; |
| + return entry; |
| + }, |
| + |
| + /** |
| + * @override |
| + */ |
| + _parseMap: function(map, lineNumber, columnNumber) |
| + { |
| + var sourceIndex = 0; |
| + var sourceLineNumber = 0; |
| + var sourceColumnNumber = 0; |
| + var nameIndex = 0; |
| + |
| + var sources = []; |
| + var originalToCanonicalURLMap = {}; |
| + for (var i = 0; i < map.sources.length; ++i) { |
| + var originalSourceURL = map.sources[i]; |
| + var sourceRoot = map.sourceRoot || ""; |
| + if (sourceRoot && !sourceRoot.endsWith("/")) |
| + sourceRoot += "/"; |
| + var href = sourceRoot + originalSourceURL; |
| + var url = href; |
| + originalToCanonicalURLMap[originalSourceURL] = url; |
| + sources.push(url); |
| + this._sources[url] = true; |
| + |
| + if (map.sourcesContent && map.sourcesContent[i]) |
| + this._sourceContentByURL[url] = map.sourcesContent[i]; |
| + } |
| + |
| + var stringCharIterator = new SourceMap.StringCharIterator(map.mappings); |
| + var sourceURL = sources[sourceIndex]; |
| + |
| + while (true) { |
| + if (stringCharIterator.peek() === ",") |
| + stringCharIterator.next(); |
| + else { |
| + while (stringCharIterator.peek() === ";") { |
| + lineNumber += 1; |
| + columnNumber = 0; |
| + stringCharIterator.next(); |
| + } |
| + if (!stringCharIterator.hasNext()) |
| + break; |
| + } |
| + |
| + columnNumber += this._decodeVLQ(stringCharIterator); |
| + if (this._isSeparator(stringCharIterator.peek())) { |
| + this._mappings.push([lineNumber, columnNumber]); |
| + continue; |
| + } |
| + |
| + var sourceIndexDelta = this._decodeVLQ(stringCharIterator); |
| + if (sourceIndexDelta) { |
| + sourceIndex += sourceIndexDelta; |
| + sourceURL = sources[sourceIndex]; |
| + } |
| + sourceLineNumber += this._decodeVLQ(stringCharIterator); |
| + sourceColumnNumber += this._decodeVLQ(stringCharIterator); |
| + if (!this._isSeparator(stringCharIterator.peek())) |
| + nameIndex += this._decodeVLQ(stringCharIterator); |
| + |
| + this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]); |
| + } |
| + |
| + for (var i = 0; i < this._mappings.length; ++i) { |
| + var mapping = this._mappings[i]; |
| + var url = mapping[2]; |
| + if (!url) |
| + continue; |
| + if (!this._reverseMappingsBySourceURL[url]) |
| + this._reverseMappingsBySourceURL[url] = []; |
| + var reverseMappings = this._reverseMappingsBySourceURL[url]; |
| + var sourceLine = mapping[3]; |
| + if (!reverseMappings[sourceLine]) |
| + reverseMappings[sourceLine] = [mapping[0], mapping[1]]; |
| + } |
| + }, |
| + |
| + /** |
| + * @param {string} char |
| + * @return {boolean} |
| + */ |
| + _isSeparator: function(char) |
| + { |
| + return char === "," || char === ";"; |
| + }, |
| + |
| + /** |
| + * @param {SourceMap.StringCharIterator} stringCharIterator |
| + * @return {number} |
| + */ |
| + _decodeVLQ: function(stringCharIterator) |
| + { |
| + // Read unsigned value. |
| + var result = 0; |
| + var shift = 0; |
| + do { |
| + var digit = this._base64Map[stringCharIterator.next()]; |
| + result += (digit & this._VLQ_BASE_MASK) << shift; |
| + shift += this._VLQ_BASE_SHIFT; |
| + } while (digit & this._VLQ_CONTINUATION_MASK); |
| + |
| + // Fix the sign. |
| + var negative = result & 1; |
| + result >>= 1; |
| + return negative ? -result : result; |
| + }, |
| + |
| + _VLQ_BASE_SHIFT: 5, |
| + _VLQ_BASE_MASK: (1 << 5) - 1, |
| + _VLQ_CONTINUATION_MASK: 1 << 5 |
| +} |
| + |
| +/** |
| + * @constructor |
| + * @param {string} string |
| + */ |
| +SourceMap.StringCharIterator = function(string) |
| +{ |
| + this._string = string; |
| + this._position = 0; |
| +} |
| + |
| +SourceMap.StringCharIterator.prototype = { |
| + /** |
| + * @return {string} |
| + */ |
| + next: function() |
| + { |
| + return this._string.charAt(this._position++); |
| + }, |
| + |
| + /** |
| + * @return {string} |
| + */ |
| + peek: function() |
| + { |
| + return this._string.charAt(this._position); |
| + }, |
| + |
| + /** |
| + * @return {boolean} |
| + */ |
| + hasNext: function() |
| + { |
| + return this._position < this._string.length; |
| + } |
| +} |