Home | History | Annotate | Download | only in static
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * This class provides data access interface for dump file profiler.
      7  * @constructor
      8  */
      9 var Profiler = function(jsonData, template) {
     10   this.jsonData_ = jsonData;
     11   // Initialize template with templates information.
     12   this.template_ = template ||
     13       (jsonData.default_template &&
     14        jsonData.templates[jsonData.default_template]) ||
     15       jsonData.templates['l2'];
     16   // Initialize selected category, and nothing selected at first.
     17   this.selected_ = null;
     18 
     19   // Trigger event.
     20   this.callbacks_ = {};
     21 };
     22 
     23 /**
     24  * Mimic Eventemitter in node. Add new listener for event.
     25  * @param {string} event
     26  * @param {Function} callback
     27  */
     28 Profiler.prototype.addListener = function(event, callback) {
     29   if (!this.callbacks_[event])
     30     this.callbacks_[event] = $.Callbacks();
     31   this.callbacks_[event].add(callback);
     32 };
     33 
     34 /**
     35  * This function will emit the event.
     36  * @param {string} event
     37  */
     38 Profiler.prototype.emit = function(event) {
     39   // Listeners should be able to receive arbitrary number of parameters.
     40   var eventArguments = Array.prototype.slice.call(arguments, 1);
     41 
     42   if (this.callbacks_[event])
     43     this.callbacks_[event].fire.apply(this, eventArguments);
     44 };
     45 
     46 /**
     47  * Remove listener from event.
     48  * @param {string} event
     49  * @param {Function} callback
     50  */
     51 Profiler.prototype.removeListener = function(event, callback) {
     52   if (this.callbacks_[event])
     53     this.callbacks_[event].remove(callback);
     54 };
     55 
     56 /**
     57  * Calcualte initial models according default template.
     58  */
     59 Profiler.prototype.reparse = function() {
     60   this.models_ = this.parseTemplate_();
     61   this.emit('changed', this.models_);
     62 };
     63 
     64 /**
     65  * Get current breakdown template.
     66  * @return {Object} current breakdown template.
     67  */
     68 Profiler.prototype.getTemplate = function() {
     69   return this.template_;
     70 };
     71 
     72 /**
     73  * Get run_id of current profiler.
     74  * @return {string} run_id of current profiler.
     75  */
     76 Profiler.prototype.getRunId = function() {
     77   return this.jsonData_['run_id'];
     78 };
     79 
     80 /**
     81  * To be called by view when new model being selected.
     82  * And then triggers all relative views to update.
     83  * @param {string} id Model id.
     84  * @param {Object} pos Clicked position.
     85  */
     86 Profiler.prototype.setSelected = function(id, pos) {
     87   this.selected_ = id;
     88   this.emit('changed:selected', id, pos);
     89 };
     90 
     91 /**
     92  * Get all models throughout the whole timeline of given id.
     93  * @param {string} id Model id.
     94  * @return {Array.<Object>} model array of given id.
     95  */
     96 Profiler.prototype.getModelsbyId = function(id) {
     97   function find(model) {
     98     if (model.id === id)
     99       return model;
    100     if ('children' in model)
    101       return model.children.reduce(function(previous, current) {
    102         var matched = find(current);
    103         if (matched)
    104           previous = matched;
    105         return previous;
    106       }, null);
    107   }
    108 
    109   return this.models_.reduce(function(previous, current) {
    110     var matched = find(current);
    111     if (matched)
    112       previous.push(matched);
    113     return previous;
    114   }, []);
    115 };
    116 
    117 /**
    118  * Get current sub of given model, return undefined if sub dont exist.
    119  * @param {string} id Model id.
    120  * @return {undefined|string} world-breakdown like 'vm-map'.
    121  */
    122 Profiler.prototype.getCurSubById = function(id) {
    123   // Root won't has breakdown.
    124   var path = id.split(',').splice(1);
    125   if (!path.length) return null;
    126 
    127   var tmpl = this.template_;
    128   var curSub = path.reduce(function(previous, current, index) {
    129     return previous[2][current];
    130   }, tmpl);
    131 
    132   // return
    133   return curSub && curSub[0] + ',' + curSub[1];
    134 };
    135 
    136 /**
    137  * Generate and then reparse new template when new sub was selected.
    138  * @param {string|null} sub World-breakdown like 'vm-map'.
    139  */
    140 Profiler.prototype.setSub = function(sub) {
    141   var selected = this.selected_;
    142   var path = selected.split(',');
    143   var key = path[path.length - 1];
    144 
    145   // Add sub breakdown to template.
    146   var models = this.getModelsbyId(selected);
    147   var subTmpl = sub.split(',');
    148   subTmpl.push({});
    149   models[0].template[2][key] = subTmpl;
    150 
    151   // Recalculate new template.
    152   this.reparse();
    153 };
    154 
    155 /**
    156  * Remove children of figured node and reparse whole tree.
    157  * @param {string} id World-breakdown like 'vm-map'.
    158  */
    159 Profiler.prototype.unsetSub = function(id) {
    160   var models = this.getModelsbyId(id);
    161   if (!('template' in models[0]))
    162     return;
    163 
    164   var path = id.split(',');
    165   var key = path[path.length - 1];
    166   if (!(key in models[0].template[2]))
    167     return;
    168   delete (models[0].template[2][key]);
    169 
    170   // Recalculate new template.
    171   this.reparse();
    172 };
    173 
    174 /**
    175  * Calculate the model of certain snapshot.
    176  * @param {string} template Local template.
    177  * @param {Object} snapshot Current snapshot.
    178  * @param {Object} worldUnits Mapping of world units.
    179  * @param {Array.<number>} localUnits Array of local units.
    180  * @param {string} name Local node path.
    181  * @return {Object} Return model, total size and remaining units.
    182  * @private
    183  */
    184 Profiler.prototype.accumulate_ = function(
    185   template, snapshot, worldUnits, localUnits, name) {
    186   var self = this;
    187   var totalSize = 0;
    188   var worldName = template[0];
    189   var breakdownName = template[1];
    190   var categories = snapshot.worlds[worldName].breakdown[breakdownName];
    191   var matchedUnitsSet = {};
    192   var model = {
    193     name: name || worldName + '-' + breakdownName,
    194     time: snapshot.time,
    195     children: []
    196   };
    197 
    198   localUnits.sort(function(a, b) { return b - a; });
    199   Object.keys(categories).forEach(function(categoryName) {
    200     var category = categories[categoryName];
    201     if (category['hidden'] === true)
    202       return;
    203     category.units.sort(function(a, b) { return b - a; });
    204     // Filter units.
    205     var matchedUnits = intersectionOfSorted(category.units, localUnits);
    206     matchedUnits.forEach(function(unit) {
    207       matchedUnitsSet[unit] = unit;
    208     });
    209 
    210     // Accumulate categories.
    211     var size = matchedUnits.reduce(function(previous, current) {
    212       return previous + worldUnits[worldName][current];
    213     }, 0);
    214     totalSize += size;
    215 
    216     // Handle subs options if exists.
    217     var child = null;
    218     if (!(categoryName in template[2])) {
    219       // Calculate child for current category.
    220       child = {
    221         name: categoryName,
    222         size: size
    223       };
    224       if ('subs' in category && category.subs.length) {
    225         child.subs = category.subs;
    226         child.template = template;
    227       }
    228 
    229       model.children.push(child);
    230     } else {
    231       // Calculate child recursively.
    232       var subTemplate = template[2][categoryName];
    233       var subWorldName = subTemplate[0];
    234       var retVal = null;
    235 
    236       if (subWorldName === worldName) {
    237         // If subs is in the same world, units should be filtered.
    238         retVal = self.accumulate_(subTemplate, snapshot, worldUnits,
    239           matchedUnits, categoryName);
    240         if ('subs' in category && category.subs.length) {
    241           retVal.model.subs = category.subs;
    242           retVal.model.template = template;
    243         }
    244         model.children.push(retVal.model);
    245         // Don't output remaining item without any unit.
    246         if (!retVal.remainderUnits.length)
    247           return;
    248 
    249         // Sum up remaining units size.
    250         var remainSize =
    251           retVal.remainderUnits.reduce(function(previous, current) {
    252             return previous + worldUnits[subWorldName][current];
    253           }, 0);
    254 
    255         retVal.model.children.push({
    256           name: categoryName + '-remaining',
    257           size: remainSize
    258         });
    259       } else {
    260         // If subs is in different world, use all units in that world.
    261         var subLocalUnits = Object.keys(worldUnits[subWorldName]);
    262         subLocalUnits = subLocalUnits.map(function(unitID) {
    263           return parseInt(unitID, 10);
    264         });
    265 
    266         retVal = self.accumulate_(subTemplate, snapshot, worldUnits,
    267           subLocalUnits, categoryName);
    268         if ('subs' in category && category.subs.length) {
    269           retVal.model.subs = category.subs;
    270           retVal.model.template = template;
    271         }
    272         model.children.push(retVal.model);
    273 
    274         if (size > retVal.totalSize) {
    275           retVal.model.children.push({
    276             name: categoryName + '-remaining',
    277             size: size - retVal.totalSize
    278           });
    279         } else if (size < retVal.totalSize) {
    280           // Output WARNING when sub-breakdown size is larger.
    281           console.log('WARNING: size of sub-breakdown is larger');
    282         }
    283       }
    284     }
    285   });
    286 
    287   var remainderUnits = localUnits.reduce(function(previous, current) {
    288     if (!(current in matchedUnitsSet))
    289       previous.push(current);
    290     return previous;
    291   }, []);
    292 
    293   return {
    294     model: model,
    295     totalSize: totalSize,
    296     remainderUnits: remainderUnits
    297   };
    298 };
    299 
    300 /**
    301  * Parse template and calculate models of the whole timeline.
    302  * @return {Array.<Object>} Models of the whole timeline.
    303  * @private
    304  */
    305 Profiler.prototype.parseTemplate_ = function() {
    306   function calModelId(model, localPath) {
    307     // Create unique id for every model.
    308     model.id = localPath.length ?
    309       localPath.join() + ',' + model.name : model.name;
    310 
    311     if ('children' in model) {
    312       model.children.forEach(function(child, index) {
    313         var childPath = localPath.slice(0);
    314         childPath.push(model.name);
    315         calModelId(child, childPath);
    316       });
    317     }
    318   }
    319 
    320   var self = this;
    321 
    322   return self.jsonData_.snapshots.map(function(snapshot) {
    323     var worldUnits = {};
    324     for (var worldName in snapshot.worlds) {
    325       worldUnits[worldName] = {};
    326       var units = snapshot.worlds[worldName].units;
    327       for (var unitID in units)
    328         worldUnits[worldName][unitID] = units[unitID][0];
    329     }
    330     var localUnits = Object.keys(worldUnits[self.template_[0]]);
    331     localUnits = localUnits.map(function(unitID) {
    332       return parseInt(unitID, 10);
    333     });
    334 
    335     var retVal =
    336       self.accumulate_(self.template_, snapshot, worldUnits, localUnits);
    337     calModelId(retVal.model, []);
    338     return retVal.model;
    339   });
    340 };
    341