WebKit/chromium/src/js/ProfilerProcessor.js
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     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 };