toolkit/components/telemetry/UITelemetry.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/telemetry/UITelemetry.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,216 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +const Cu = Components.utils;
    1.11 +
    1.12 +const PREF_BRANCH = "toolkit.telemetry.";
    1.13 +const PREF_ENABLED = PREF_BRANCH + "enabled";
    1.14 +
    1.15 +this.EXPORTED_SYMBOLS = [
    1.16 +  "UITelemetry",
    1.17 +];
    1.18 +
    1.19 +Cu.import("resource://gre/modules/Services.jsm", this);
    1.20 +
    1.21 +/**
    1.22 + * UITelemetry is a helper JSM used to record UI specific telemetry events.
    1.23 + *
    1.24 + * It implements nsIUITelemetryObserver, defined in nsIAndroidBridge.idl.
    1.25 + */
    1.26 +this.UITelemetry = {
    1.27 +  _enabled: undefined,
    1.28 +  _activeSessions: {},
    1.29 +  _measurements: [],
    1.30 +
    1.31 +  // Lazily decide whether telemetry is enabled.
    1.32 +  get enabled() {
    1.33 +    if (this._enabled !== undefined) {
    1.34 +      return this._enabled;
    1.35 +    }
    1.36 +
    1.37 +    // Set an observer to watch for changes at runtime.
    1.38 +    Services.prefs.addObserver(PREF_ENABLED, this, false);
    1.39 +    Services.obs.addObserver(this, "profile-before-change", false);
    1.40 +
    1.41 +    // Pick up the current value.
    1.42 +    try {
    1.43 +      this._enabled = Services.prefs.getBoolPref(PREF_ENABLED);
    1.44 +    } catch (e) {
    1.45 +      this._enabled = false;
    1.46 +    }
    1.47 +
    1.48 +    return this._enabled;
    1.49 +  },
    1.50 +
    1.51 +  observe: function(aSubject, aTopic, aData) {
    1.52 +    if (aTopic == "profile-before-change") {
    1.53 +      Services.obs.removeObserver(this, "profile-before-change");
    1.54 +      Services.prefs.removeObserver(PREF_ENABLED, this);
    1.55 +      this._enabled = undefined;
    1.56 +      return;
    1.57 +    }
    1.58 +
    1.59 +    if (aTopic == "nsPref:changed") {
    1.60 +      switch (aData) {
    1.61 +        case PREF_ENABLED:
    1.62 +          let on = Services.prefs.getBoolPref(PREF_ENABLED);
    1.63 +          this._enabled = on;
    1.64 +
    1.65 +          // Wipe ourselves if we were just disabled.
    1.66 +          if (!on) {
    1.67 +            this._activeSessions = {};
    1.68 +            this._measurements = [];
    1.69 +          }
    1.70 +          break;
    1.71 +      }
    1.72 +    }
    1.73 +  },
    1.74 +
    1.75 +  /**
    1.76 +   * This exists exclusively for testing -- our events are not intended to
    1.77 +   * be retrieved via an XPCOM interface.
    1.78 +   */
    1.79 +  get wrappedJSObject() {
    1.80 +    return this;
    1.81 +  },
    1.82 +
    1.83 +  /**
    1.84 +   * Holds the functions that provide UITelemetry's simple
    1.85 +   * measurements. Those functions are mapped to unique names,
    1.86 +   * and should be registered with addSimpleMeasureFunction.
    1.87 +   */
    1.88 +  _simpleMeasureFunctions: {},
    1.89 +
    1.90 +  /**
    1.91 +   * Adds a single event described by a timestamp, an action, and the calling
    1.92 +   * method.
    1.93 +   *
    1.94 +   * Optionally provide a string 'extras', which will be recorded as part of
    1.95 +   * the event.
    1.96 +   *
    1.97 +   * All extant sessions will be recorded by name for each event.
    1.98 +   */
    1.99 +  addEvent: function(aAction, aMethod, aTimestamp, aExtras) {
   1.100 +    if (!this.enabled) {
   1.101 +      return;
   1.102 +    }
   1.103 +
   1.104 +    let sessions = Object.keys(this._activeSessions);
   1.105 +    let aEvent = {
   1.106 +      type: "event",
   1.107 +      action: aAction,
   1.108 +      method: aMethod,
   1.109 +      sessions: sessions,
   1.110 +      timestamp: aTimestamp,
   1.111 +    };
   1.112 +
   1.113 +    if (aExtras) {
   1.114 +      aEvent.extras = aExtras;
   1.115 +    }
   1.116 +
   1.117 +    this._recordEvent(aEvent);
   1.118 +  },
   1.119 +
   1.120 +  /**
   1.121 +   * Begins tracking a session by storing a timestamp for session start.
   1.122 +   */
   1.123 +  startSession: function(aName, aTimestamp) {
   1.124 +    if (!this.enabled) {
   1.125 +      return;
   1.126 +    }
   1.127 +
   1.128 +    if (this._activeSessions[aName]) {
   1.129 +      // Do not overwrite a previous event start if it already exists.
   1.130 +      return;
   1.131 +    }
   1.132 +    this._activeSessions[aName] = aTimestamp;
   1.133 +  },
   1.134 +
   1.135 +  /**
   1.136 +   * Tracks the end of a session with a timestamp.
   1.137 +   */
   1.138 +  stopSession: function(aName, aReason, aTimestamp) {
   1.139 +    if (!this.enabled) {
   1.140 +      return;
   1.141 +    }
   1.142 +
   1.143 +    let sessionStart = this._activeSessions[aName];
   1.144 +    delete this._activeSessions[aName];
   1.145 +
   1.146 +    if (!sessionStart) {
   1.147 +      return;
   1.148 +    }
   1.149 +
   1.150 +    let aEvent = {
   1.151 +      type: "session",
   1.152 +      name: aName,
   1.153 +      reason: aReason,
   1.154 +      start: sessionStart,
   1.155 +      end: aTimestamp,
   1.156 +    };
   1.157 +
   1.158 +    this._recordEvent(aEvent);
   1.159 +  },
   1.160 +
   1.161 +  _recordEvent: function(aEvent) {
   1.162 +    this._measurements.push(aEvent);
   1.163 +  },
   1.164 +
   1.165 +  /**
   1.166 +   * Called by TelemetryPing to populate the simple measurement
   1.167 +   * blob. This function will iterate over all functions added
   1.168 +   * via addSimpleMeasureFunction and return an object with the
   1.169 +   * results of those functions.
   1.170 +   */
   1.171 +  getSimpleMeasures: function() {
   1.172 +    if (!this.enabled) {
   1.173 +      return {};
   1.174 +    }
   1.175 +
   1.176 +    let result = {};
   1.177 +    for (let name in this._simpleMeasureFunctions) {
   1.178 +      result[name] = this._simpleMeasureFunctions[name]();
   1.179 +    }
   1.180 +    return result;
   1.181 +  },
   1.182 +
   1.183 +  /**
   1.184 +   * Allows the caller to register functions that will get called
   1.185 +   * for simple measures during a Telemetry ping. aName is a unique
   1.186 +   * identifier used as they key for the simple measurement in the
   1.187 +   * object that getSimpleMeasures returns.
   1.188 +   *
   1.189 +   * This function throws an exception if aName already has a function
   1.190 +   * registered for it.
   1.191 +   */
   1.192 +  addSimpleMeasureFunction: function(aName, aFunction) {
   1.193 +    if (!this.enabled) {
   1.194 +      return;
   1.195 +    }
   1.196 +
   1.197 +    if (aName in this._simpleMeasureFunctions) {
   1.198 +      throw new Error("A simple measurement function is already registered for " + aName);
   1.199 +    }
   1.200 +
   1.201 +    if (!aFunction || typeof aFunction !== 'function') {
   1.202 +      throw new Error("addSimpleMeasureFunction called with non-function argument.");
   1.203 +    }
   1.204 +
   1.205 +    this._simpleMeasureFunctions[aName] = aFunction;
   1.206 +  },
   1.207 +
   1.208 +  removeSimpleMeasureFunction: function(aName) {
   1.209 +    delete this._simpleMeasureFunctions[aName];
   1.210 +  },
   1.211 +
   1.212 +  getUIMeasurements: function() {
   1.213 +    if (!this.enabled) {
   1.214 +      return [];
   1.215 +    }
   1.216 +
   1.217 +    return this._measurements.slice();
   1.218 +  }
   1.219 +};

mercurial