summaryrefslogtreecommitdiffstats
path: root/devtools/client/performance/views/details-memory-flamegraph.js
blob: e8f1f51fd1adf95dfba406ca9fc4c611e2ae9c24 (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
/* 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/. */
/* globals $, PerformanceController, OverviewView */
"use strict";

const {
  FlameGraph,
  FlameGraphUtils,
} = require("devtools/client/shared/widgets/FlameGraph");
const { extend } = require("devtools/shared/extend");
const RecordingUtils = require("devtools/shared/performance/recording-utils");
const EventEmitter = require("devtools/shared/event-emitter");

const EVENTS = require("devtools/client/performance/events");
const {
  DetailsSubview,
} = require("devtools/client/performance/views/details-abstract-subview");
const { L10N } = require("devtools/client/performance/modules/global");

/**
 * FlameGraph view containing a pyramid-like visualization of memory allocation
 * sites, controlled by DetailsView.
 */
const MemoryFlameGraphView = extend(DetailsSubview, {
  shouldUpdateWhileMouseIsActive: true,

  rerenderPrefs: [
    "invert-flame-graph",
    "flatten-tree-recursion",
    "show-idle-blocks",
  ],

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

    this.graph = new FlameGraph($("#memory-flamegraph-view"));
    this.graph.timelineTickUnits = L10N.getStr("graphs.ms");
    this.graph.setTheme(PerformanceController.getTheme());
    await this.graph.ready();

    this._onRangeChangeInGraph = this._onRangeChangeInGraph.bind(this);
    this._onThemeChanged = this._onThemeChanged.bind(this);

    PerformanceController.on(EVENTS.THEME_CHANGED, this._onThemeChanged);
    this.graph.on("selecting", this._onRangeChangeInGraph);
  },

  /**
   * Unbinds events.
   */
  async destroy() {
    DetailsSubview.destroy.call(this);

    PerformanceController.off(EVENTS.THEME_CHANGED, this._onThemeChanged);
    this.graph.off("selecting", this._onRangeChangeInGraph);

    await this.graph.destroy();
  },

  /**
   * Method for handling all the set up for rendering a new flamegraph.
   *
   * @param object interval [optional]
   *        The { startTime, endTime }, in milliseconds.
   */
  render: function(interval = {}) {
    const recording = PerformanceController.getCurrentRecording();
    const duration = recording.getDuration();
    const allocations = recording.getAllocations();

    const thread = RecordingUtils.getProfileThreadFromAllocations(allocations);
    const data = FlameGraphUtils.createFlameGraphDataFromThread(thread, {
      invertStack: PerformanceController.getOption("invert-flame-graph"),
      flattenRecursion: PerformanceController.getOption(
        "flatten-tree-recursion"
      ),
      showIdleBlocks:
        PerformanceController.getOption("show-idle-blocks") &&
        L10N.getStr("table.idle"),
    });

    this.graph.setData({
      data,
      bounds: {
        startTime: 0,
        endTime: duration,
      },
      visible: {
        startTime: interval.startTime || 0,
        endTime: interval.endTime || duration,
      },
    });

    this.emit(EVENTS.UI_MEMORY_FLAMEGRAPH_RENDERED);
  },

  /**
   * Fired when a range is selected or cleared in the FlameGraph.
   */
  _onRangeChangeInGraph: function() {
    const interval = this.graph.getViewRange();

    // Squelch rerendering this view when we update the range here
    // to avoid recursion, as our FlameGraph handles rerendering itself
    // when originating from within the graph.
    this.requiresUpdateOnRangeChange = false;
    OverviewView.setTimeInterval(interval);
    this.requiresUpdateOnRangeChange = true;
  },

  /**
   * Called whenever a pref is changed and this view needs to be rerendered.
   */
  _onRerenderPrefChanged: function() {
    const recording = PerformanceController.getCurrentRecording();
    const allocations = recording.getAllocations();
    const thread = RecordingUtils.getProfileThreadFromAllocations(allocations);
    FlameGraphUtils.removeFromCache(thread);
  },

  /**
   * Called when `devtools.theme` changes.
   */
  _onThemeChanged: function(theme) {
    this.graph.setTheme(theme);
    this.graph.refresh({ force: true });
  },

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

EventEmitter.decorate(MemoryFlameGraphView);

exports.MemoryFlameGraphView = MemoryFlameGraphView;