Web Inspector: implement Flame Chart for CPU profiler.
[WebKit-https.git] / Source / WebCore / inspector / front-end / CPUProfileView.js
1 /*
2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 /**
27  * @constructor
28  * @extends {WebInspector.View}
29  * @param {WebInspector.CPUProfileHeader} profile
30  */
31 WebInspector.CPUProfileView = function(profile)
32 {
33     WebInspector.View.call(this);
34
35     this.element.addStyleClass("profile-view");
36     
37     this.showSelfTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowSelfTimeAsPercent", true);
38     this.showTotalTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowTotalTimeAsPercent", true);
39     this.showAverageTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowAverageTimeAsPercent", true);
40     this._viewType = WebInspector.settings.createSetting("cpuProfilerView", WebInspector.CPUProfileView._TypeHeavy);
41
42     var columns = { "self": { title: WebInspector.UIString("Self"), width: "72px", sort: "descending", sortable: true },
43                     "total": { title: WebInspector.UIString("Total"), width: "72px", sortable: true },
44                     "average": { title: WebInspector.UIString("Average"), width: "72px", sortable: true },
45                     "calls": { title: WebInspector.UIString("Calls"), width: "54px", sortable: true },
46                     "function": { title: WebInspector.UIString("Function"), disclosure: true, sortable: true } };
47
48     if (Capabilities.samplingCPUProfiler) {
49         delete columns.average;
50         delete columns.calls;
51     }
52
53     this.dataGrid = new WebInspector.DataGrid(columns);
54     this.dataGrid.addEventListener("sorting changed", this._sortProfile, this);
55     this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
56
57     if (WebInspector.experimentsSettings.cpuFlameChart.isEnabled()) {
58         this._splitView = new WebInspector.SplitView(false, "flameChartSplitLocation");
59         this._splitView.show(this.element);
60
61         this.dataGrid.show(this._splitView.firstElement());
62
63         this.flameChart = new WebInspector.FlameChart(this);
64         this.flameChart.show(this._splitView.secondElement());
65     } else
66         this.dataGrid.show(this.element);
67
68     this.viewSelectComboBox = new WebInspector.StatusBarComboBox(this._changeView.bind(this));
69
70     var heavyViewOption = this.viewSelectComboBox.createOption(WebInspector.UIString("Heavy (Bottom Up)"), "", WebInspector.CPUProfileView._TypeHeavy);
71     var treeViewOption = this.viewSelectComboBox.createOption(WebInspector.UIString("Tree (Top Down)"), "", WebInspector.CPUProfileView._TypeTree);
72     this.viewSelectComboBox.select(this._viewType.get() === WebInspector.CPUProfileView._TypeHeavy ? heavyViewOption : treeViewOption);
73
74     this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item");
75     this.percentButton.addEventListener("click", this._percentClicked, this);
76
77     this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item");
78     this.focusButton.setEnabled(false);
79     this.focusButton.addEventListener("click", this._focusClicked, this);
80
81     this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item");
82     this.excludeButton.setEnabled(false);
83     this.excludeButton.addEventListener("click", this._excludeClicked, this);
84
85     this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item");
86     this.resetButton.visible = false;
87     this.resetButton.addEventListener("click", this._resetClicked, this);
88
89     this.profileHead = /** @type {?ProfilerAgent.CPUProfileNode} */ (null);
90     this.profile = profile;
91
92     this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultFormatter(30));
93
94     ProfilerAgent.getCPUProfile(this.profile.uid, this._getCPUProfileCallback.bind(this));
95 }
96
97 WebInspector.CPUProfileView._TypeTree = "Tree";
98 WebInspector.CPUProfileView._TypeHeavy = "Heavy";
99
100 WebInspector.CPUProfileView.prototype = {
101     /**
102      * @param {?Protocol.Error} error
103      * @param {ProfilerAgent.CPUProfile} profile
104      */
105     _getCPUProfileCallback: function(error, profile)
106     {
107         if (error)
108             return;
109
110         if (!profile.head) {
111             // Profiling was tentatively terminated with the "Clear all profiles." button.
112             return;
113         }
114         this.profileHead = profile.head;
115
116         if (profile.idleTime)
117             this._injectIdleTimeNode(profile);
118
119         this._assignParentsInProfile();
120         this._changeView();
121         this._updatePercentButton();
122         if (this.flameChart)
123             this.flameChart.update();
124     },
125
126     get statusBarItems()
127     {
128         return [this.viewSelectComboBox.element, this.percentButton.element, this.focusButton.element, this.excludeButton.element, this.resetButton.element];
129     },
130
131     /**
132      * @return {!WebInspector.ProfileDataGridTree}
133      */
134     _getBottomUpProfileDataGridTree: function()
135     {
136         if (!this._bottomUpProfileDataGridTree)
137             this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, this.profileHead);
138         return this._bottomUpProfileDataGridTree;
139     },
140
141     /**
142      * @return {!WebInspector.ProfileDataGridTree}
143      */
144     _getTopDownProfileDataGridTree: function()
145     {
146         if (!this._topDownProfileDataGridTree)
147             this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, this.profileHead);
148         return this._topDownProfileDataGridTree;
149     },
150
151     willHide: function()
152     {
153         this._currentSearchResultIndex = -1;
154     },
155
156     refresh: function()
157     {
158         var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;
159
160         this.dataGrid.rootNode().removeChildren();
161
162         var children = this.profileDataGridTree.children;
163         var count = children.length;
164
165         for (var index = 0; index < count; ++index)
166             this.dataGrid.rootNode().appendChild(children[index]);
167
168         if (selectedProfileNode)
169             selectedProfileNode.selected = true;
170     },
171
172     refreshVisibleData: function()
173     {
174         var child = this.dataGrid.rootNode().children[0];
175         while (child) {
176             child.refresh();
177             child = child.traverseNextNode(false, null, true);
178         }
179     },
180
181     refreshShowAsPercents: function()
182     {
183         this._updatePercentButton();
184         this.refreshVisibleData();
185     },
186
187     searchCanceled: function()
188     {
189         if (this._searchResults) {
190             for (var i = 0; i < this._searchResults.length; ++i) {
191                 var profileNode = this._searchResults[i].profileNode;
192
193                 delete profileNode._searchMatchedSelfColumn;
194                 delete profileNode._searchMatchedTotalColumn;
195                 delete profileNode._searchMatchedAverageColumn;
196                 delete profileNode._searchMatchedCallsColumn;
197                 delete profileNode._searchMatchedFunctionColumn;
198
199                 profileNode.refresh();
200             }
201         }
202
203         delete this._searchFinishedCallback;
204         this._currentSearchResultIndex = -1;
205         this._searchResults = [];
206     },
207
208     performSearch: function(query, finishedCallback)
209     {
210         // Call searchCanceled since it will reset everything we need before doing a new search.
211         this.searchCanceled();
212
213         query = query.trim();
214
215         if (!query.length)
216             return;
217
218         this._searchFinishedCallback = finishedCallback;
219
220         var greaterThan = (query.startsWith(">"));
221         var lessThan = (query.startsWith("<"));
222         var equalTo = (query.startsWith("=") || ((greaterThan || lessThan) && query.indexOf("=") === 1));
223         var percentUnits = (query.lastIndexOf("%") === (query.length - 1));
224         var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2));
225         var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1));
226
227         var queryNumber = parseFloat(query);
228         if (greaterThan || lessThan || equalTo) {
229             if (equalTo && (greaterThan || lessThan))
230                 queryNumber = parseFloat(query.substring(2));
231             else
232                 queryNumber = parseFloat(query.substring(1));
233         }
234
235         var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber);
236
237         // Make equalTo implicitly true if it wasn't specified there is no other operator.
238         if (!isNaN(queryNumber) && !(greaterThan || lessThan))
239             equalTo = true;
240
241         var matcher = new RegExp(query.escapeForRegExp(), "i");
242
243         function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode)
244         {
245             delete profileDataGridNode._searchMatchedSelfColumn;
246             delete profileDataGridNode._searchMatchedTotalColumn;
247             delete profileDataGridNode._searchMatchedAverageColumn;
248             delete profileDataGridNode._searchMatchedCallsColumn;
249             delete profileDataGridNode._searchMatchedFunctionColumn;
250
251             if (percentUnits) {
252                 if (lessThan) {
253                     if (profileDataGridNode.selfPercent < queryNumber)
254                         profileDataGridNode._searchMatchedSelfColumn = true;
255                     if (profileDataGridNode.totalPercent < queryNumber)
256                         profileDataGridNode._searchMatchedTotalColumn = true;
257                     if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
258                         profileDataGridNode._searchMatchedAverageColumn = true;
259                 } else if (greaterThan) {
260                     if (profileDataGridNode.selfPercent > queryNumber)
261                         profileDataGridNode._searchMatchedSelfColumn = true;
262                     if (profileDataGridNode.totalPercent > queryNumber)
263                         profileDataGridNode._searchMatchedTotalColumn = true;
264                     if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
265                         profileDataGridNode._searchMatchedAverageColumn = true;
266                 }
267
268                 if (equalTo) {
269                     if (profileDataGridNode.selfPercent == queryNumber)
270                         profileDataGridNode._searchMatchedSelfColumn = true;
271                     if (profileDataGridNode.totalPercent == queryNumber)
272                         profileDataGridNode._searchMatchedTotalColumn = true;
273                     if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
274                         profileDataGridNode._searchMatchedAverageColumn = true;
275                 }
276             } else if (millisecondsUnits || secondsUnits) {
277                 if (lessThan) {
278                     if (profileDataGridNode.selfTime < queryNumberMilliseconds)
279                         profileDataGridNode._searchMatchedSelfColumn = true;
280                     if (profileDataGridNode.totalTime < queryNumberMilliseconds)
281                         profileDataGridNode._searchMatchedTotalColumn = true;
282                     if (profileDataGridNode.averageTime < queryNumberMilliseconds)
283                         profileDataGridNode._searchMatchedAverageColumn = true;
284                 } else if (greaterThan) {
285                     if (profileDataGridNode.selfTime > queryNumberMilliseconds)
286                         profileDataGridNode._searchMatchedSelfColumn = true;
287                     if (profileDataGridNode.totalTime > queryNumberMilliseconds)
288                         profileDataGridNode._searchMatchedTotalColumn = true;
289                     if (profileDataGridNode.averageTime > queryNumberMilliseconds)
290                         profileDataGridNode._searchMatchedAverageColumn = true;
291                 }
292
293                 if (equalTo) {
294                     if (profileDataGridNode.selfTime == queryNumberMilliseconds)
295                         profileDataGridNode._searchMatchedSelfColumn = true;
296                     if (profileDataGridNode.totalTime == queryNumberMilliseconds)
297                         profileDataGridNode._searchMatchedTotalColumn = true;
298                     if (profileDataGridNode.averageTime == queryNumberMilliseconds)
299                         profileDataGridNode._searchMatchedAverageColumn = true;
300                 }
301             } else {
302                 if (equalTo && profileDataGridNode.numberOfCalls == queryNumber)
303                     profileDataGridNode._searchMatchedCallsColumn = true;
304                 if (greaterThan && profileDataGridNode.numberOfCalls > queryNumber)
305                     profileDataGridNode._searchMatchedCallsColumn = true;
306                 if (lessThan && profileDataGridNode.numberOfCalls < queryNumber)
307                     profileDataGridNode._searchMatchedCallsColumn = true;
308             }
309
310             if (profileDataGridNode.functionName.match(matcher) || (profileDataGridNode.url && profileDataGridNode.url.match(matcher)))
311                 profileDataGridNode._searchMatchedFunctionColumn = true;
312
313             if (profileDataGridNode._searchMatchedSelfColumn ||
314                 profileDataGridNode._searchMatchedTotalColumn ||
315                 profileDataGridNode._searchMatchedAverageColumn ||
316                 profileDataGridNode._searchMatchedCallsColumn ||
317                 profileDataGridNode._searchMatchedFunctionColumn)
318             {
319                 profileDataGridNode.refresh();
320                 return true;
321             }
322
323             return false;
324         }
325
326         var current = this.profileDataGridTree.children[0];
327
328         while (current) {
329             if (matchesQuery(current)) {
330                 this._searchResults.push({ profileNode: current });
331             }
332
333             current = current.traverseNextNode(false, null, false);
334         }
335
336         finishedCallback(this, this._searchResults.length);
337     },
338
339     jumpToFirstSearchResult: function()
340     {
341         if (!this._searchResults || !this._searchResults.length)
342             return;
343         this._currentSearchResultIndex = 0;
344         this._jumpToSearchResult(this._currentSearchResultIndex);
345     },
346
347     jumpToLastSearchResult: function()
348     {
349         if (!this._searchResults || !this._searchResults.length)
350             return;
351         this._currentSearchResultIndex = (this._searchResults.length - 1);
352         this._jumpToSearchResult(this._currentSearchResultIndex);
353     },
354
355     jumpToNextSearchResult: function()
356     {
357         if (!this._searchResults || !this._searchResults.length)
358             return;
359         if (++this._currentSearchResultIndex >= this._searchResults.length)
360             this._currentSearchResultIndex = 0;
361         this._jumpToSearchResult(this._currentSearchResultIndex);
362     },
363
364     jumpToPreviousSearchResult: function()
365     {
366         if (!this._searchResults || !this._searchResults.length)
367             return;
368         if (--this._currentSearchResultIndex < 0)
369             this._currentSearchResultIndex = (this._searchResults.length - 1);
370         this._jumpToSearchResult(this._currentSearchResultIndex);
371     },
372
373     showingFirstSearchResult: function()
374     {
375         return (this._currentSearchResultIndex === 0);
376     },
377
378     showingLastSearchResult: function()
379     {
380         return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
381     },
382
383     _jumpToSearchResult: function(index)
384     {
385         var searchResult = this._searchResults[index];
386         if (!searchResult)
387             return;
388
389         var profileNode = searchResult.profileNode;
390         profileNode.revealAndSelect();
391     },
392
393     _changeView: function()
394     {
395         if (!this.profile)
396             return;
397
398         switch (this.viewSelectComboBox.selectedOption().value) {
399         case WebInspector.CPUProfileView._TypeTree:
400             this.profileDataGridTree = this._getTopDownProfileDataGridTree();
401             this._sortProfile();
402             this._viewType.set(WebInspector.CPUProfileView._TypeTree);
403             break;
404         case WebInspector.CPUProfileView._TypeHeavy:
405             this.profileDataGridTree = this._getBottomUpProfileDataGridTree();
406             this._sortProfile();
407             this._viewType.set(WebInspector.CPUProfileView._TypeHeavy);
408         }
409
410         if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
411             return;
412
413         // The current search needs to be performed again. First negate out previous match
414         // count by calling the search finished callback with a negative number of matches.
415         // Then perform the search again the with same query and callback.
416         this._searchFinishedCallback(this, -this._searchResults.length);
417         this.performSearch(this.currentQuery, this._searchFinishedCallback);
418     },
419
420     _percentClicked: function(event)
421     {
422         var currentState = this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get();
423         this.showSelfTimeAsPercent.set(!currentState);
424         this.showTotalTimeAsPercent.set(!currentState);
425         this.showAverageTimeAsPercent.set(!currentState);
426         this.refreshShowAsPercents();
427     },
428
429     _updatePercentButton: function()
430     {
431         if (this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get()) {
432             this.percentButton.title = WebInspector.UIString("Show absolute total and self times.");
433             this.percentButton.toggled = true;
434         } else {
435             this.percentButton.title = WebInspector.UIString("Show total and self times as percentages.");
436             this.percentButton.toggled = false;
437         }
438     },
439
440     _focusClicked: function(event)
441     {
442         if (!this.dataGrid.selectedNode)
443             return;
444
445         this.resetButton.visible = true;
446         this.profileDataGridTree.focus(this.dataGrid.selectedNode);
447         this.refresh();
448         this.refreshVisibleData();
449     },
450
451     _excludeClicked: function(event)
452     {
453         var selectedNode = this.dataGrid.selectedNode
454
455         if (!selectedNode)
456             return;
457
458         selectedNode.deselect();
459
460         this.resetButton.visible = true;
461         this.profileDataGridTree.exclude(selectedNode);
462         this.refresh();
463         this.refreshVisibleData();
464     },
465
466     _resetClicked: function(event)
467     {
468         this.resetButton.visible = false;
469         this.profileDataGridTree.restore();
470         this._linkifier.reset();
471         this.refresh();
472         this.refreshVisibleData();
473     },
474
475     _dataGridNodeSelected: function(node)
476     {
477         this.focusButton.setEnabled(true);
478         this.excludeButton.setEnabled(true);
479     },
480
481     _dataGridNodeDeselected: function(node)
482     {
483         this.focusButton.setEnabled(false);
484         this.excludeButton.setEnabled(false);
485     },
486
487     _sortProfile: function()
488     {
489         var sortAscending = this.dataGrid.sortOrder === "ascending";
490         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
491         var sortProperty = {
492                 "average": "averageTime",
493                 "self": "selfTime",
494                 "total": "totalTime",
495                 "calls": "numberOfCalls",
496                 "function": "functionName"
497             }[sortColumnIdentifier];
498
499         this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending));
500
501         this.refresh();
502     },
503
504     _mouseDownInDataGrid: function(event)
505     {
506         if (event.detail < 2)
507             return;
508
509         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
510         if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column") && !cell.hasStyleClass("average-column")))
511             return;
512
513         if (cell.hasStyleClass("total-column"))
514             this.showTotalTimeAsPercent.set(!this.showTotalTimeAsPercent.get());
515         else if (cell.hasStyleClass("self-column"))
516             this.showSelfTimeAsPercent.set(!this.showSelfTimeAsPercent.get());
517         else if (cell.hasStyleClass("average-column"))
518             this.showAverageTimeAsPercent.set(!this.showAverageTimeAsPercent.get());
519
520         this.refreshShowAsPercents();
521
522         event.consume(true);
523     },
524
525     _assignParentsInProfile: function()
526     {
527         var head = this.profileHead;
528         head.parent = null;
529         head.head = null;
530         var nodesToTraverse = [ { parent: head, children: head.children } ];
531         while (nodesToTraverse.length > 0) {
532             var pair = nodesToTraverse.pop();
533             var parent = pair.parent;
534             var children = pair.children;
535             var length = children.length;
536             for (var i = 0; i < length; ++i) {
537                 children[i].head = head;
538                 children[i].parent = parent;
539                 if (children[i].children.length > 0)
540                     nodesToTraverse.push({ parent: children[i], children: children[i].children });
541             }
542         }
543     },
544
545     /**
546      * @param {ProfilerAgent.CPUProfile} profile
547      */
548     _injectIdleTimeNode: function(profile)
549     {
550         var idleTime = profile.idleTime;
551         var nodes = profile.head.children;
552
553         var programNode = {selfTime: 0};
554         for (var i = nodes.length - 1; i >= 0; --i) {
555             if (nodes[i].functionName === "(program)") {
556                 programNode = nodes[i];
557                 break;
558             }
559         }
560         var programTime = programNode.selfTime;
561         if (idleTime > programTime)
562             idleTime = programTime;
563         programTime = programTime - idleTime;
564         programNode.selfTime = programTime;
565         programNode.totalTime = programTime;
566         var idleNode = {
567             functionName: "(idle)",
568             url: null,
569             lineNumber: 0,
570             totalTime: idleTime,
571             selfTime: idleTime,
572             numberOfCalls: 0,
573             visible: true,
574             callUID: 0,
575             children: []
576         };
577         nodes.push(idleNode);
578     },
579
580     __proto__: WebInspector.View.prototype
581 }
582
583 /**
584  * @constructor
585  * @extends {WebInspector.ProfileType}
586  * @implements {ProfilerAgent.Dispatcher}
587  */
588 WebInspector.CPUProfileType = function()
589 {
590     WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile"));
591     InspectorBackend.registerProfilerDispatcher(this);
592     this._recording = false;
593     WebInspector.CPUProfileType.instance = this;
594 }
595
596 WebInspector.CPUProfileType.TypeId = "CPU";
597
598 WebInspector.CPUProfileType.prototype = {
599     get buttonTooltip()
600     {
601         return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling.");
602     },
603
604     /**
605      * @override
606      * @return {boolean}
607      */
608     buttonClicked: function()
609     {
610         if (this._recording) {
611             this.stopRecordingProfile();
612             return false;
613         } else {
614             this.startRecordingProfile();
615             return true;
616         }
617     },
618
619     get treeItemTitle()
620     {
621         return WebInspector.UIString("CPU PROFILES");
622     },
623
624     get description()
625     {
626         return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions.");
627     },
628
629     /**
630      * @param {ProfilerAgent.ProfileHeader} profileHeader
631      */
632     addProfileHeader: function(profileHeader)
633     {
634         this.addProfile(this.createProfile(profileHeader));
635     },
636
637     isRecordingProfile: function()
638     {
639         return this._recording;
640     },
641
642     startRecordingProfile: function()
643     {
644         this._recording = true;
645         WebInspector.userMetrics.ProfilesCPUProfileTaken.record();
646         ProfilerAgent.start();
647     },
648
649     stopRecordingProfile: function()
650     {
651         this._recording = false;
652         ProfilerAgent.stop();
653     },
654
655     /**
656      * @param {boolean} isProfiling
657      */
658     setRecordingProfile: function(isProfiling)
659     {
660         this._recording = isProfiling;
661     },
662
663     /**
664      * @override
665      * @param {string=} title
666      * @return {!WebInspector.ProfileHeader}
667      */
668     createTemporaryProfile: function(title)
669     {
670         title = title || WebInspector.UIString("Recording\u2026");
671         return new WebInspector.CPUProfileHeader(this, title);
672     },
673
674     /**
675      * @override
676      * @param {ProfilerAgent.ProfileHeader} profile
677      * @return {!WebInspector.ProfileHeader}
678      */
679     createProfile: function(profile)
680     {
681         return new WebInspector.CPUProfileHeader(this, profile.title, profile.uid);
682     },
683
684     /**
685      * @override
686      * @param {!WebInspector.ProfileHeader} profile
687      */
688     removeProfile: function(profile)
689     {
690         WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
691         if (!profile.isTemporary)
692             ProfilerAgent.removeProfile(this.id, profile.uid);
693     },
694
695     /**
696      * @override
697      * @param {function(this:WebInspector.ProfileType, ?string, Array.<ProfilerAgent.ProfileHeader>)} populateCallback
698      */
699     _requestProfilesFromBackend: function(populateCallback)
700     {
701         ProfilerAgent.getProfileHeaders(populateCallback);
702     },
703
704     /**
705      * @override
706      */
707     resetProfiles: function()
708     {
709         this._reset();
710     },
711
712     /** @deprecated To be removed from the protocol */
713     addHeapSnapshotChunk: function(uid, chunk)
714     {
715         throw new Error("Never called");
716     },
717
718     /** @deprecated To be removed from the protocol */
719     finishHeapSnapshot: function(uid)
720     {
721         throw new Error("Never called");
722     },
723
724     /** @deprecated To be removed from the protocol */
725     reportHeapSnapshotProgress: function(done, total)
726     {
727         throw new Error("Never called");
728     },
729
730     __proto__: WebInspector.ProfileType.prototype
731 }
732
733 /**
734  * @constructor
735  * @extends {WebInspector.ProfileHeader}
736  * @param {!WebInspector.CPUProfileType} type
737  * @param {string} title
738  * @param {number=} uid
739  */
740 WebInspector.CPUProfileHeader = function(type, title, uid)
741 {
742     WebInspector.ProfileHeader.call(this, type, title, uid);
743 }
744
745 WebInspector.CPUProfileHeader.prototype = {
746     /**
747      * @override
748      */
749     createSidebarTreeElement: function()
750     {
751         return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Profile %d"), "profile-sidebar-tree-item");
752     },
753
754     /**
755      * @override
756      * @param {WebInspector.ProfilesPanel} profilesPanel
757      */
758     createView: function(profilesPanel)
759     {
760         return new WebInspector.CPUProfileView(this);
761     },
762
763     __proto__: WebInspector.ProfileHeader.prototype
764 }
765
766 importScript("FlameChart.js");