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.
6 * This class provides data access interface for dump file profiler.
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;
24 * Mimic Eventemitter in node. Add new listener for event.
25 * @param {string} event
26 * @param {Function} callback
28 Profiler.prototype.addListener = function(event, callback) {
29 if (!this.callbacks_[event])
30 this.callbacks_[event] = $.Callbacks();
31 this.callbacks_[event].add(callback);
35 * This function will emit the event.
36 * @param {string} event
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);
42 if (this.callbacks_[event])
43 this.callbacks_[event].fire.apply(this, eventArguments);
47 * Remove listener from event.
48 * @param {string} event
49 * @param {Function} callback
51 Profiler.prototype.removeListener = function(event, callback) {
52 if (this.callbacks_[event])
53 this.callbacks_[event].remove(callback);
57 * Calcualte initial models according default template.
59 Profiler.prototype.reparse = function() {
60 this.models_ = this.parseTemplate_();
61 this.emit('changed', this.models_);
65 * Get current breakdown template.
66 * @return {Object} current breakdown template.
68 Profiler.prototype.getTemplate = function() {
69 return this.template_;
73 * Get run_id of current profiler.
74 * @return {string} run_id of current profiler.
76 Profiler.prototype.getRunId = function() {
77 return this.jsonData_['run_id'];
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.
86 Profiler.prototype.setSelected = function(id, pos) {
88 this.emit('changed:selected', id, pos);
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.
96 Profiler.prototype.getModelsbyId = function(id) {
97 function find(model) {
100 if ('children' in model)
101 return model.children.reduce(function(previous, current) {
102 var matched = find(current);
109 return this.models_.reduce(function(previous, current) {
110 var matched = find(current);
112 previous.push(matched);
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'.
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;
127 var tmpl = this.template_;
128 var curSub = path.reduce(function(previous, current, index) {
129 return previous[2][current];
133 return curSub && curSub[0] + ',' + curSub[1];
137 * Generate and then reparse new template when new sub was selected.
138 * @param {string|null} sub World-breakdown like 'vm-map'.
140 Profiler.prototype.setSub = function(sub) {
141 var selected = this.selected_;
142 var path = selected.split(',');
143 var key = path[path.length - 1];
145 // Add sub breakdown to template.
146 var models = this.getModelsbyId(selected);
147 var subTmpl = sub.split(',');
149 models[0].template[2][key] = subTmpl;
151 // Recalculate new template.
156 * Remove children of figured node and reparse whole tree.
157 * @param {string} id World-breakdown like 'vm-map'.
159 Profiler.prototype.unsetSub = function(id) {
160 var models = this.getModelsbyId(id);
161 if (!('template' in models[0]))
164 var path = id.split(',');
165 var key = path[path.length - 1];
166 if (!(key in models[0].template[2]))
168 delete (models[0].template[2][key]);
170 // Recalculate new template.
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.
184 Profiler.prototype.accumulate_ = function(
185 template, snapshot, worldUnits, localUnits, name) {
188 var worldName = template[0];
189 var breakdownName = template[1];
190 var categories = snapshot.worlds[worldName].breakdown[breakdownName];
191 var matchedUnitsSet = {};
193 name: name || worldName + '-' + breakdownName,
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)
203 category.units.sort(function(a, b) { return b - a; });
205 var matchedUnits = intersectionOfSorted(category.units, localUnits);
206 matchedUnits.forEach(function(unit) {
207 matchedUnitsSet[unit] = unit;
210 // Accumulate categories.
211 var size = matchedUnits.reduce(function(previous, current) {
212 return previous + worldUnits[worldName][current];
216 // Handle subs options if exists.
218 if (!(categoryName in template[2])) {
219 // Calculate child for current category.
224 if ('subs' in category && category.subs.length) {
225 child.subs = category.subs;
226 child.template = template;
229 model.children.push(child);
231 // Calculate child recursively.
232 var subTemplate = template[2][categoryName];
233 var subWorldName = subTemplate[0];
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;
244 model.children.push(retVal.model);
245 // Don't output remaining item without any unit.
246 if (!retVal.remainderUnits.length)
249 // Sum up remaining units size.
251 retVal.remainderUnits.reduce(function(previous, current) {
252 return previous + worldUnits[subWorldName][current];
255 retVal.model.children.push({
256 name: categoryName + '-remaining',
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);
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;
272 model.children.push(retVal.model);
274 if (size > retVal.totalSize) {
275 retVal.model.children.push({
276 name: categoryName + '-remaining',
277 size: size - retVal.totalSize
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');
287 var remainderUnits = localUnits.reduce(function(previous, current) {
288 if (!(current in matchedUnitsSet))
289 previous.push(current);
295 totalSize: totalSize,
296 remainderUnits: remainderUnits
301 * Parse template and calculate models of the whole timeline.
302 * @return {Array.<Object>} Models of the whole timeline.
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;
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);
322 return self.jsonData_.snapshots.map(function(snapshot) {
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];
330 var localUnits = Object.keys(worldUnits[self.template_[0]]);
331 localUnits = localUnits.map(function(unitID) {
332 return parseInt(unitID, 10);
336 self.accumulate_(self.template_, snapshot, worldUnits, localUnits);
337 calModelId(retVal.model, []);