tor-browser

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

support_measurements.js (9335B)


      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
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /* eslint-env node */
      6 /* eslint-disable mozilla/avoid-Date-timing */
      7 
      8 const os = require("os");
      9 const path = require("path");
     10 const fs = require("fs");
     11 
     12 const usbPowerProfiler = require(
     13  path.join(
     14    process.env.BROWSERTIME_ROOT,
     15    "node_modules",
     16    "usb-power-profiling",
     17    "usb-power-profiling.js"
     18  )
     19 );
     20 
     21 const {
     22  gatherWindowsPowerUsage,
     23  getBrowsertimeResultsPath,
     24  startWindowsPowerProfiling,
     25  stopWindowsPowerProfiling,
     26 } = require("./profiling");
     27 
     28 class SupportMeasurements {
     29  constructor(context, commands, measureCPU, measurePower, measureTime) {
     30    this.context = context;
     31    this.commands = commands;
     32    this.testTimes = [];
     33 
     34    this.isWindows11 =
     35      os.type() == "Windows_NT" && /10.0.2[2-9]/.test(os.release());
     36 
     37    this.isAndroid =
     38      context.options.android && context.options.android.enabled == "true";
     39    this.application = context.options.browser;
     40 
     41    if (this.isAndroid) {
     42      if (this.application == "firefox") {
     43        this.androidPackage = this.context.options.firefox.android.package;
     44      } else if (this.application == "chrome") {
     45        this.androidPackage = "com.android.chrome";
     46      } else {
     47        this.androidPackage = this.context.options.chrome.android.package;
     48      }
     49    }
     50 
     51    this.measurementMap = {
     52      cpuTime: {
     53        run: measureCPU,
     54        initialize: null,
     55        start: "_startMeasureCPU",
     56        stop: "_stopMeasureCPU",
     57        finalize: null,
     58      },
     59      powerUsageSupport: {
     60        run: measurePower,
     61        initialize: "_initializeMeasurePower",
     62        start: "_startMeasurePower",
     63        stop: "_stopMeasurePower",
     64        finalize: "_finalizeMeasurePower",
     65      },
     66      "wallclock-for-tracking-only": {
     67        run: measureTime,
     68        initialize: null,
     69        start: "_startMeasureTime",
     70        stop: "_stopMeasureTime",
     71        finalize: null,
     72      },
     73    };
     74  }
     75 
     76  async _gatherAndroidCPUTimes() {
     77    this.processIDs = await this.commands.android.shell(
     78      `pgrep -f "${this.androidPackage}"`
     79    );
     80 
     81    let processTimes = {};
     82    for (let processID of this.processIDs.split("\n")) {
     83      let processTimeInfo = (
     84        await this.commands.android.shell(`ps -p ${processID} -o name=,time=`)
     85      ).trim();
     86 
     87      if (!processTimeInfo) {
     88        // Sometimes a processID returns empty info
     89        continue;
     90      }
     91 
     92      let nameAndTime = processTimeInfo.split(" ");
     93      nameAndTime.forEach(el => el.trim());
     94 
     95      let hmsTime = nameAndTime[nameAndTime.length - 1].split(":");
     96      processTimes[nameAndTime[0]] =
     97        parseInt(hmsTime[0], 10) * 60 * 60 +
     98        parseInt(hmsTime[1], 10) * 60 +
     99        parseInt(hmsTime[2], 10);
    100    }
    101 
    102    return processTimes;
    103  }
    104 
    105  async _startMeasureCPU() {
    106    this.context.log.info("Starting CPU Time measurements");
    107    if (!this.isAndroid) {
    108      this.startCPUTimes = os.cpus().map(c => c.times);
    109    } else {
    110      this.startCPUTimes = await this._gatherAndroidCPUTimes();
    111    }
    112  }
    113 
    114  async _stopMeasureCPU(measurementName) {
    115    let totalTime = 0;
    116 
    117    if (!this.isAndroid) {
    118      let endCPUTimes = os.cpus().map(c => c.times);
    119      totalTime = endCPUTimes
    120        .map(
    121          (times, i) =>
    122            times.user -
    123            this.startCPUTimes[i].user +
    124            (times.sys - this.startCPUTimes[i].sys)
    125        )
    126        .reduce((currSum, val) => currSum + val, 0);
    127    } else {
    128      let endCPUTimes = await this._gatherAndroidCPUTimes();
    129 
    130      for (let processName in endCPUTimes) {
    131        if (Object.hasOwn(this.startCPUTimes, processName)) {
    132          totalTime +=
    133            endCPUTimes[processName] - this.startCPUTimes[processName];
    134        } else {
    135          // Assumes that the process was started during the test
    136          totalTime += endCPUTimes[processName];
    137        }
    138      }
    139 
    140      // Convert to ms
    141      totalTime = totalTime * 1000;
    142    }
    143 
    144    this.context.log.info(`Total CPU time: ${totalTime}ms`);
    145    this.commands.measure.addObject({
    146      [measurementName]: [totalTime],
    147    });
    148  }
    149 
    150  async _initializeMeasurePower() {
    151    this.context.log.info("Initializing power usage measurements");
    152    if (this.isAndroid) {
    153      await usbPowerProfiler.startSampling();
    154    } else if (this.isWindows11) {
    155      await startWindowsPowerProfiling(this.context.index);
    156    }
    157  }
    158 
    159  async _startMeasurePower() {
    160    this.context.log.info("Starting power usage measurements");
    161    this.startPowerTime = Date.now();
    162  }
    163 
    164  async _stopMeasurePower(measurementName) {
    165    this.context.log.info("Taking power usage measurements");
    166    if (this.isAndroid) {
    167      let powerUsageData = await usbPowerProfiler.getPowerData(
    168        this.startPowerTime,
    169        Date.now()
    170      );
    171      let powerUsage = powerUsageData[0].samples.data.reduce(
    172        (currSum, currVal) => currSum + Number.parseInt(currVal[1]),
    173        0
    174      );
    175 
    176      const powerProfile = await usbPowerProfiler.profileFromData();
    177      const browsertimeResultsPath = await getBrowsertimeResultsPath(
    178        this.context,
    179        this.commands,
    180        true
    181      );
    182 
    183      const data = JSON.stringify(powerProfile, undefined, 2);
    184      await fs.promises.writeFile(
    185        path.join(
    186          browsertimeResultsPath,
    187          `profile_power_${this.context.index}.json`
    188        ),
    189        data
    190      );
    191 
    192      this.commands.measure.addObject({
    193        [measurementName]: [powerUsage],
    194      });
    195    } else if (this.isWindows11) {
    196      this.testTimes.push([this.startPowerTime, Date.now()]);
    197    }
    198  }
    199 
    200  async _finalizeMeasurePower() {
    201    this.context.log.info("Finalizing power usage measurements");
    202    if (this.isAndroid) {
    203      await usbPowerProfiler.stopSampling();
    204      await usbPowerProfiler.resetPowerData();
    205    } else if (this.isWindows11) {
    206      await stopWindowsPowerProfiling();
    207 
    208      let powerData = await gatherWindowsPowerUsage(this.testTimes);
    209      powerData.forEach((powerUsage, ind) => {
    210        if (!this.commands.measure.result[ind].extras.powerUsageSupport) {
    211          this.commands.measure.result[ind].extras.powerUsageSupport = [];
    212        }
    213        this.commands.measure.result[ind].extras.powerUsageSupport.push({
    214          powerUsageSupport: powerUsage,
    215        });
    216      });
    217    }
    218  }
    219 
    220  async _startMeasureTime() {
    221    this.context.log.info("Starting wallclock measurement");
    222    this.startTime = performance.now();
    223  }
    224 
    225  async _stopMeasureTime(measurementName) {
    226    this.context.log.info("Taking wallclock measurement");
    227    this.commands.measure.addObject({
    228      [measurementName]: [
    229        parseFloat((performance.now() - this.startTime).toFixed(2)),
    230      ],
    231    });
    232  }
    233 
    234  async reset(context, commands) {
    235    this.testTimes = [];
    236    this.context = context;
    237    this.commands = commands;
    238  }
    239 
    240  async initialize() {
    241    for (let measurementName in this.measurementMap) {
    242      let measurementInfo = this.measurementMap[measurementName];
    243      if (!(measurementInfo.run && measurementInfo.initialize)) {
    244        continue;
    245      }
    246      await this[measurementInfo.initialize](measurementName);
    247    }
    248  }
    249 
    250  async start() {
    251    for (let measurementName in this.measurementMap) {
    252      let measurementInfo = this.measurementMap[measurementName];
    253      if (!(measurementInfo.run && measurementInfo.start)) {
    254        continue;
    255      }
    256      await this[measurementInfo.start](measurementName);
    257    }
    258  }
    259 
    260  async stop() {
    261    for (let measurementName in this.measurementMap) {
    262      let measurementInfo = this.measurementMap[measurementName];
    263      if (!(measurementInfo.run && measurementInfo.stop)) {
    264        continue;
    265      }
    266      await this[measurementInfo.stop](measurementName);
    267    }
    268  }
    269 
    270  async finalize() {
    271    for (let measurementName in this.measurementMap) {
    272      let measurementInfo = this.measurementMap[measurementName];
    273      if (!(measurementInfo.run && measurementInfo.finalize)) {
    274        continue;
    275      }
    276      await this[measurementInfo.finalize](measurementName);
    277    }
    278  }
    279 }
    280 
    281 let supportMeasurementObj;
    282 async function initializeMeasurements(
    283  context,
    284  commands,
    285  measureCPU,
    286  measurePower,
    287  measureTime
    288 ) {
    289  if (!supportMeasurementObj) {
    290    supportMeasurementObj = new SupportMeasurements(
    291      context,
    292      commands,
    293      measureCPU,
    294      measurePower,
    295      measureTime
    296    );
    297  }
    298 
    299  await supportMeasurementObj.initialize();
    300 }
    301 
    302 async function startMeasurements(context, commands) {
    303  if (!supportMeasurementObj) {
    304    throw new Error(
    305      "initializeMeasurements must be called before startMeasurements"
    306    );
    307  }
    308 
    309  await supportMeasurementObj.reset(context, commands);
    310  await supportMeasurementObj.start();
    311 }
    312 
    313 async function stopMeasurements() {
    314  if (!supportMeasurementObj) {
    315    throw new Error(
    316      "initializeMeasurements must be called before stopMeasurements"
    317    );
    318  }
    319  await supportMeasurementObj.stop();
    320 }
    321 
    322 async function finalizeMeasurements() {
    323  if (!supportMeasurementObj) {
    324    throw new Error(
    325      "initializeMeasurements must be called before finalizeMeasurements"
    326    );
    327  }
    328  await supportMeasurementObj.finalize();
    329 }
    330 
    331 module.exports = {
    332  SupportMeasurements,
    333  initializeMeasurements,
    334  startMeasurements,
    335  stopMeasurements,
    336  finalizeMeasurements,
    337 };