OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview Profiler processor is used to process log file produced | 6 * @fileoverview Profiler processor is used to process log file produced |
7 * by V8 and produce an internal profile representation which is used | 7 * by V8 and produce an internal profile representation which is used |
8 * for building profile views in 'Profiles' tab. | 8 * for building profile views in 'Profiles' tab. |
9 */ | 9 */ |
10 goog.provide('devtools.profiler.Processor'); | 10 goog.provide('devtools.profiler.Processor'); |
11 | 11 |
12 | 12 |
13 /** | 13 /** |
| 14 * Creates a Profile View builder object compatible with WebKit Profiler UI. |
| 15 * |
| 16 * @param {number} samplingRate Number of ms between profiler ticks. |
| 17 * @constructor |
| 18 */ |
| 19 devtools.profiler.WebKitViewBuilder = function(samplingRate) { |
| 20 devtools.profiler.ViewBuilder.call(this, samplingRate); |
| 21 }; |
| 22 goog.inherits(devtools.profiler.WebKitViewBuilder, |
| 23 devtools.profiler.ViewBuilder); |
| 24 |
| 25 |
| 26 /** |
| 27 * @override |
| 28 */ |
| 29 devtools.profiler.WebKitViewBuilder.prototype.createViewNode = function( |
| 30 funcName, totalTime, selfTime, head) { |
| 31 return new devtools.profiler.WebKitViewNode( |
| 32 funcName, totalTime, selfTime, head); |
| 33 }; |
| 34 |
| 35 |
| 36 /** |
| 37 * Constructs a Profile View node object for displaying in WebKit Profiler UI. |
| 38 * |
| 39 * @param {string} internalFuncName A fully qualified function name. |
| 40 * @param {number} totalTime Amount of time that application spent in the |
| 41 * corresponding function and its descendants (not that depending on |
| 42 * profile they can be either callees or callers.) |
| 43 * @param {number} selfTime Amount of time that application spent in the |
| 44 * corresponding function only. |
| 45 * @param {devtools.profiler.ProfileView.Node} head Profile view head. |
| 46 * @constructor |
| 47 */ |
| 48 devtools.profiler.WebKitViewNode = function( |
| 49 internalFuncName, totalTime, selfTime, head) { |
| 50 devtools.profiler.ProfileView.Node.call(this, |
| 51 internalFuncName, totalTime, selfTime, head); |
| 52 this.initFuncInfo_(); |
| 53 this.callUID = internalFuncName; |
| 54 }; |
| 55 goog.inherits(devtools.profiler.WebKitViewNode, |
| 56 devtools.profiler.ProfileView.Node); |
| 57 |
| 58 |
| 59 /** |
| 60 * RegEx for stripping V8's prefixes of compiled functions. |
| 61 */ |
| 62 devtools.profiler.WebKitViewNode.FUNC_NAME_STRIP_RE = |
| 63 /^(?:LazyCompile|Function): (.*)$/; |
| 64 |
| 65 |
| 66 /** |
| 67 * RegEx for extracting script source URL and line number. |
| 68 */ |
| 69 devtools.profiler.WebKitViewNode.FUNC_NAME_PARSE_RE = |
| 70 /^([^ ]+) (.*):(\d+)( \{\d+\})?$/; |
| 71 |
| 72 |
| 73 /** |
| 74 * Inits 'functionName', 'url', and 'lineNumber' fields using 'internalFuncName' |
| 75 * field. |
| 76 * @private |
| 77 */ |
| 78 devtools.profiler.WebKitViewNode.prototype.initFuncInfo_ = function() { |
| 79 var nodeAlias = devtools.profiler.WebKitViewNode; |
| 80 this.functionName = this.internalFuncName; |
| 81 |
| 82 var strippedName = nodeAlias.FUNC_NAME_STRIP_RE.exec(this.functionName); |
| 83 if (strippedName) { |
| 84 this.functionName = strippedName[1]; |
| 85 } |
| 86 |
| 87 var parsedName = nodeAlias.FUNC_NAME_PARSE_RE.exec(this.functionName); |
| 88 if (parsedName) { |
| 89 this.functionName = parsedName[1]; |
| 90 if (parsedName[4]) { |
| 91 this.functionName += parsedName[4]; |
| 92 } |
| 93 this.url = parsedName[2]; |
| 94 this.lineNumber = parsedName[3]; |
| 95 } else { |
| 96 this.url = ''; |
| 97 this.lineNumber = 0; |
| 98 } |
| 99 }; |
| 100 |
| 101 |
| 102 /** |
14 * Ancestor of a profile object that leaves out only JS-related functions. | 103 * Ancestor of a profile object that leaves out only JS-related functions. |
15 * @constructor | 104 * @constructor |
16 */ | 105 */ |
17 devtools.profiler.JsProfile = function() { | 106 devtools.profiler.JsProfile = function() { |
18 devtools.profiler.Profile.call(this); | 107 devtools.profiler.Profile.call(this); |
19 }; | 108 }; |
20 goog.inherits(devtools.profiler.JsProfile, devtools.profiler.Profile); | 109 goog.inherits(devtools.profiler.JsProfile, devtools.profiler.Profile); |
21 | 110 |
22 | 111 |
23 /** | 112 /** |
| 113 * RegExp that leaves only JS functions. |
24 * @type {RegExp} | 114 * @type {RegExp} |
25 */ | 115 */ |
26 devtools.profiler.JsProfile.JS_FUNC_RE = /^(LazyCompile|Function|Script):/; | 116 devtools.profiler.JsProfile.JS_FUNC_RE = /^(LazyCompile|Function|Script):/; |
27 | 117 |
| 118 /** |
| 119 * RegExp that filters out native code (ending with "native src.js:xxx"). |
| 120 * @type {RegExp} |
| 121 */ |
| 122 devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE = /\ native\ \w+\.js:\d+$/; |
| 123 |
| 124 /** |
| 125 * RegExp that filters out native scripts. |
| 126 * @type {RegExp} |
| 127 */ |
| 128 devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE = /^Script:\ native/; |
| 129 |
| 130 /** |
| 131 * RegExp that filters out devtools functions. See inject.js and |
| 132 * inject_dispatch.js. |
| 133 * @type {RegExp} |
| 134 */ |
| 135 devtools.profiler.JsProfile.JS_DEVTOOLS_FUNC_RE = |
| 136 /^\w+:\ devtools(\$\$|\.Injected)/; |
| 137 |
28 | 138 |
29 /** | 139 /** |
30 * @override | 140 * @override |
31 */ | 141 */ |
32 devtools.profiler.JsProfile.prototype.skipThisFunction = function(name) { | 142 devtools.profiler.JsProfile.prototype.skipThisFunction = function(name) { |
33 return !devtools.profiler.JsProfile.JS_FUNC_RE.test(name); | 143 return !devtools.profiler.JsProfile.JS_FUNC_RE.test(name) || |
| 144 // To profile V8's natives comment out two lines below and '||' above. |
| 145 devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE.test(name) || |
| 146 devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE.test(name) || |
| 147 devtools.profiler.JsProfile.JS_DEVTOOLS_FUNC_RE.test(name); |
34 }; | 148 }; |
35 | 149 |
36 | 150 |
37 /** | 151 /** |
38 * Profiler processor. Consumes profiler log and builds profile views. | 152 * Profiler processor. Consumes profiler log and builds profile views. |
| 153 * |
| 154 * @param {function(devtools.profiler.ProfileView)} newProfileCallback Callback |
| 155 * that receives a new processed profile. |
39 * @constructor | 156 * @constructor |
40 */ | 157 */ |
41 devtools.profiler.Processor = function() { | 158 devtools.profiler.Processor = function() { |
42 /** | 159 devtools.profiler.LogReader.call(this, { |
43 * Current profile. | 160 'code-creation': { |
| 161 parsers: [null, this.createAddressParser('code'), parseInt, null], |
| 162 processor: this.processCodeCreation_, backrefs: true, |
| 163 needsProfile: true }, |
| 164 'code-move': { parsers: [this.createAddressParser('code'), |
| 165 this.createAddressParser('code-move-to')], |
| 166 processor: this.processCodeMove_, backrefs: true, |
| 167 needsProfile: true }, |
| 168 'code-delete': { parsers: [this.createAddressParser('code')], |
| 169 processor: this.processCodeDelete_, backrefs: true, |
| 170 needsProfile: true }, |
| 171 'tick': { parsers: [this.createAddressParser('code'), |
| 172 this.createAddressParser('stack'), parseInt, 'var-args'], |
| 173 processor: this.processTick_, backrefs: true, needProfile: true }, |
| 174 'profiler': { parsers: [null, 'var-args'], |
| 175 processor: this.processProfiler_, needsProfile: false }, |
| 176 'heap-sample-begin': { parsers: [null, null, parseInt], |
| 177 processor: this.processHeapSampleBegin_ }, |
| 178 'heap-sample-stats': { parsers: [null, null, parseInt, parseInt], |
| 179 processor: this.processHeapSampleStats_ }, |
| 180 'heap-sample-item': { parsers: [null, parseInt, parseInt], |
| 181 processor: this.processHeapSampleItem_ }, |
| 182 'heap-js-cons-item': { parsers: [null, parseInt, parseInt], |
| 183 processor: this.processHeapJsConsItem_ }, |
| 184 'heap-sample-end': { parsers: [null, null], |
| 185 processor: this.processHeapSampleEnd_ }, |
| 186 // Not used in DevTools Profiler. |
| 187 'shared-library': null, |
| 188 // Obsolete row types. |
| 189 'code-allocate': null, |
| 190 'begin-code-region': null, |
| 191 'end-code-region': null}); |
| 192 |
| 193 |
| 194 /** |
| 195 * Callback that is called when a new profile is encountered in the log. |
| 196 * @type {function()} |
| 197 */ |
| 198 this.startedProfileProcessing_ = null; |
| 199 |
| 200 /** |
| 201 * Callback that is called periodically to display processing status. |
| 202 * @type {function()} |
| 203 */ |
| 204 this.profileProcessingStatus_ = null; |
| 205 |
| 206 /** |
| 207 * Callback that is called when a profile has been processed and is ready |
| 208 * to be shown. |
| 209 * @type {function(devtools.profiler.ProfileView)} |
| 210 */ |
| 211 this.finishedProfileProcessing_ = null; |
| 212 |
| 213 /** |
| 214 * The current profile. |
44 * @type {devtools.profiler.JsProfile} | 215 * @type {devtools.profiler.JsProfile} |
45 */ | 216 */ |
46 this.profile_ = new devtools.profiler.JsProfile(); | 217 this.currentProfile_ = null; |
47 | 218 |
48 /** | 219 /** |
49 * Builder of profile views. | 220 * Builder of profile views. Created during "profiler,begin" event processing. |
50 * @type {devtools.profiler.ViewBuilder} | 221 * @type {devtools.profiler.WebKitViewBuilder} |
51 */ | 222 */ |
52 this.viewBuilder_ = new devtools.profiler.ViewBuilder(1); | 223 this.viewBuilder_ = null; |
53 | 224 |
54 /** | 225 /** |
55 * Next profile id. | 226 * Next profile id. |
56 * @type {number} | 227 * @type {number} |
57 */ | 228 */ |
58 this.profileId_ = 1; | 229 this.profileId_ = 1; |
59 }; | 230 |
60 | 231 /** |
61 | 232 * Counter for processed ticks. |
62 /** | 233 * @type {number} |
63 * A dispatch table for V8 profiler event log records. | 234 */ |
64 * @private | 235 this.ticksCount_ = 0; |
65 */ | 236 |
66 devtools.profiler.Processor.RecordsDispatch_ = { | 237 /** |
67 'code-creation': { parsers: [null, parseInt, parseInt, null], | 238 * The current heap snapshot. |
68 processor: 'processCodeCreation_' }, | 239 * @type {string} |
69 'code-move': { parsers: [parseInt, parseInt], | 240 */ |
70 processor: 'processCodeMove_' }, | 241 this.currentHeapSnapshot_ = null; |
71 'code-delete': { parsers: [parseInt], processor: 'processCodeDelete_' }, | 242 |
72 'tick': { parsers: [parseInt, parseInt, parseInt, 'var-args'], | 243 /** |
73 processor: 'processTick_' }, | 244 * Next heap snapshot id. |
74 // Not used in DevTools Profiler. | 245 * @type {number} |
75 'profiler': null, | 246 */ |
76 'shared-library': null, | 247 this.heapSnapshotId_ = 1; |
77 // Obsolete row types. | 248 }; |
78 'code-allocate': null, | 249 goog.inherits(devtools.profiler.Processor, devtools.profiler.LogReader); |
79 'begin-code-region': null, | 250 |
80 'end-code-region': null | 251 |
81 }; | 252 /** |
82 | 253 * @override |
83 | 254 */ |
84 /** | 255 devtools.profiler.Processor.prototype.printError = function(str) { |
85 * Processes a portion of V8 profiler event log. | 256 debugPrint(str); |
| 257 }; |
| 258 |
| 259 |
| 260 /** |
| 261 * @override |
| 262 */ |
| 263 devtools.profiler.Processor.prototype.skipDispatch = function(dispatch) { |
| 264 return dispatch.needsProfile && this.currentProfile_ == null; |
| 265 }; |
| 266 |
| 267 |
| 268 /** |
| 269 * Sets profile processing callbacks. |
86 * | 270 * |
87 * @param {string} chunk A portion of log. | 271 * @param {function()} started Started processing callback. |
88 */ | 272 * @param {function(devtools.profiler.ProfileView)} finished Finished |
89 devtools.profiler.Processor.prototype.processLogChunk = function(chunk) { | 273 * processing callback. |
90 this.processLog_(chunk.split('\n')); | 274 */ |
91 }; | 275 devtools.profiler.Processor.prototype.setCallbacks = function( |
92 | 276 started, processing, finished) { |
93 | 277 this.startedProfileProcessing_ = started; |
94 /** | 278 this.profileProcessingStatus_ = processing; |
95 * Processes a log lines. | 279 this.finishedProfileProcessing_ = finished; |
| 280 }; |
| 281 |
| 282 |
| 283 /** |
| 284 * An address for the fake "(program)" entry. WebKit's visualisation |
| 285 * has assumptions on how the top of the call tree should look like, |
| 286 * and we need to add a fake entry as the topmost function. This |
| 287 * address is chosen because it's the end address of the first memory |
| 288 * page, which is never used for code or data, but only as a guard |
| 289 * page for catching AV errors. |
96 * | 290 * |
97 * @param {Array<string>} lines Log lines. | 291 * @type {number} |
98 * @private | 292 */ |
99 */ | 293 devtools.profiler.Processor.PROGRAM_ENTRY = 0xffff; |
100 devtools.profiler.Processor.prototype.processLog_ = function(lines) { | 294 /** |
101 var csvParser = new devtools.profiler.CsvParser(); | 295 * @type {string} |
102 try { | 296 */ |
103 for (var i = 0, n = lines.length; i < n; ++i) { | 297 devtools.profiler.Processor.PROGRAM_ENTRY_STR = '0xffff'; |
104 var line = lines[i]; | 298 |
105 if (!line) { | 299 |
106 continue; | 300 /** |
| 301 * Sets new profile callback. |
| 302 * @param {function(devtools.profiler.ProfileView)} callback Callback function. |
| 303 */ |
| 304 devtools.profiler.Processor.prototype.setNewProfileCallback = function( |
| 305 callback) { |
| 306 this.newProfileCallback_ = callback; |
| 307 }; |
| 308 |
| 309 |
| 310 devtools.profiler.Processor.prototype.processProfiler_ = function( |
| 311 state, params) { |
| 312 var processingInterval = null; |
| 313 switch (state) { |
| 314 case 'resume': |
| 315 if (this.currentProfile_ == null) { |
| 316 this.currentProfile_ = new devtools.profiler.JsProfile(); |
| 317 // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY |
| 318 this.currentProfile_.addCode( |
| 319 'Function', '(program)', |
| 320 devtools.profiler.Processor.PROGRAM_ENTRY, 1); |
| 321 if (this.startedProfileProcessing_) { |
| 322 this.startedProfileProcessing_(); |
| 323 } |
| 324 this.ticksCount_ = 0; |
| 325 var self = this; |
| 326 if (this.profileProcessingStatus_) { |
| 327 processingInterval = window.setInterval( |
| 328 function() { self.profileProcessingStatus_(self.ticksCount_); }, |
| 329 1000); |
| 330 } |
107 } | 331 } |
108 var fields = csvParser.parseLine(line); | 332 break; |
109 this.dispatchLogRow_(fields); | 333 case 'pause': |
110 } | 334 if (this.currentProfile_ != null) { |
111 } catch (e) { | 335 window.clearInterval(processingInterval); |
112 debugPrint('line ' + (i + 1) + ': ' + (e.message || e)); | 336 if (this.finishedProfileProcessing_) { |
113 throw e; | 337 this.finishedProfileProcessing_(this.createProfileForView()); |
| 338 } |
| 339 this.currentProfile_ = null; |
| 340 } |
| 341 break; |
| 342 case 'begin': |
| 343 var samplingRate = NaN; |
| 344 if (params.length > 0) { |
| 345 samplingRate = parseInt(params[0]); |
| 346 } |
| 347 if (isNaN(samplingRate)) { |
| 348 samplingRate = 1; |
| 349 } |
| 350 this.viewBuilder_ = new devtools.profiler.WebKitViewBuilder(samplingRate); |
| 351 break; |
| 352 // These events are valid but aren't used. |
| 353 case 'compression': |
| 354 case 'end': break; |
| 355 default: |
| 356 throw new Error('unknown profiler state: ' + state); |
114 } | 357 } |
115 }; | 358 }; |
116 | 359 |
117 | 360 |
118 /** | |
119 * Does a dispatch of a log record. | |
120 * | |
121 * @param {Array<string>} fields Log record. | |
122 * @private | |
123 */ | |
124 devtools.profiler.Processor.prototype.dispatchLogRow_ = function(fields) { | |
125 // Obtain the dispatch. | |
126 var command = fields[0]; | |
127 if (!(command in devtools.profiler.Processor.RecordsDispatch_)) { | |
128 throw new Error('unknown command: ' + command); | |
129 } | |
130 var dispatch = devtools.profiler.Processor.RecordsDispatch_[command]; | |
131 | |
132 if (dispatch === null) { | |
133 return; | |
134 } | |
135 | |
136 // Parse fields. | |
137 var parsedFields = []; | |
138 for (var i = 0; i < dispatch.parsers.length; ++i) { | |
139 var parser = dispatch.parsers[i]; | |
140 if (parser === null) { | |
141 parsedFields.push(fields[1 + i]); | |
142 } else if (typeof parser == 'function') { | |
143 parsedFields.push(parser(fields[1 + i])); | |
144 } else { | |
145 // var-args | |
146 parsedFields.push(fields.slice(1 + i)); | |
147 break; | |
148 } | |
149 } | |
150 | |
151 // Run the processor. | |
152 this[dispatch.processor].apply(this, parsedFields); | |
153 }; | |
154 | |
155 | |
156 devtools.profiler.Processor.prototype.processCodeCreation_ = function( | 361 devtools.profiler.Processor.prototype.processCodeCreation_ = function( |
157 type, start, size, name) { | 362 type, start, size, name) { |
158 this.profile_.addCode(type, name, start, size); | 363 this.currentProfile_.addCode(this.expandAlias(type), name, start, size); |
159 }; | 364 }; |
160 | 365 |
161 | 366 |
162 devtools.profiler.Processor.prototype.processCodeMove_ = function(from, to) { | 367 devtools.profiler.Processor.prototype.processCodeMove_ = function(from, to) { |
163 this.profile_.moveCode(from, to); | 368 this.currentProfile_.moveCode(from, to); |
164 }; | 369 }; |
165 | 370 |
166 | 371 |
167 devtools.profiler.Processor.prototype.processCodeDelete_ = function(start) { | 372 devtools.profiler.Processor.prototype.processCodeDelete_ = function(start) { |
168 this.profile_.deleteCode(start); | 373 this.currentProfile_.deleteCode(start); |
169 }; | 374 }; |
170 | 375 |
171 | 376 |
172 devtools.profiler.Processor.prototype.processTick_ = function( | 377 devtools.profiler.Processor.prototype.processTick_ = function( |
173 pc, sp, vmState, stack) { | 378 pc, sp, vmState, stack) { |
174 var fullStack = [pc]; | 379 // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY |
175 for (var i = 0, n = stack.length; i < n; ++i) { | 380 stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR); |
176 var frame = stack[i]; | 381 this.currentProfile_.recordTick(this.processStack(pc, stack)); |
177 // Leave only numbers starting with 0x. Filter possible 'overflow' string. | 382 this.ticksCount_++; |
178 if (frame.charAt(0) == '0') { | 383 }; |
179 fullStack.push(parseInt(frame, 16)); | 384 |
180 } | 385 |
| 386 devtools.profiler.Processor.prototype.processHeapSampleBegin_ = function( |
| 387 space, state, ticks) { |
| 388 if (space != 'Heap') return; |
| 389 this.currentHeapSnapshot_ = { |
| 390 number: this.heapSnapshotId_++, |
| 391 entries: {}, |
| 392 lowlevels: {}, |
| 393 ticks: ticks |
| 394 }; |
| 395 }; |
| 396 |
| 397 |
| 398 devtools.profiler.Processor.prototype.processHeapSampleStats_ = function( |
| 399 space, state, capacity, used) { |
| 400 if (space != 'Heap') return; |
| 401 this.currentHeapSnapshot_.capacity = capacity; |
| 402 this.currentHeapSnapshot_.used = used; |
| 403 }; |
| 404 |
| 405 |
| 406 devtools.profiler.Processor.prototype.processHeapSampleItem_ = function( |
| 407 item, number, size) { |
| 408 if (!this.currentHeapSnapshot_) return; |
| 409 this.currentHeapSnapshot_.lowlevels[item] = { |
| 410 type: item, count: number, size: size |
| 411 }; |
| 412 }; |
| 413 |
| 414 |
| 415 devtools.profiler.Processor.prototype.processHeapJsConsItem_ = function( |
| 416 item, number, size) { |
| 417 if (!this.currentHeapSnapshot_) return; |
| 418 this.currentHeapSnapshot_.entries[item] = { |
| 419 cons: item, count: number, size: size |
| 420 }; |
| 421 }; |
| 422 |
| 423 |
| 424 devtools.profiler.Processor.prototype.processHeapSampleEnd_ = function( |
| 425 space, state) { |
| 426 if (space != 'Heap') return; |
| 427 var snapshot = this.currentHeapSnapshot_; |
| 428 this.currentHeapSnapshot_ = null; |
| 429 // For some reason, 'used' from 'heap-sample-stats' sometimes differ from |
| 430 // the sum of objects sizes. To avoid discrepancy, we re-calculate 'used'. |
| 431 snapshot.used = 0; |
| 432 for (var item in snapshot.lowlevels) { |
| 433 snapshot.used += snapshot.lowlevels[item].size; |
181 } | 434 } |
182 this.profile_.recordTick(fullStack); | 435 WebInspector.panels.heap.addSnapshot(snapshot); |
183 }; | 436 }; |
184 | 437 |
185 | 438 |
186 /** | 439 /** |
187 * Creates a profile for further displaying in ProfileView. | 440 * Creates a profile for further displaying in ProfileView. |
188 */ | 441 */ |
189 devtools.profiler.Processor.prototype.createProfileForView = function() { | 442 devtools.profiler.Processor.prototype.createProfileForView = function() { |
190 var profile = new devtools.profiler.ProfileView(); | 443 var profile = this.viewBuilder_.buildView( |
| 444 this.currentProfile_.getTopDownProfile()); |
191 profile.uid = this.profileId_++; | 445 profile.uid = this.profileId_++; |
192 profile.title = UserInitiatedProfileName + '.' + profile.uid; | 446 profile.title = UserInitiatedProfileName + '.' + profile.uid; |
193 // A trick to cope with ProfileView.bottomUpProfileDataGridTree and | |
194 // ProfileView.topDownProfileDataGridTree behavior. | |
195 profile.head = profile; | |
196 profile.heavyProfile = this.viewBuilder_.buildView( | |
197 this.profile_.getBottomUpProfile(), true); | |
198 profile.treeProfile = this.viewBuilder_.buildView( | |
199 this.profile_.getTopDownProfile()); | |
200 return profile; | 447 return profile; |
201 }; | 448 }; |
OLD | NEW |