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
, []);