forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			413 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			413 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* Any copyright is dedicated to the Public Domain.
 | 
						|
 * http://creativecommons.org/publicdomain/zero/1.0/ */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
/**
 | 
						|
 * Return a web-based URL for a given file based on the testing directory.
 | 
						|
 * @param {String} fileName
 | 
						|
 *        file that caller wants its web-based url
 | 
						|
 */
 | 
						|
function GetTestWebBasedURL(fileName) {
 | 
						|
  return (
 | 
						|
    getRootDirectory(gTestPath).replace(
 | 
						|
      "chrome://mochitests/content",
 | 
						|
      "http://example.org"
 | 
						|
    ) + fileName
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
const kNS_PER_MS = 1000000;
 | 
						|
 | 
						|
function printProcInfo(procInfo) {
 | 
						|
  info(
 | 
						|
    `  pid: ${procInfo.pid}, type = parent, cpu time = ${
 | 
						|
      procInfo.cpuTime / kNS_PER_MS
 | 
						|
    }ms`
 | 
						|
  );
 | 
						|
  for (let child of procInfo.children) {
 | 
						|
    info(
 | 
						|
      `  pid: ${child.pid}, type = ${child.type}, cpu time = ${
 | 
						|
        child.cpuTime / kNS_PER_MS
 | 
						|
      }ms`
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// It would be nice to have an API to list all the statically defined labels of
 | 
						|
// a labeled_counter. Hopefully bug 1672273 will help with this.
 | 
						|
const kGleanProcessTypeLabels = [
 | 
						|
  "parent.active",
 | 
						|
  "parent.active.playing-audio",
 | 
						|
  "parent.active.playing-video",
 | 
						|
  "parent.inactive",
 | 
						|
  "parent.inactive.playing-audio",
 | 
						|
  "parent.inactive.playing-video",
 | 
						|
  "prealloc",
 | 
						|
  "privilegedabout",
 | 
						|
  "rdd",
 | 
						|
  "socket",
 | 
						|
  "web.background",
 | 
						|
  "web.background-perceivable",
 | 
						|
  "web.foreground",
 | 
						|
  "extension",
 | 
						|
  "gpu",
 | 
						|
  "gmplugin",
 | 
						|
  "utility",
 | 
						|
  "__other__",
 | 
						|
];
 | 
						|
 | 
						|
async function getChildCpuTime(pid) {
 | 
						|
  return (await ChromeUtils.requestProcInfo()).children.find(p => p.pid == pid)
 | 
						|
    .cpuTime;
 | 
						|
}
 | 
						|
 | 
						|
add_task(async () => {
 | 
						|
  // Temporarily open a new foreground tab to make the current tab a background
 | 
						|
  // tab, and burn some CPU time in it while it's in the background.
 | 
						|
  const kBusyWaitForMs = 50;
 | 
						|
  let cpuTimeSpentOnBackgroundTab;
 | 
						|
  let firstBrowser = gBrowser.selectedTab.linkedBrowser;
 | 
						|
  let processPriorityChangedPromise = BrowserTestUtils.contentTopicObserved(
 | 
						|
    firstBrowser.browsingContext,
 | 
						|
    "ipc:process-priority-changed"
 | 
						|
  );
 | 
						|
  await BrowserTestUtils.withNewTab(
 | 
						|
    GetTestWebBasedURL("dummy.html"),
 | 
						|
    async () => {
 | 
						|
      await processPriorityChangedPromise;
 | 
						|
      // We can't be sure that a busy loop lasting for a specific duration
 | 
						|
      // will use the same amount of CPU time, as that would require a core
 | 
						|
      // to be fully available for our busy loop, which is unlikely on single
 | 
						|
      // core hardware.
 | 
						|
      // To be able to have a predictable amount of CPU time used, we need to
 | 
						|
      // check using ChromeUtils.requestProcInfo how much CPU time has actually
 | 
						|
      // been spent.
 | 
						|
      let pid = firstBrowser.frameLoader.remoteTab.osPid;
 | 
						|
      let initalCpuTime = await getChildCpuTime(pid);
 | 
						|
      let afterCpuTime;
 | 
						|
      do {
 | 
						|
        await SpecialPowers.spawn(firstBrowser, [kBusyWaitForMs], async () => {
 | 
						|
          let startTime = Date.now();
 | 
						|
          while (Date.now() - startTime < 10) {
 | 
						|
            // Burn CPU time...
 | 
						|
          }
 | 
						|
        });
 | 
						|
        afterCpuTime = await getChildCpuTime(pid);
 | 
						|
      } while (afterCpuTime - initalCpuTime < kBusyWaitForMs * kNS_PER_MS);
 | 
						|
      cpuTimeSpentOnBackgroundTab = Math.floor(
 | 
						|
        (afterCpuTime - initalCpuTime) / kNS_PER_MS
 | 
						|
      );
 | 
						|
    }
 | 
						|
  );
 | 
						|
 | 
						|
  let beforeProcInfo = await ChromeUtils.requestProcInfo();
 | 
						|
  await Services.fog.testFlushAllChildren();
 | 
						|
 | 
						|
  let cpuTimeByType = {},
 | 
						|
    gpuTimeByType = {};
 | 
						|
  for (let label of kGleanProcessTypeLabels) {
 | 
						|
    cpuTimeByType[label] =
 | 
						|
      Glean.power.cpuTimePerProcessTypeMs[label].testGetValue();
 | 
						|
    gpuTimeByType[label] =
 | 
						|
      Glean.power.gpuTimePerProcessTypeMs[label].testGetValue();
 | 
						|
  }
 | 
						|
  let totalCpuTime = Glean.power.totalCpuTimeMs.testGetValue();
 | 
						|
  let totalGpuTime = Glean.power.totalGpuTimeMs.testGetValue();
 | 
						|
 | 
						|
  let afterProcInfo = await ChromeUtils.requestProcInfo();
 | 
						|
 | 
						|
  info("CPU time from ProcInfo before calling testFlushAllChildren:");
 | 
						|
  printProcInfo(beforeProcInfo);
 | 
						|
 | 
						|
  info("CPU time for each label:");
 | 
						|
  let totalCpuTimeByType = 0;
 | 
						|
  for (let label of kGleanProcessTypeLabels) {
 | 
						|
    totalCpuTimeByType += cpuTimeByType[label] ?? 0;
 | 
						|
    info(`  ${label} = ${cpuTimeByType[label]}`);
 | 
						|
  }
 | 
						|
 | 
						|
  info("CPU time from ProcInfo after calling testFlushAllChildren:");
 | 
						|
  printProcInfo(afterProcInfo);
 | 
						|
 | 
						|
  Assert.equal(
 | 
						|
    totalCpuTimeByType,
 | 
						|
    totalCpuTime,
 | 
						|
    "The sum of CPU time used by all process types should match totalCpuTimeMs"
 | 
						|
  );
 | 
						|
 | 
						|
  // In infra the parent process time will be reported as parent.inactive,
 | 
						|
  // but when running the test locally the user might move the mouse a bit.
 | 
						|
  let parentTime =
 | 
						|
    (cpuTimeByType["parent.active"] || 0) +
 | 
						|
    (cpuTimeByType["parent.inactive"] || 0);
 | 
						|
  Assert.greaterOrEqual(
 | 
						|
    parentTime,
 | 
						|
    Math.floor(beforeProcInfo.cpuTime / kNS_PER_MS),
 | 
						|
    "reported parent cpu time should be at least what the first requestProcInfo returned"
 | 
						|
  );
 | 
						|
  Assert.lessOrEqual(
 | 
						|
    parentTime,
 | 
						|
    Math.ceil(afterProcInfo.cpuTime / kNS_PER_MS),
 | 
						|
    "reported parent cpu time should be at most what the second requestProcInfo returned"
 | 
						|
  );
 | 
						|
 | 
						|
  kGleanProcessTypeLabels
 | 
						|
    .filter(label => label.startsWith("parent.") && label.includes(".playing-"))
 | 
						|
    .forEach(label => {
 | 
						|
      Assert.strictEqual(
 | 
						|
        cpuTimeByType[label],
 | 
						|
        null,
 | 
						|
        `no media was played so the CPU time for ${label} should be null`
 | 
						|
      );
 | 
						|
    });
 | 
						|
 | 
						|
  if (beforeProcInfo.children.some(p => p.type == "preallocated")) {
 | 
						|
    Assert.greaterOrEqual(
 | 
						|
      cpuTimeByType.prealloc,
 | 
						|
      beforeProcInfo.children.reduce(
 | 
						|
        (time, p) =>
 | 
						|
          time +
 | 
						|
          (p.type == "preallocated" ? Math.floor(p.cpuTime / kNS_PER_MS) : 0),
 | 
						|
        0
 | 
						|
      ),
 | 
						|
      "reported cpu time for preallocated content processes should be at least the sum of what the first requestProcInfo returned."
 | 
						|
    );
 | 
						|
    // We can't compare with the values returned by the second requestProcInfo
 | 
						|
    // call because one preallocated content processes has been turned into
 | 
						|
    // a normal content process when we opened a tab.
 | 
						|
  } else {
 | 
						|
    info(
 | 
						|
      "no preallocated content process existed when we started our test, but some might have existed before"
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  if (beforeProcInfo.children.some(p => p.type == "privilegedabout")) {
 | 
						|
    Assert.greaterOrEqual(
 | 
						|
      cpuTimeByType.privilegedabout,
 | 
						|
      1,
 | 
						|
      "we used some CPU time in a foreground tab, but don't know how much as the process might have started as preallocated"
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  for (let label of [
 | 
						|
    "rdd",
 | 
						|
    "socket",
 | 
						|
    "extension",
 | 
						|
    "gpu",
 | 
						|
    "gmplugin",
 | 
						|
    "utility",
 | 
						|
  ]) {
 | 
						|
    if (!kGleanProcessTypeLabels.includes(label)) {
 | 
						|
      Assert.ok(
 | 
						|
        false,
 | 
						|
        `coding error in the test, ${label} isn't part of ${kGleanProcessTypeLabels.join(
 | 
						|
          ", "
 | 
						|
        )}`
 | 
						|
      );
 | 
						|
    }
 | 
						|
    if (beforeProcInfo.children.some(p => p.type == label)) {
 | 
						|
      function getCpuTime(procInfo) {
 | 
						|
        return (
 | 
						|
          procInfo.children.find(p => p.type == label).cpuTime / kNS_PER_MS
 | 
						|
        );
 | 
						|
      }
 | 
						|
      let beforeCpuTime = Math.floor(getCpuTime(beforeProcInfo));
 | 
						|
      let afterCpuTime = Math.ceil(getCpuTime(afterProcInfo));
 | 
						|
      let cpuTime = cpuTimeByType[label];
 | 
						|
      if (afterCpuTime == 0) {
 | 
						|
        Assert.equal(
 | 
						|
          cpuTime,
 | 
						|
          null,
 | 
						|
          "The " + label + " process used less than 1ms of CPU time."
 | 
						|
        );
 | 
						|
      } else if (beforeCpuTime == 0 && cpuTime === null) {
 | 
						|
        info(
 | 
						|
          "The " +
 | 
						|
            label +
 | 
						|
            " process might have used used less than 1ms of CPU time."
 | 
						|
        );
 | 
						|
      } else {
 | 
						|
        Assert.greaterOrEqual(
 | 
						|
          cpuTime,
 | 
						|
          beforeCpuTime,
 | 
						|
          "reported cpu time for " +
 | 
						|
            label +
 | 
						|
            " process should be at least what the first requestProcInfo returned."
 | 
						|
        );
 | 
						|
        Assert.lessOrEqual(
 | 
						|
          cpuTime,
 | 
						|
          afterCpuTime,
 | 
						|
          "reported cpu time for " +
 | 
						|
            label +
 | 
						|
            " process should be at most what the second requestProcInfo returned."
 | 
						|
        );
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      info(
 | 
						|
        "no " +
 | 
						|
          label +
 | 
						|
          " process existed when we started our test, but some might have existed before"
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Assert.greaterOrEqual(
 | 
						|
    cpuTimeByType["web.background"],
 | 
						|
    cpuTimeSpentOnBackgroundTab,
 | 
						|
    "web.background should be at least the time we spent."
 | 
						|
  );
 | 
						|
 | 
						|
  Assert.greaterOrEqual(
 | 
						|
    cpuTimeByType["web.foreground"],
 | 
						|
    1,
 | 
						|
    "we used some CPU time in a foreground tab, but don't know how much"
 | 
						|
  );
 | 
						|
 | 
						|
  // We only have web.background-perceivable CPU time if a muted video was
 | 
						|
  // played in a background tab.
 | 
						|
  Assert.strictEqual(
 | 
						|
    cpuTimeByType["web.background-perceivable"],
 | 
						|
    null,
 | 
						|
    "CPU time should only be recorded in the web.background-perceivable label"
 | 
						|
  );
 | 
						|
 | 
						|
  // __other__ should be null, if it is not, we have a missing label in the metrics.yaml file.
 | 
						|
  Assert.strictEqual(
 | 
						|
    cpuTimeByType.__other__,
 | 
						|
    null,
 | 
						|
    "no CPU time should be recorded in the __other__ label"
 | 
						|
  );
 | 
						|
 | 
						|
  info("GPU time for each label:");
 | 
						|
  let totalGpuTimeByType = undefined;
 | 
						|
  for (let label of kGleanProcessTypeLabels) {
 | 
						|
    if (gpuTimeByType[label] !== null) {
 | 
						|
      totalGpuTimeByType = (totalGpuTimeByType || 0) + gpuTimeByType[label];
 | 
						|
    }
 | 
						|
    info(`  ${label} = ${gpuTimeByType[label]}`);
 | 
						|
  }
 | 
						|
 | 
						|
  Assert.equal(
 | 
						|
    totalGpuTimeByType,
 | 
						|
    totalGpuTime,
 | 
						|
    "The sum of GPU time used by all process types should match totalGpuTimeMs"
 | 
						|
  );
 | 
						|
 | 
						|
  // __other__ should be null, if it is not, we have a missing label in the metrics.yaml file.
 | 
						|
  Assert.strictEqual(
 | 
						|
    gpuTimeByType.__other__,
 | 
						|
    null,
 | 
						|
    "no GPU time should be recorded in the __other__ label"
 | 
						|
  );
 | 
						|
 | 
						|
  // Now test per-thread CPU time.
 | 
						|
  // We don't test parentActive as the user is not marked active on infra.
 | 
						|
  let processTypes = [
 | 
						|
    "parentInactive",
 | 
						|
    "contentBackground",
 | 
						|
    "contentForeground",
 | 
						|
  ];
 | 
						|
  if (beforeProcInfo.children.some(p => p.type == "gpu")) {
 | 
						|
    processTypes.push("gpuProcess");
 | 
						|
  }
 | 
						|
  // The list of accepted labels is not accessible to the JS code, so test only the main thread.
 | 
						|
  const kThreadName = "geckomain";
 | 
						|
  if (AppConstants.NIGHTLY_BUILD) {
 | 
						|
    for (let processType of processTypes) {
 | 
						|
      Assert.greater(
 | 
						|
        Glean.powerCpuMsPerThread[processType][kThreadName].testGetValue(),
 | 
						|
        0,
 | 
						|
        `some CPU time should have been recorded for the ${processType} main thread`
 | 
						|
      );
 | 
						|
      Assert.greater(
 | 
						|
        Glean.powerWakeupsPerThread[processType][kThreadName].testGetValue(),
 | 
						|
        0,
 | 
						|
        `some thread wake ups should have been recorded for the ${processType} main thread`
 | 
						|
      );
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    // We are not recording per thread CPU use outside of the Nightly channel.
 | 
						|
    for (let processType of processTypes) {
 | 
						|
      Assert.equal(
 | 
						|
        Glean.powerCpuMsPerThread[processType][kThreadName].testGetValue(),
 | 
						|
        null,
 | 
						|
        `no CPU time should have been recorded for the ${processType} main thread`
 | 
						|
      );
 | 
						|
      Assert.equal(
 | 
						|
        Glean.powerWakeupsPerThread[processType][kThreadName].testGetValue(),
 | 
						|
        null,
 | 
						|
        `no thread wake ups should have been recorded for the ${processType} main thread`
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_tracker_power() {
 | 
						|
  await SpecialPowers.pushPrefEnv({
 | 
						|
    set: [
 | 
						|
      ["privacy.trackingprotection.enabled", false],
 | 
						|
      ["privacy.trackingprotection.annotate_channels", true],
 | 
						|
    ],
 | 
						|
  });
 | 
						|
  let initialValues = [];
 | 
						|
  for (let trackerType of [
 | 
						|
    "ad",
 | 
						|
    "analytics",
 | 
						|
    "cryptomining",
 | 
						|
    "fingerprinting",
 | 
						|
    "social",
 | 
						|
    "unknown",
 | 
						|
  ]) {
 | 
						|
    initialValues[trackerType] =
 | 
						|
      Glean.power.cpuTimePerTrackerTypeMs[trackerType].testGetValue() || 0;
 | 
						|
  }
 | 
						|
 | 
						|
  await BrowserTestUtils.withNewTab(
 | 
						|
    GetTestWebBasedURL("dummy.html"),
 | 
						|
    async () => {
 | 
						|
      // Load a tracker in a subframe, as we only record CPU time used by third party trackers.
 | 
						|
      await SpecialPowers.spawn(
 | 
						|
        gBrowser.selectedTab.linkedBrowser,
 | 
						|
        [
 | 
						|
          GetTestWebBasedURL("dummy.html").replace(
 | 
						|
            "example.org",
 | 
						|
            "trackertest.org"
 | 
						|
          ),
 | 
						|
        ],
 | 
						|
        async frameUrl => {
 | 
						|
          let iframe = content.document.createElement("iframe");
 | 
						|
          iframe.setAttribute("src", frameUrl);
 | 
						|
          await new content.Promise(resolve => {
 | 
						|
            iframe.onload = resolve;
 | 
						|
            content.document.body.appendChild(iframe);
 | 
						|
          });
 | 
						|
        }
 | 
						|
      );
 | 
						|
    }
 | 
						|
  );
 | 
						|
 | 
						|
  await Services.fog.testFlushAllChildren();
 | 
						|
 | 
						|
  let unknownTrackerCPUTime =
 | 
						|
    Glean.power.cpuTimePerTrackerTypeMs.unknown.testGetValue() || 0;
 | 
						|
  Assert.greater(
 | 
						|
    unknownTrackerCPUTime,
 | 
						|
    initialValues.unknown,
 | 
						|
    "The CPU time of unknown trackers should have increased"
 | 
						|
  );
 | 
						|
 | 
						|
  for (let trackerType of [
 | 
						|
    "ad",
 | 
						|
    "analytics",
 | 
						|
    "cryptomining",
 | 
						|
    "fingerprinting",
 | 
						|
    "social",
 | 
						|
  ]) {
 | 
						|
    Assert.equal(
 | 
						|
      Glean.power.cpuTimePerTrackerTypeMs[trackerType].testGetValue() || 0,
 | 
						|
      initialValues[trackerType],
 | 
						|
      `no new CPU time should have been recorded for ${trackerType} trackers`
 | 
						|
    );
 | 
						|
  }
 | 
						|
});
 |