summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/PerformanceCounters.sys.mjs
blob: f5ada161d176ecb79449f364da50b427859ed6d1 (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
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* 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/. */

/**
 * This module contains a global counter to store API call in the current process.
 */

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { DeferredTask } from "resource://gre/modules/DeferredTask.sys.mjs";

const { DefaultMap } = ExtensionUtils;

const lazy = {};

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gTimingEnabled",
  "extensions.webextensions.enablePerformanceCounters",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gTimingMaxAge",
  "extensions.webextensions.performanceCountersMaxAge",
  1000
);

class CounterMap extends DefaultMap {
  defaultConstructor() {
    return new DefaultMap(() => ({ duration: 0, calls: 0 }));
  }

  flush() {
    let result = new CounterMap(undefined, this);
    this.clear();
    return result;
  }

  merge(other) {
    for (let [webextId, counters] of other) {
      for (let [api, counter] of counters) {
        let current = this.get(webextId).get(api);
        current.calls += counter.calls;
        current.duration += counter.duration;
      }
    }
  }
}

/**
 * Global Deferred used to send to the parent performance counters
 * when the counter is in a child.
 */
var _performanceCountersSender = null;

// Pre-definition of the global Counters instance.
export var PerformanceCounters = null;

function _sendPerformanceCounters(childApiManagerId) {
  let counters = PerformanceCounters.flush();
  // No need to send empty counters.
  if (counters.size == 0) {
    return;
  }
  let options = { childId: childApiManagerId, counters: counters };
  Services.cpmm.sendAsyncMessage("Extension:SendPerformanceCounter", options);
}

class Counters {
  constructor() {
    this.data = new CounterMap();
  }

  /**
   * Returns true if performance counters are enabled.
   *
   * Indirection used so gTimingEnabled is not exposed direcly
   * in PerformanceCounters -- which would prevent tests to dynamically
   * change the preference value once PerformanceCounters.jsm is loaded.
   *
   * @returns {boolean}
   */
  get enabled() {
    return lazy.gTimingEnabled;
  }

  /**
   * Returns the counters max age
   *
   * Indirection used so gTimingMaxAge is not exposed direcly
   * in PerformanceCounters -- which would prevent tests to dynamically
   * change the preference value once PerformanceCounters.jsm is loaded.
   *
   * @returns {number}
   */
  get maxAge() {
    return lazy.gTimingMaxAge;
  }

  /**
   * Stores an execution time.
   *
   * @param {string} webExtensionId The web extension id.
   * @param {string} apiPath The API path.
   * @param {integer} duration How long the call took.
   * @param {childApiManagerId} childApiManagerId If executed from a child, its API manager id.
   */
  storeExecutionTime(webExtensionId, apiPath, duration, childApiManagerId) {
    let apiCounter = this.data.get(webExtensionId).get(apiPath);
    apiCounter.duration += duration;
    apiCounter.calls += 1;

    // Create the global deferred task if we're in a child and
    // it's the first time.
    if (childApiManagerId) {
      if (!_performanceCountersSender) {
        _performanceCountersSender = new DeferredTask(() => {
          _sendPerformanceCounters(childApiManagerId);
        }, this.maxAge);
      }
      _performanceCountersSender.arm();
    }
  }

  /**
   * Merges another CounterMap into this.data
   *
   * Can be used by the main process to merge data received
   * from the children.
   *
   * @param {CounterMap} data The map to merge.
   */
  merge(data) {
    this.data.merge(data);
  }

  /**
   * Returns the performance counters and purges them.
   *
   * @returns {CounterMap}
   */
  flush() {
    return this.data.flush();
  }

  /**
   * Returns the performance counters.
   *
   * @returns {CounterMap}
   */
  getData() {
    return this.data;
  }
}

PerformanceCounters = new Counters();