"use strict";

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

var cpuThreadCount;

add_task(async function setup() {
  // FOG needs a profile directory to put its data in.
  do_get_profile();

  Services.fog.initializeFOG();

  cpuThreadCount = (await Services.sysinfo.processInfo).count;
});

async function getCpuTimeFromProcInfo() {
  const NS_PER_MS = 1000000;
  let cpuTimeForProcess = p => p.cpuTime / NS_PER_MS;
  let procInfo = await ChromeUtils.requestProcInfo();
  return (
    cpuTimeForProcess(procInfo) +
    procInfo.children.map(cpuTimeForProcess).reduce((a, b) => a + b, 0)
  );
}

add_task(
  {
    // Bug 1917426 - for unknown reasons this does not work in Thunderbird
    // on automation (but works locally).
    skip_if: () =>
      AppConstants.MOZ_APP_NAME == "thunderbird" &&
      AppConstants.platform == "win",
  },
  async function test_totalCpuTime_in_parent() {
    let startTime = Date.now();

    let initialProcInfoCpuTime = Math.floor(await getCpuTimeFromProcInfo());
    await Services.fog.testFlushAllChildren();

    let initialCpuTime = Glean.power.totalCpuTimeMs.testGetValue();
    Assert.greater(
      initialCpuTime,
      0,
      "The CPU time used by starting the test harness should be more than 0"
    );
    let initialProcInfoCpuTime2 = Math.ceil(await getCpuTimeFromProcInfo());

    Assert.greaterOrEqual(
      initialCpuTime,
      initialProcInfoCpuTime,
      "The CPU time reported through Glean should be at least as much as the CPU time reported by ProcInfo before asking Glean for the data"
    );
    Assert.lessOrEqual(
      initialCpuTime,
      initialProcInfoCpuTime2,
      "The CPU time reported through Glean should be no more than the CPU time reported by ProcInfo after asking Glean for the data"
    );

    // 50 is an arbitrary value, but the resolution is 16ms on Windows,
    // so this needs to be significantly more than 16.
    const kBusyWaitForMs = 50;
    while (Date.now() - startTime < kBusyWaitForMs) {
      // Burn CPU time...
    }

    let additionalProcInfoCpuTime =
      Math.floor(await getCpuTimeFromProcInfo()) - initialProcInfoCpuTime2;
    await Services.fog.testFlushAllChildren();

    let additionalCpuTime =
      Glean.power.totalCpuTimeMs.testGetValue() - initialCpuTime;
    info(
      `additional CPU time according to ProcInfo: ${additionalProcInfoCpuTime}ms and Glean ${additionalCpuTime}ms`
    );

    // On a machine where the CPU is very busy, our busy wait loop might burn less
    // CPU than expected if other processes are being scheduled instead of us.
    let expectedAdditionalCpuTime = Math.min(
      additionalProcInfoCpuTime,
      kBusyWaitForMs
    );
    Assert.greaterOrEqual(
      additionalCpuTime,
      expectedAdditionalCpuTime,
      `The total CPU time should have increased by at least ${expectedAdditionalCpuTime}ms`
    );
    let wallClockTime = Date.now() - startTime;
    Assert.lessOrEqual(
      additionalCpuTime,
      wallClockTime * cpuThreadCount,
      `The total CPU time should have increased by at most the wall clock time (${wallClockTime}ms) * ${cpuThreadCount} CPU threads`
    );
  }
);

add_task(async function test_totalCpuTime_in_child() {
  const MESSAGE_CHILD_TEST_DONE = "ChildTest:Done";

  let startTime = Date.now();
  await Services.fog.testFlushAllChildren();
  let initialCpuTime = Glean.power.totalCpuTimeMs.testGetValue();

  let initialProcInfoCpuTime = await getCpuTimeFromProcInfo();
  run_test_in_child("test_total_cpu_time_child.js");
  let burntCpuTime = await do_await_remote_message(MESSAGE_CHILD_TEST_DONE);
  let additionalProcInfoCpuTime =
    (await getCpuTimeFromProcInfo()) - initialProcInfoCpuTime;

  await Services.fog.testFlushAllChildren();
  let additionalCpuTime =
    Glean.power.totalCpuTimeMs.testGetValue() - initialCpuTime;
  info(
    `additional CPU time according to ProcInfo: ${additionalProcInfoCpuTime}ms and Glean ${additionalCpuTime}ms`
  );

  // On a machine where the CPU is very busy, our busy wait loop might burn less
  // CPU than expected if other processes are being scheduled instead of us.
  let expectedAdditionalCpuTime = Math.min(
    Math.floor(additionalProcInfoCpuTime),
    burntCpuTime
  );
  Assert.greaterOrEqual(
    additionalCpuTime,
    expectedAdditionalCpuTime,
    `The total CPU time should have increased by at least ${expectedAdditionalCpuTime}ms`
  );
  let wallClockTime = Date.now() - startTime;
  Assert.lessOrEqual(
    additionalCpuTime,
    wallClockTime * cpuThreadCount,
    `The total CPU time should have increased by at most the wall clock time (${wallClockTime}ms) * ${cpuThreadCount} CPU threads`
  );
});
