Home | History | Annotate | Download | only in js
      1 /*
      2  * Copyright (C) 2010 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @fileoverview Profiler processor is used to process log file produced
     33  * by V8 and produce an internal profile representation which is used
     34  * for building profile views in "Profiles" tab.
     35  */
     36 
     37 
     38 /**
     39  * Creates a Profile View builder object compatible with WebKit Profiler UI.
     40  *
     41  * @param {number} samplingRate Number of ms between profiler ticks.
     42  * @constructor
     43  */
     44 devtools.profiler.WebKitViewBuilder = function(samplingRate)
     45 {
     46     devtools.profiler.ViewBuilder.call(this, samplingRate);
     47 };
     48 devtools.profiler.WebKitViewBuilder.prototype.__proto__ = devtools.profiler.ViewBuilder.prototype;
     49 
     50 
     51 /**
     52  * @override
     53  */
     54 devtools.profiler.WebKitViewBuilder.prototype.createViewNode = function(funcName, totalTime, selfTime, head)
     55 {
     56     return new devtools.profiler.WebKitViewNode(funcName, totalTime, selfTime, head);
     57 };
     58 
     59 
     60 /**
     61  * Constructs a Profile View node object for displaying in WebKit Profiler UI.
     62  *
     63  * @param {string} internalFuncName A fully qualified function name.
     64  * @param {number} totalTime Amount of time that application spent in the
     65  *     corresponding function and its descendants (not that depending on
     66  *     profile they can be either callees or callers.)
     67  * @param {number} selfTime Amount of time that application spent in the
     68  *     corresponding function only.
     69  * @param {devtools.profiler.ProfileView.Node} head Profile view head.
     70  * @constructor
     71  */
     72 devtools.profiler.WebKitViewNode = function(internalFuncName, totalTime, selfTime, head)
     73 {
     74     devtools.profiler.ProfileView.Node.call(this, internalFuncName, totalTime, selfTime, head);
     75     this.initFuncInfo_();
     76     this.callUID = internalFuncName;
     77 };
     78 devtools.profiler.WebKitViewNode.prototype.__proto__ = devtools.profiler.ProfileView.Node.prototype;
     79 
     80 
     81 /**
     82  * RegEx for stripping V8's prefixes of compiled functions.
     83  */
     84 devtools.profiler.WebKitViewNode.FUNC_NAME_STRIP_RE = /^(?:LazyCompile|Function|Callback): (.*)$/;
     85 
     86 
     87 /**
     88  * RegEx for extracting script source URL and line number.
     89  */
     90 devtools.profiler.WebKitViewNode.FUNC_NAME_PARSE_RE = /^((?:get | set )?[^ ]+) (.*):(\d+)( \{\d+\})?$/;
     91 
     92 
     93 /**
     94  * Inits "functionName", "url", and "lineNumber" fields using "internalFuncName"
     95  * field.
     96  * @private
     97  */
     98 devtools.profiler.WebKitViewNode.prototype.initFuncInfo_ = function()
     99 {
    100     var nodeAlias = devtools.profiler.WebKitViewNode;
    101     this.functionName = this.internalFuncName;
    102 
    103     var strippedName = nodeAlias.FUNC_NAME_STRIP_RE.exec(this.functionName);
    104     if (strippedName)
    105         this.functionName = strippedName[1];
    106 
    107     var parsedName = nodeAlias.FUNC_NAME_PARSE_RE.exec(this.functionName);
    108     if (parsedName) {
    109         this.functionName = parsedName[1];
    110         if (parsedName[4])
    111             this.functionName += parsedName[4];
    112         this.url = parsedName[2];
    113         this.lineNumber = parsedName[3];
    114     } else {
    115         this.url = '';
    116         this.lineNumber = 0;
    117     }
    118 };
    119 
    120 
    121 /**
    122  * Ancestor of a profile object that leaves out only JS-related functions.
    123  * @constructor
    124  */
    125 devtools.profiler.JsProfile = function()
    126 {
    127     devtools.profiler.Profile.call(this);
    128 };
    129 devtools.profiler.JsProfile.prototype.__proto__ = devtools.profiler.Profile.prototype;
    130 
    131 
    132 /**
    133  * RegExp that leaves only non-native JS functions.
    134  * @type {RegExp}
    135  */
    136 devtools.profiler.JsProfile.JS_NON_NATIVE_RE = new RegExp(
    137       "^" +
    138         "(?:Callback:)|" +
    139         "(?:Script: (?!native))|" +
    140         "(?:(?:LazyCompile|Function): [^ ]*(?: (?!native )[^ ]+:\\d+)?$)");
    141 
    142 
    143 /**
    144  * @override
    145  */
    146 devtools.profiler.JsProfile.prototype.skipThisFunction = function(name)
    147 {
    148     return !devtools.profiler.JsProfile.JS_NON_NATIVE_RE.test(name);
    149 };
    150 
    151 
    152 /**
    153  * Profiler processor. Consumes profiler log and builds profile views.
    154  * FIXME: change field naming style to use trailing underscore.
    155  *
    156  * @param {function(devtools.profiler.ProfileView)} newProfileCallback Callback
    157  *     that receives a new processed profile.
    158  * @constructor
    159  */
    160 devtools.profiler.Processor = function()
    161 {
    162     var dispatches = {
    163         "code-creation": {
    164             parsers: [null, this.createAddressParser("code"), parseInt, null],
    165             processor: this.processCodeCreation_, backrefs: true,
    166             needsProfile: true },
    167         "code-move": { parsers: [this.createAddressParser("code"),
    168             this.createAddressParser("code-move-to")],
    169             processor: this.processCodeMove_, backrefs: true,
    170             needsProfile: true },
    171         "code-delete": { parsers: [this.createAddressParser("code")],
    172             processor: this.processCodeDelete_, backrefs: true,
    173             needsProfile: true },
    174         "function-creation": { parsers: [this.createAddressParser("code"),
    175             this.createAddressParser("function-obj")],
    176             processor: this.processFunctionCreation_, backrefs: true },
    177         "function-move": { parsers: [this.createAddressParser("code"),
    178             this.createAddressParser("code-move-to")],
    179             processor: this.processFunctionMove_, backrefs: true },
    180         "function-delete": { parsers: [this.createAddressParser("code")],
    181             processor: this.processFunctionDelete_, backrefs: true },
    182         "tick": { parsers: [this.createAddressParser("code"),
    183             this.createAddressParser("stack"), parseInt, "var-args"],
    184             processor: this.processTick_, backrefs: true, needProfile: true },
    185         "profiler": { parsers: [null, "var-args"],
    186             processor: this.processProfiler_, needsProfile: false },
    187         "heap-sample-begin": { parsers: [null, null, parseInt],
    188             processor: this.processHeapSampleBegin_ },
    189         "heap-sample-stats": { parsers: [null, null, parseInt, parseInt],
    190             processor: this.processHeapSampleStats_ },
    191         "heap-sample-item": { parsers: [null, parseInt, parseInt],
    192             processor: this.processHeapSampleItem_ },
    193         "heap-js-cons-item": { parsers: [null, parseInt, parseInt],
    194             processor: this.processHeapJsConsItem_ },
    195         "heap-js-ret-item": { parsers: [null, "var-args"],
    196             processor: this.processHeapJsRetItem_ },
    197         "heap-sample-end": { parsers: [null, null],
    198             processor: this.processHeapSampleEnd_ },
    199         // Not used in DevTools Profiler.
    200         "shared-library": null,
    201         // Obsolete row types.
    202         "code-allocate": null,
    203         "begin-code-region": null,
    204         "end-code-region": null};
    205 
    206     if (devtools.profiler.Profile.VERSION === 2) {
    207         dispatches["tick"] =  { parsers: [this.createAddressParser("code"),
    208             this.createAddressParser("stack"),
    209             this.createAddressParser("func"), parseInt, "var-args"],
    210             processor: this.processTickV2_, backrefs: true };
    211     }
    212 
    213     devtools.profiler.LogReader.call(this, dispatches);
    214 
    215     /**
    216      * Callback that is called when a new profile is encountered in the log.
    217      * @type {function()}
    218      */
    219     this.startedProfileProcessing_ = null;
    220 
    221     /**
    222      * Callback that is called periodically to display processing status.
    223      * @type {function()}
    224      */
    225     this.profileProcessingStatus_ = null;
    226 
    227     /**
    228      * Callback that is called when a profile has been processed and is ready
    229      * to be shown.
    230      * @type {function(devtools.profiler.ProfileView)}
    231      */
    232     this.finishedProfileProcessing_ = null;
    233 
    234     /**
    235      * The current profile.
    236      * @type {devtools.profiler.JsProfile}
    237      */
    238     this.currentProfile_ = null;
    239 
    240     /**
    241      * Builder of profile views. Created during "profiler,begin" event processing.
    242      * @type {devtools.profiler.WebKitViewBuilder}
    243      */
    244     this.viewBuilder_ = null;
    245 
    246     /**
    247      * Next profile id.
    248      * @type {number}
    249      */
    250     this.profileId_ = 1;
    251 
    252     /**
    253      * Counter for processed ticks.
    254      * @type {number}
    255      */
    256     this.ticksCount_ = 0;
    257 
    258     /**
    259      * Interval id for updating processing status.
    260      * @type {number}
    261      */
    262     this.processingInterval_ = null;
    263 
    264     /**
    265      * The current heap snapshot.
    266      * @type {string}
    267      */
    268     this.currentHeapSnapshot_ = null;
    269 
    270     /**
    271      * Next heap snapshot id.
    272      * @type {number}
    273      */
    274     this.heapSnapshotId_ = 1;
    275 };
    276 devtools.profiler.Processor.prototype.__proto__ = devtools.profiler.LogReader.prototype;
    277 
    278 
    279 /**
    280  * @override
    281  */
    282 devtools.profiler.Processor.prototype.printError = function(str)
    283 {
    284     debugPrint(str);
    285 };
    286 
    287 
    288 /**
    289  * @override
    290  */
    291 devtools.profiler.Processor.prototype.skipDispatch = function(dispatch)
    292 {
    293     return dispatch.needsProfile && this.currentProfile_ === null;
    294 };
    295 
    296 
    297 /**
    298  * Sets profile processing callbacks.
    299  *
    300  * @param {function()} started Started processing callback.
    301  * @param {function(devtools.profiler.ProfileView)} finished Finished
    302  *     processing callback.
    303  */
    304 devtools.profiler.Processor.prototype.setCallbacks = function(started, processing, finished)
    305 {
    306     this.startedProfileProcessing_ = started;
    307     this.profileProcessingStatus_ = processing;
    308     this.finishedProfileProcessing_ = finished;
    309 };
    310 
    311 
    312 /**
    313  * An address for the fake "(program)" entry. WebKit's visualisation
    314  * has assumptions on how the top of the call tree should look like,
    315  * and we need to add a fake entry as the topmost function. This
    316  * address is chosen because it's the end address of the first memory
    317  * page, which is never used for code or data, but only as a guard
    318  * page for catching AV errors.
    319  *
    320  * @type {number}
    321  */
    322 devtools.profiler.Processor.PROGRAM_ENTRY = 0xffff;
    323 /**
    324  * @type {string}
    325  */
    326 devtools.profiler.Processor.PROGRAM_ENTRY_STR = "0xffff";
    327 
    328 
    329 /**
    330  * Sets new profile callback.
    331  * @param {function(devtools.profiler.ProfileView)} callback Callback function.
    332  */
    333 devtools.profiler.Processor.prototype.setNewProfileCallback = function(callback)
    334 {
    335     this.newProfileCallback_ = callback;
    336 };
    337 
    338 
    339 devtools.profiler.Processor.prototype.processProfiler_ = function(state, params)
    340 {
    341     switch (state) {
    342         case "resume":
    343             if (this.currentProfile_ === null) {
    344                 this.currentProfile_ = new devtools.profiler.JsProfile();
    345                 // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
    346                 this.currentProfile_.addCode("Function", "(program)", devtools.profiler.Processor.PROGRAM_ENTRY, 1);
    347                 if (this.startedProfileProcessing_)
    348                     this.startedProfileProcessing_();
    349                 this.ticksCount_ = 0;
    350                 var self = this;
    351                 if (this.profileProcessingStatus_) {
    352                     this.processingInterval_ = window.setInterval(
    353                         function() { self.profileProcessingStatus_(self.ticksCount_); },
    354                         1000);
    355                 }
    356             }
    357             break;
    358         case "pause":
    359             if (this.currentProfile_ !== null) {
    360                 window.clearInterval(this.processingInterval_);
    361                 this.processingInterval_ = null;
    362                 if (this.finishedProfileProcessing_)
    363                     this.finishedProfileProcessing_(this.createProfileForView());
    364                 this.currentProfile_ = null;
    365             }
    366             break;
    367         case "begin":
    368             var samplingRate = NaN;
    369             if (params.length > 0)
    370                 samplingRate = parseInt(params[0]);
    371             if (isNaN(samplingRate))
    372                 samplingRate = 1;
    373             this.viewBuilder_ = new devtools.profiler.WebKitViewBuilder(samplingRate);
    374             break;
    375         // These events are valid but aren't used.
    376         case "compression":
    377         case "end": break;
    378         default:
    379             throw new Error("unknown profiler state: " + state);
    380     }
    381 };
    382 
    383 
    384 devtools.profiler.Processor.prototype.processCodeCreation_ = function(type, start, size, name)
    385 {
    386     this.currentProfile_.addCode(this.expandAlias(type), name, start, size);
    387 };
    388 
    389 
    390 devtools.profiler.Processor.prototype.processCodeMove_ = function(from, to)
    391 {
    392     this.currentProfile_.moveCode(from, to);
    393 };
    394 
    395 
    396 devtools.profiler.Processor.prototype.processCodeDelete_ = function(start)
    397 {
    398     this.currentProfile_.deleteCode(start);
    399 };
    400 
    401 
    402 devtools.profiler.Processor.prototype.processFunctionCreation_ = function(functionAddr, codeAddr)
    403 {
    404     this.currentProfile_.addCodeAlias(functionAddr, codeAddr);
    405 };
    406 
    407 
    408 devtools.profiler.Processor.prototype.processFunctionMove_ = function(from, to)
    409 {
    410     this.currentProfile_.safeMoveDynamicCode(from, to);
    411 };
    412 
    413 
    414 devtools.profiler.Processor.prototype.processFunctionDelete_ = function(start)
    415 {
    416     this.currentProfile_.safeDeleteDynamicCode(start);
    417 };
    418 
    419 
    420 // TODO(mnaganov): Remove after next V8 roll.
    421 devtools.profiler.Processor.prototype.processTick_ = function(pc, sp, vmState, stack)
    422 {
    423     // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
    424     stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR);
    425     this.currentProfile_.recordTick(this.processStack(pc, stack));
    426     this.ticksCount_++;
    427 };
    428 
    429 
    430 devtools.profiler.Processor.prototype.processTickV2_ = function(pc, sp, func, vmState, stack)
    431 {
    432     // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY
    433     stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR);
    434 
    435 
    436     if (func) {
    437         var funcEntry = this.currentProfile_.findEntry(func);
    438         if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction())
    439             func = 0;
    440         else {
    441             var currEntry = this.currentProfile_.findEntry(pc);
    442             if (!currEntry || !currEntry.isJSFunction || currEntry.isJSFunction()) {
    443                 func = 0;
    444             }
    445         }
    446     }
    447 
    448     this.currentProfile_.recordTick(this.processStack(pc, func, stack));
    449     this.ticksCount_++;
    450 };
    451 
    452 
    453 devtools.profiler.Processor.prototype.processHeapSampleBegin_ = function(space, state, ticks)
    454 {
    455     if (space !== "Heap") return;
    456     this.currentHeapSnapshot_ = {
    457         number: this.heapSnapshotId_++,
    458         entries: {},
    459         clusters: {},
    460         lowlevels: {},
    461         ticks: ticks
    462     };
    463 };
    464 
    465 
    466 devtools.profiler.Processor.prototype.processHeapSampleStats_ = function(space, state, capacity, used)
    467 {
    468     if (space !== "Heap") return;
    469 };
    470 
    471 
    472 devtools.profiler.Processor.prototype.processHeapSampleItem_ = function(item, number, size)
    473 {
    474     if (!this.currentHeapSnapshot_) return;
    475     this.currentHeapSnapshot_.lowlevels[item] = {
    476         type: item, count: number, size: size
    477     };
    478 };
    479 
    480 
    481 devtools.profiler.Processor.prototype.processHeapJsConsItem_ = function(item, number, size)
    482 {
    483     if (!this.currentHeapSnapshot_) return;
    484     this.currentHeapSnapshot_.entries[item] = {
    485         cons: item, count: number, size: size, retainers: {}
    486     };
    487 };
    488 
    489 
    490 devtools.profiler.Processor.prototype.processHeapJsRetItem_ = function(item, retainersArray)
    491 {
    492     if (!this.currentHeapSnapshot_) return;
    493     var rawRetainers = {};
    494     for (var i = 0, n = retainersArray.length; i < n; ++i) {
    495         var entry = retainersArray[i].split(";");
    496         rawRetainers[entry[0]] = parseInt(entry[1], 10);
    497     }
    498 
    499     function mergeRetainers(entry) {
    500         for (var rawRetainer in rawRetainers) {
    501             var consName = rawRetainer.indexOf(":") !== -1 ? rawRetainer.split(":")[0] : rawRetainer;
    502             if (!(consName in entry.retainers))
    503                 entry.retainers[consName] = { cons: consName, count: 0, clusters: {} };
    504             var retainer = entry.retainers[consName];
    505             retainer.count += rawRetainers[rawRetainer];
    506             if (consName !== rawRetainer)
    507                 retainer.clusters[rawRetainer] = true;
    508         }
    509     }
    510 
    511     if (item.indexOf(":") !== -1) {
    512       // Array, Function, or Object instances cluster case.
    513       if (!(item in this.currentHeapSnapshot_.clusters)) {
    514           this.currentHeapSnapshot_.clusters[item] = {
    515               cons: item, retainers: {}
    516           };
    517       }
    518       mergeRetainers(this.currentHeapSnapshot_.clusters[item]);
    519       item = item.split(":")[0];
    520     }
    521     mergeRetainers(this.currentHeapSnapshot_.entries[item]);
    522 };
    523 
    524 
    525 devtools.profiler.Processor.prototype.processHeapSampleEnd_ = function(space, state)
    526 {
    527     if (space !== "Heap") return;
    528     var snapshot = this.currentHeapSnapshot_;
    529     this.currentHeapSnapshot_ = null;
    530     WebInspector.panels.profiles.addSnapshot(snapshot);
    531 };
    532 
    533 
    534 /**
    535  * Creates a profile for further displaying in ProfileView.
    536  */
    537 devtools.profiler.Processor.prototype.createProfileForView = function()
    538 {
    539     var profile = this.viewBuilder_.buildView(this.currentProfile_.getTopDownProfile());
    540     profile.uid = this.profileId_++;
    541     profile.title = UserInitiatedProfileName + "." + profile.uid;
    542     return profile;
    543 };
    544