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 |