De-dup PrefixSet code in SafeBrowsingDatabaseManager.
[chromium-blink-merge.git] / tools / deep_memory_profiler / visualizer / static / profiler.js
blobf76dbd20bc35bd10766ccf914824d5585079f1c3
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.
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;
19   // Trigger event.
20   this.callbacks_ = {};
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);
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);
42   if (this.callbacks_[event])
43     this.callbacks_[event].fire.apply(this, eventArguments);
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);
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_);
64 /**
65  * Get current breakdown template.
66  * @return {Object} current breakdown template.
67  */
68 Profiler.prototype.getTemplate = function() {
69   return this.template_;
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'];
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);
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   }
109   return this.models_.reduce(function(previous, current) {
110     var matched = find(current);
111     if (matched)
112       previous.push(matched);
113     return previous;
114   }, []);
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;
127   var tmpl = this.template_;
128   var curSub = path.reduce(function(previous, current, index) {
129     return previous[2][current];
130   }, tmpl);
132   // return
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'.
139  */
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(',');
148   subTmpl.push({});
149   models[0].template[2][key] = subTmpl;
151   // Recalculate new template.
152   this.reparse();
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;
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]);
170   // Recalculate new template.
171   this.reparse();
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   };
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     });
210     // Accumulate categories.
211     var size = matchedUnits.reduce(function(previous, current) {
212       return previous + worldUnits[worldName][current];
213     }, 0);
214     totalSize += size;
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       }
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;
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;
249         // Sum up remaining units size.
250         var remainSize =
251           retVal.remainderUnits.reduce(function(previous, current) {
252             return previous + worldUnits[subWorldName][current];
253           }, 0);
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         });
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);
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   });
287   var remainderUnits = localUnits.reduce(function(previous, current) {
288     if (!(current in matchedUnitsSet))
289       previous.push(current);
290     return previous;
291   }, []);
293   return {
294     model: model,
295     totalSize: totalSize,
296     remainderUnits: remainderUnits
297   };
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;
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   }
320   var self = this;
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     });
335     var retVal =
336       self.accumulate_(self.template_, snapshot, worldUnits, localUnits);
337     calModelId(retVal.model, []);
338     return retVal.model;
339   });