summaryrefslogtreecommitdiffstats
path: root/toolkit/components/perfmonitoring/PerformanceStats-content.js
blob: 9a6a2d81dd5ec7c73418500f37fa68cc9caac10d (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
/* 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/. */

/**
 * A proxy implementing communication between the PerformanceStats.jsm modules
 * of the parent and children processes.
 *
 * This script is loaded in all processes but is essentially a NOOP in the
 * parent process.
 */

"use strict";

var { utils: Cu, classes: Cc, interfaces: Ci } = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});

XPCOMUtils.defineLazyModuleGetter(this, "PerformanceStats",
  "resource://gre/modules/PerformanceStats.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
  "resource://gre/modules/Task.jsm");

/**
 * A global performance monitor used by this process.
 *
 * For the sake of simplicity, rather than attempting to map each PerformanceMonitor
 * of the parent to a PerformanceMonitor in each child process, we maintain a single
 * PerformanceMonitor in each child process. Probes activation/deactivation for this
 * monitor is controlled by the activation/deactivation of probes in the parent.
 *
 * In the parent, this is always an empty monitor.
 */
var gMonitor = PerformanceStats.getMonitor([]);

/**
 * `true` if this is a content process, `false` otherwise.
 */
var isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;

/**
 * Handle message `performance-stats-service-acquire`: ensure that the global
 * monitor has a given probe. This message must be sent by the parent process
 * whenever a probe is activated application-wide.
 *
 * Note that we may miss acquire messages if they are sent before this process is
 * launched. For this reason, `performance-stats-service-collect` automatically
 * re-acquires probes if it realizes that they are missing.
 *
 * This operation is a NOOP on the parent process.
 *
 * @param {{payload: Array<string>}} msg.data The message received. `payload`
 * must be an array of probe names.
 */
Services.cpmm.addMessageListener("performance-stats-service-acquire", function(msg) {
  if (!isContent) {
    return;
  }
  let name = msg.data.payload;
  ensureAcquired(name);
});

/**
 * Handle message `performance-stats-service-release`: release a given probe
 * from the global monitor. This message must be sent by the parent process
 * whenever a probe is deactivated application-wide.
 *
 * Note that we may miss release messages if they are sent before this process is
 * launched. This is ok, as probes are inactive by default: if we miss the release
 * message, we have already missed the acquire message, and the effect of both
 * messages together is to reset to the default state.
 *
 * This operation is a NOOP on the parent process.
 *
 * @param {{payload: Array<string>}} msg.data The message received. `payload`
 * must be an array of probe names.
 */
Services.cpmm.addMessageListener("performance-stats-service-release", function(msg) {
  if (!isContent) {
    return;
  }

  // Keep only the probes that do not appear in the payload
  let probes = gMonitor.probeNames
    .filter(x => msg.data.payload.indexOf(x) == -1);
  gMonitor = PerformanceStats.getMonitor(probes);
});

/**
 * Ensure that this process has all the probes it needs.
 *
 * @param {Array<string>} probeNames The name of all probes needed by the
 * process.
 */
function ensureAcquired(probeNames) {
  let alreadyAcquired = gMonitor.probeNames;

  // Algorithm is O(n^2) because we expect that n ≤ 3.
  let shouldAcquire = [];
  for (let probeName of probeNames) {
    if (alreadyAcquired.indexOf(probeName) == -1) {
      shouldAcquire.push(probeName)
    }
  }

  if (shouldAcquire.length == 0) {
    return;
  }
  gMonitor = PerformanceStats.getMonitor([...alreadyAcquired, ...shouldAcquire]);
}

/**
 * Handle message `performance-stats-service-collected`: collect the data
 * obtained by the monitor. This message must be sent by the parent process
 * whenever we grab a performance snapshot of the application.
 *
 * This operation provides `null` on the parent process.
 *
 * @param {{data: {payload: Array<string>}}} msg The message received. `payload`
 * must be an array of probe names.
 */
Services.cpmm.addMessageListener("performance-stats-service-collect", Task.async(function*(msg) {
  let {id, payload: {probeNames}} = msg.data;
  if (!isContent) {
    // This message was sent by the parent process to itself.
    // As per protocol, respond `null`.
    Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
      id,
      data: null
    });
    return;
  }

  // We may have missed acquire messages if the process was loaded too late.
  // Catch up now.
  ensureAcquired(probeNames);

  // Collect and return data.
  let data = yield gMonitor.promiseSnapshot({probeNames});
  Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
    id,
    data
  });
}));