tor-browser

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

harness.js (8041B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 // Global defaults
      6 
      7 // Allocate this much "garbage" per frame. This might correspond exactly to a
      8 // number of objects/values, or it might be some set of objects, depending on
      9 // the mutator in question.
     10 var gDefaultGarbagePerFrame = "8K";
     11 
     12 // In order to avoid a performance cliff between when the per-frame garbage
     13 // fits in the nursery and when it doesn't, most mutators will collect multiple
     14 // "piles" of garbage and round-robin through them, so that the per-frame
     15 // garbage stays alive for some number of frames. There will still be some
     16 // internal temporary allocations that don't end up in the piles; presumably,
     17 // the nursery will take care of those.
     18 //
     19 // If the per-frame garbage is K and the number of piles is P, then some of the
     20 // garbage will start getting tenured as long as P*K > size(nursery).
     21 var gDefaultGarbagePiles = "8";
     22 
     23 var gDefaultTestDuration = 8.0;
     24 
     25 // The Host interface that provides functionality needed by the test harnesses
     26 // (web + various shells). Subclasses should override with the appropriate
     27 // functionality. The methods that throw an error must be implemented. The ones
     28 // that return undefined are optional.
     29 //
     30 // Note that currently the web UI doesn't really use the scheduling pieces of
     31 // this.
     32 var Host = class {
     33  constructor() {}
     34  start_turn() {
     35    throw new Error("unimplemented");
     36  }
     37  end_turn() {
     38    throw new Error("unimplemented");
     39  }
     40  suspend(duration) {
     41    throw new Error("unimplemented");
     42  } // Shell driver only
     43  now() {
     44    return performance.now();
     45  }
     46 
     47  minorGCCount() {
     48    return undefined;
     49  }
     50  majorGCCount() {
     51    return undefined;
     52  }
     53  GCSliceCount() {
     54    return undefined;
     55  }
     56 
     57  features = {
     58    haveMemorySizes: false,
     59    haveGCCounts: false,
     60  };
     61 };
     62 
     63 function percent(x) {
     64  return `${(x*100).toFixed(2)}%`;
     65 }
     66 
     67 function parse_units(v) {
     68  if (!v.length) {
     69    return NaN;
     70  }
     71  var lastChar = v[v.length - 1].toLowerCase();
     72  if (!isNaN(parseFloat(lastChar))) {
     73    return parseFloat(v);
     74  }
     75  var units = parseFloat(v.substr(0, v.length - 1));
     76  if (lastChar == "k") {
     77    return units * 1e3;
     78  }
     79  if (lastChar == "m") {
     80    return units * 1e6;
     81  }
     82  if (lastChar == "g") {
     83    return units * 1e9;
     84  }
     85  return NaN;
     86 }
     87 
     88 var AllocationLoad = class {
     89  constructor(info, name) {
     90    this.load = info;
     91    this.load.name = this.load.name ?? name;
     92 
     93    this._garbagePerFrame =
     94      info.garbagePerFrame ||
     95      parse_units(info.defaultGarbagePerFrame || gDefaultGarbagePerFrame);
     96    this._garbagePiles =
     97      info.garbagePiles ||
     98      parse_units(info.defaultGarbagePiles || gDefaultGarbagePiles);
     99  }
    100 
    101  get name() {
    102    return this.load.name;
    103  }
    104  get description() {
    105    return this.load.description;
    106  }
    107  get garbagePerFrame() {
    108    return this._garbagePerFrame;
    109  }
    110  set garbagePerFrame(amount) {
    111    this._garbagePerFrame = amount;
    112  }
    113  get garbagePiles() {
    114    return this._garbagePiles;
    115  }
    116  set garbagePiles(amount) {
    117    this._garbagePiles = amount;
    118  }
    119 
    120  start() {
    121    this.load.load(this._garbagePiles);
    122  }
    123 
    124  stop() {
    125    this.load.unload();
    126  }
    127 
    128  reload() {
    129    this.stop();
    130    this.start();
    131  }
    132 
    133  tick() {
    134    this.load.makeGarbage(this._garbagePerFrame);
    135  }
    136 
    137  is_dummy_load() {
    138    return this.load.name == "noAllocation";
    139  }
    140 };
    141 
    142 var AllocationLoadManager = class {
    143  constructor(tests) {
    144    this._loads = new Map();
    145    for (const [name, info] of tests.entries()) {
    146      this._loads.set(name, new AllocationLoad(info, name));
    147    }
    148    this._active = undefined;
    149    this._paused = false;
    150 
    151    // Public API
    152    this.sequencer = null;
    153    this.testDurationMS = gDefaultTestDuration * 1000;
    154  }
    155 
    156  getByName(name) {
    157    const mutator = this._loads.get(name);
    158    if (!mutator) {
    159      throw new Error(`invalid mutator '${name}'`);
    160    }
    161    return mutator;
    162  }
    163 
    164  activeLoad() {
    165    return this._active;
    166  }
    167 
    168  setActiveLoad(mutator) {
    169    if (this._active) {
    170      this._active.stop();
    171    }
    172    this._active = mutator;
    173    this._active.start();
    174  }
    175 
    176  deactivateLoad() {
    177    this._active.stop();
    178    this._active = undefined;
    179  }
    180 
    181  get paused() {
    182    return this._paused;
    183  }
    184  set paused(pause) {
    185    this._paused = pause;
    186  }
    187 
    188  load_running() {
    189    return this._active;
    190  }
    191 
    192  change_garbagePiles(amount) {
    193    if (this._active) {
    194      this._active.garbagePiles = amount;
    195      this._active.reload();
    196    }
    197  }
    198 
    199  change_garbagePerFrame(amount) {
    200    if (this._active) {
    201      this._active.garbagePerFrame = amount;
    202    }
    203  }
    204 
    205  tick(now = gHost.now()) {
    206    this.lastActive = this._active;
    207    let completed = false;
    208 
    209    if (this.sequencer) {
    210      if (this.sequencer.tick(now)) {
    211        completed = true;
    212        if (this.sequencer.current) {
    213          this.setActiveLoad(this.sequencer.current);
    214        } else {
    215          this.deactivateLoad();
    216        }
    217        if (this.sequencer.done()) {
    218          this.sequencer = null;
    219        }
    220      }
    221    }
    222 
    223    if (this._active && !this._paused) {
    224      this._active.tick();
    225    }
    226 
    227    return completed;
    228  }
    229 
    230  startSequencer(sequencer, now = gHost.now()) {
    231    this.sequencer = sequencer;
    232    this.sequencer.start(now);
    233    this.setActiveLoad(this.sequencer.current);
    234  }
    235 
    236  stopped() {
    237    return !this.sequencer || this.sequencer.done();
    238  }
    239 
    240  currentLoadRemaining(now = gHost.now()) {
    241    if (this.stopped()) {
    242      return 0;
    243    }
    244 
    245    // TODO: The web UI displays a countdown to the end of the current mutator.
    246    // This won't work for potential future things like "run until 3 major GCs
    247    // have been seen", so the API will need to be modified to provide
    248    // information in that case.
    249    return this.testDurationMS - this.sequencer.currentLoadElapsed(now);
    250  }
    251 };
    252 
    253 // Current test state.
    254 var gLoadMgr = undefined;
    255 
    256 function format_with_units(n, label, shortlabel, kbase) {
    257  function format(n, prefix, unit) {
    258    let s = Number.isInteger(n) ? n.toString() : n.toFixed(2);
    259    return `${s}${prefix}${unit}`;
    260  }
    261 
    262  if (n < kbase * 4) {
    263    return `${n} ${label}`;
    264  } else if (n < kbase ** 2 * 4) {
    265    return format(n / kbase, 'K', shortlabel);
    266  } else if (n < kbase ** 3 * 4) {
    267    return format(n / kbase ** 2, 'M', shortlabel);
    268  }
    269  return format(n / kbase ** 3, 'G', shortlabel);
    270 }
    271 
    272 function format_bytes(bytes) {
    273  return format_with_units(bytes, "bytes", "B", 1024);
    274 }
    275 
    276 function format_num(n) {
    277  return format_with_units(n, "", "", 1000);
    278 }
    279 
    280 function update_histogram(histogram, delay) {
    281  // Round to a whole number of 10us intervals to provide enough resolution to
    282  // capture a 16ms target with adequate accuracy.
    283  delay = Math.round(delay * 100) / 100;
    284  var current = histogram.has(delay) ? histogram.get(delay) : 0;
    285  histogram.set(delay, ++current);
    286 }
    287 
    288 // Compute a score based on the total ms we missed frames by per second.
    289 function compute_test_score(histogram) {
    290  var score = 0;
    291  for (let [delay, count] of histogram) {
    292    score += Math.abs((delay - 1000 / 60) * count);
    293  }
    294  score = score / (gLoadMgr.testDurationMS / 1000);
    295  return Math.round(score * 1000) / 1000;
    296 }
    297 
    298 // Build a spark-lines histogram for the test results to show with the aggregate score.
    299 function compute_spark_histogram_percents(histogram) {
    300  var ranges = [
    301    [-99999999, 16.6],
    302    [16.6, 16.8],
    303    [16.8, 25],
    304    [25, 33.4],
    305    [33.4, 60],
    306    [60, 100],
    307    [100, 300],
    308    [300, 99999999],
    309  ];
    310  var rescaled = new Map();
    311  for (let [delay] of histogram) {
    312    for (var i = 0; i < ranges.length; ++i) {
    313      var low = ranges[i][0];
    314      var high = ranges[i][1];
    315      if (low <= delay && delay < high) {
    316        update_histogram(rescaled, i);
    317        break;
    318      }
    319    }
    320  }
    321  var total = 0;
    322  for (const [, count] of rescaled) {
    323    total += count;
    324  }
    325 
    326  var spark = [];
    327  for (let i = 0; i < ranges.length; ++i) {
    328    const amt = rescaled.has(i) ? rescaled.get(i) : 0;
    329    spark.push(amt / total);
    330  }
    331 
    332  return spark;
    333 }