tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

telemetry-test-helpers.js (8579B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 /* global is ok registerCleanupFunction Services */
      5 
      6 "use strict";
      7 
      8 // We try to avoid polluting the global scope as far as possible by defining
      9 // constants in the methods that use them because this script is not sandboxed
     10 // meaning that it is loaded via Services.scriptloader.loadSubScript()
     11 
     12 class TelemetryHelpers {
     13  constructor() {
     14    this.oldCanRecord = Services.telemetry.canRecordExtended;
     15    this.generateTelemetryTests = this.generateTelemetryTests.bind(this);
     16    registerCleanupFunction(this.stopTelemetry.bind(this));
     17  }
     18 
     19  /**
     20   * Allow collection of extended telemetry data.
     21   */
     22  startTelemetry() {
     23    Services.telemetry.canRecordExtended = true;
     24  }
     25 
     26  /**
     27   * Clear all telemetry types.
     28   */
     29  stopTelemetry() {
     30    // Clear histograms, scalars and Telemetry Events.
     31    this.clearHistograms(Services.telemetry.getSnapshotForHistograms);
     32    this.clearHistograms(Services.telemetry.getSnapshotForKeyedHistograms);
     33    Services.telemetry.clearScalars();
     34    Services.telemetry.clearEvents();
     35 
     36    Services.telemetry.canRecordExtended = this.oldCanRecord;
     37  }
     38 
     39  /**
     40   * Clears Telemetry Histograms.
     41   *
     42   * @param {Function} snapshotFunc
     43   *        The function used to take the snapshot. This can be one of the
     44   *        following:
     45   *          - Services.telemetry.getSnapshotForHistograms
     46   *          - Services.telemetry.getSnapshotForKeyedHistograms
     47   */
     48  clearHistograms(snapshotFunc) {
     49    snapshotFunc("main", true);
     50  }
     51 
     52  /**
     53   * Check the value of a given telemetry histogram.
     54   *
     55   * @param  {string} histId
     56   *         Histogram id
     57   * @param  {string} key
     58   *         Keyed histogram key
     59   * @param  {Array | number} expected
     60   *         Expected value
     61   * @param  {string} checkType
     62   *         "array" (default) - Check that an array matches the histogram data.
     63   *         "hasentries"  - For non-enumerated linear and exponential
     64   *                             histograms. This checks for at least one entry.
     65   *         "scalar" - Telemetry type is a scalar.
     66   *         "keyedscalar" - Telemetry type is a keyed scalar.
     67   */
     68  checkTelemetry(histId, key, expected, checkType) {
     69    let actual;
     70    let msg;
     71 
     72    if (checkType === "array" || checkType === "hasentries") {
     73      if (key) {
     74        const keyedHistogram = Services.telemetry
     75          .getKeyedHistogramById(histId)
     76          .snapshot();
     77        const result = keyedHistogram[key];
     78 
     79        if (result) {
     80          actual = result.values;
     81        } else {
     82          ok(false, `${histId}[${key}] exists`);
     83          return;
     84        }
     85      } else {
     86        actual = Services.telemetry.getHistogramById(histId).snapshot().values;
     87      }
     88    }
     89 
     90    switch (checkType) {
     91      case "array":
     92        msg = key ? `${histId}["${key}"] correct.` : `${histId} correct.`;
     93        is(JSON.stringify(actual), JSON.stringify(expected), msg);
     94        break;
     95      case "hasentries": {
     96        const hasEntry = Object.values(actual).some(num => num > 0);
     97        if (key) {
     98          ok(hasEntry, `${histId}["${key}"] has at least one entry.`);
     99        } else {
    100          ok(hasEntry, `${histId} has at least one entry.`);
    101        }
    102        break;
    103      }
    104      case "scalar": {
    105        const scalars = Services.telemetry.getSnapshotForScalars(
    106          "main",
    107          false
    108        ).parent;
    109 
    110        is(scalars[histId], expected, `${histId} correct`);
    111        break;
    112      }
    113      case "keyedscalar": {
    114        const keyedScalars = Services.telemetry.getSnapshotForKeyedScalars(
    115          "main",
    116          false
    117        ).parent;
    118        const value = keyedScalars[histId][key];
    119 
    120        msg = key ? `${histId}["${key}"] correct.` : `${histId} correct.`;
    121        is(value, expected, msg);
    122        break;
    123      }
    124    }
    125  }
    126 
    127  /**
    128   * Generate telemetry tests. You should call generateTelemetryTests("DEVTOOLS_")
    129   * from your result checking code in telemetry tests. It logs checkTelemetry
    130   * calls for all changed telemetry values.
    131   *
    132   * @param  {string} prefix
    133   *         Optionally limits results to histogram ids starting with prefix.
    134   */
    135  generateTelemetryTests(prefix = "") {
    136    // Get all histograms and scalars
    137    const histograms = Services.telemetry.getSnapshotForHistograms(
    138      "main",
    139      true
    140    ).parent;
    141    const keyedHistograms = Services.telemetry.getSnapshotForKeyedHistograms(
    142      "main",
    143      true
    144    ).parent;
    145    const scalars = Services.telemetry.getSnapshotForScalars(
    146      "main",
    147      false
    148    ).parent;
    149    const keyedScalars = Services.telemetry.getSnapshotForKeyedScalars(
    150      "main",
    151      false
    152    ).parent;
    153    const allHistograms = Object.assign(
    154      {},
    155      histograms,
    156      keyedHistograms,
    157      scalars,
    158      keyedScalars
    159    );
    160    // Get all keys
    161    const histIds = Object.keys(allHistograms).filter(histId =>
    162      histId.startsWith(prefix)
    163    );
    164 
    165    dump("=".repeat(80) + "\n");
    166    for (const histId of histIds) {
    167      const snapshot = allHistograms[histId];
    168 
    169      if (histId === histId.toLowerCase()) {
    170        if (typeof snapshot === "object") {
    171          // Keyed Scalar
    172          const keys = Object.keys(snapshot);
    173 
    174          for (const key of keys) {
    175            const value = snapshot[key];
    176 
    177            dump(
    178              `checkTelemetry("${histId}", "${key}", ${value}, "keyedscalar");\n`
    179            );
    180          }
    181        } else {
    182          // Scalar
    183          dump(`checkTelemetry("${histId}", "", ${snapshot}, "scalar");\n`);
    184        }
    185      } else if (
    186        typeof snapshot.histogram_type !== "undefined" &&
    187        typeof snapshot.values !== "undefined"
    188      ) {
    189        // Histogram
    190        const actual = snapshot.values;
    191 
    192        this.displayDataFromHistogramSnapshot(snapshot, "", histId, actual);
    193      } else {
    194        // Keyed Histogram
    195        const keys = Object.keys(snapshot);
    196 
    197        for (const key of keys) {
    198          const value = snapshot[key];
    199          const actual = value.counts;
    200 
    201          this.displayDataFromHistogramSnapshot(value, key, histId, actual);
    202        }
    203      }
    204    }
    205    dump("=".repeat(80) + "\n");
    206  }
    207 
    208  /**
    209   * Generates the inner contents of a test's checkTelemetry() method.
    210   *
    211   * @param {HistogramSnapshot} snapshot
    212   *        A snapshot of a telemetry chart obtained via getSnapshotForHistograms or
    213   *        similar.
    214   * @param {string} key
    215   *        Only used for keyed histograms. This is the key we are interested in
    216   *        checking.
    217   * @param {string} histId
    218   *        The histogram ID.
    219   * @param {Array | string | boolean} actual
    220   *        The value of the histogram data.
    221   */
    222  displayDataFromHistogramSnapshot(snapshot, key, histId, actual) {
    223    key = key ? `"${key}"` : `""`;
    224 
    225    switch (snapshot.histogram_type) {
    226      case Services.telemetry.HISTOGRAM_EXPONENTIAL:
    227      case Services.telemetry.HISTOGRAM_LINEAR: {
    228        let total = 0;
    229        for (const val of Object.values(actual)) {
    230          total += val;
    231        }
    232 
    233        if (histId.endsWith("_ENUMERATED")) {
    234          if (total > 0) {
    235            actual = actual.toSource();
    236            dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
    237          }
    238          return;
    239        }
    240 
    241        dump(`checkTelemetry("${histId}", ${key}, null, "hasentries");\n`);
    242        break;
    243      }
    244      case Services.telemetry.HISTOGRAM_BOOLEAN:
    245        actual = actual.toSource();
    246 
    247        if (actual !== "({})") {
    248          dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
    249        }
    250        break;
    251      case Services.telemetry.HISTOGRAM_FLAG:
    252        actual = actual.toSource();
    253 
    254        if (actual !== "({0:1, 1:0})") {
    255          dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
    256        }
    257        break;
    258      case Services.telemetry.HISTOGRAM_COUNT:
    259        actual = actual.toSource();
    260 
    261        dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
    262        break;
    263    }
    264  }
    265 }
    266 
    267 // "exports"... because this is a helper and not imported via require we need to
    268 // expose the three main methods that should be used by tests. The reason this
    269 // is not imported via require is because it needs access to test methods
    270 // (is, ok etc).
    271 
    272 /* eslint-disable no-unused-vars */
    273 const telemetryHelpers = new TelemetryHelpers();
    274 const generateTelemetryTests = telemetryHelpers.generateTelemetryTests;
    275 const checkTelemetry = telemetryHelpers.checkTelemetry;
    276 const startTelemetry = telemetryHelpers.startTelemetry;
    277 /* eslint-enable no-unused-vars */