summaryrefslogtreecommitdiffstats
path: root/devtools/client/performance/views/details-js-call-tree.js
blob: 6c4e808af9157d3b44edf3917096e4fa70fbd6e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from ../performance-controller.js */
/* import-globals-from ../performance-view.js */
/* globals DetailsSubview */
"use strict";

/**
 * CallTree view containing profiler call tree, controlled by DetailsView.
 */
var JsCallTreeView = Heritage.extend(DetailsSubview, {

  rerenderPrefs: [
    "invert-call-tree",
    "show-platform-data",
    "flatten-tree-recursion",
    "show-jit-optimizations",
  ],

  // Units are in milliseconds.
  rangeChangeDebounceTime: 75,

  /**
   * Sets up the view with event binding.
   */
  initialize: function () {
    DetailsSubview.initialize.call(this);

    this._onLink = this._onLink.bind(this);
    this._onFocus = this._onFocus.bind(this);

    this.container = $("#js-calltree-view .call-tree-cells-container");

    this.optimizationsElement = $("#jit-optimizations-view");
  },

  /**
   * Unbinds events.
   */
  destroy: function () {
    ReactDOM.unmountComponentAtNode(this.optimizationsElement);
    this.optimizationsElement = null;
    this.container = null;
    this.threadNode = null;
    DetailsSubview.destroy.call(this);
  },

  /**
   * Method for handling all the set up for rendering a new call tree.
   *
   * @param object interval [optional]
   *        The { startTime, endTime }, in milliseconds.
   */
  render: function (interval = {}) {
    let recording = PerformanceController.getCurrentRecording();
    let profile = recording.getProfile();
    let showOptimizations = PerformanceController.getOption("show-jit-optimizations");

    let options = {
      contentOnly: !PerformanceController.getOption("show-platform-data"),
      invertTree: PerformanceController.getOption("invert-call-tree"),
      flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
      showOptimizationHint: showOptimizations
    };
    let threadNode = this.threadNode = this._prepareCallTree(profile, interval, options);
    this._populateCallTree(threadNode, options);

    // For better or worse, re-rendering loses frame selection,
    // so we should always hide opts on rerender
    this.hideOptimizations();

    this.emit(EVENTS.UI_JS_CALL_TREE_RENDERED);
  },

  showOptimizations: function () {
    this.optimizationsElement.classList.remove("hidden");
  },

  hideOptimizations: function () {
    this.optimizationsElement.classList.add("hidden");
  },

  _onFocus: function (_, treeItem) {
    let showOptimizations = PerformanceController.getOption("show-jit-optimizations");
    let frameNode = treeItem.frame;
    let optimizationSites = frameNode && frameNode.hasOptimizations()
                            ? frameNode.getOptimizations().optimizationSites
                            : [];

    if (!showOptimizations || !frameNode || optimizationSites.length === 0) {
      this.hideOptimizations();
      this.emit("focus", treeItem);
      return;
    }

    this.showOptimizations();

    let frameData = frameNode.getInfo();
    let optimizations = JITOptimizationsView({
      frameData,
      optimizationSites,
      onViewSourceInDebugger: (url, line) => {
        gToolbox.viewSourceInDebugger(url, line).then(success => {
          if (success) {
            this.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
          } else {
            this.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER);
          }
        });
      }
    });

    ReactDOM.render(optimizations, this.optimizationsElement);

    this.emit("focus", treeItem);
  },

  /**
   * Fired on the "link" event for the call tree in this container.
   */
  _onLink: function (_, treeItem) {
    let { url, line } = treeItem.frame.getInfo();
    gToolbox.viewSourceInDebugger(url, line).then(success => {
      if (success) {
        this.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
      } else {
        this.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER);
      }
    });
  },

  /**
   * Called when the recording is stopped and prepares data to
   * populate the call tree.
   */
  _prepareCallTree: function (profile, { startTime, endTime }, options) {
    let thread = profile.threads[0];
    let { contentOnly, invertTree, flattenRecursion } = options;
    let threadNode = new ThreadNode(thread, { startTime, endTime, contentOnly, invertTree,
                                              flattenRecursion });

    // Real profiles from nsProfiler (i.e. not synthesized from allocation
    // logs) always have a (root) node. Go down one level in the uninverted
    // view to avoid displaying both the synthesized root node and the (root)
    // node from the profiler.
    if (!invertTree) {
      threadNode.calls = threadNode.calls[0].calls;
    }

    return threadNode;
  },

  /**
   * Renders the call tree.
   */
  _populateCallTree: function (frameNode, options = {}) {
    // If we have an empty profile (no samples), then don't invert the tree, as
    // it would hide the root node and a completely blank call tree space can be
    // mis-interpreted as an error.
    let inverted = options.invertTree && frameNode.samples > 0;

    let root = new CallView({
      frame: frameNode,
      inverted: inverted,
      // The synthesized root node is hidden in inverted call trees.
      hidden: inverted,
      // Call trees should only auto-expand when not inverted. Passing undefined
      // will default to the CALL_TREE_AUTO_EXPAND depth.
      autoExpandDepth: inverted ? 0 : undefined,
      showOptimizationHint: options.showOptimizationHint
    });

    // Bind events.
    root.on("link", this._onLink);
    root.on("focus", this._onFocus);

    // Clear out other call trees.
    this.container.innerHTML = "";
    root.attachTo(this.container);

    // When platform data isn't shown, hide the cateogry labels, since they're
    // only available for C++ frames. Pass *false* to make them invisible.
    root.toggleCategories(!options.contentOnly);

    // Return the CallView for tests
    return root;
  },

  toString: () => "[object JsCallTreeView]"
});

EventEmitter.decorate(JsCallTreeView);