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