/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "FOGIPC.h"

#include <cstdint>
#include <limits>
#include "mozilla/glean/fog_ffi_generated.h"
#include "mozilla/glean/ProcesstoolsMetrics.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/gfx/GPUChild.h"
#include "mozilla/gfx/GPUParent.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/glean/bindings/jog/JOG.h"
#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/glean/FOGTransportChild.h"
#include "mozilla/Hal.h"
#include "mozilla/Logging.h"
#include "mozilla/MozPromise.h"
#include "mozilla/net/SocketProcessChild.h"
#include "mozilla/net/SocketProcessParent.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProcInfo.h"
#include "mozilla/RDDChild.h"
#include "mozilla/RDDParent.h"
#include "mozilla/RDDProcessManager.h"
#include "mozilla/ipc/UtilityProcessChild.h"
#include "mozilla/ipc/UtilityProcessManager.h"
#include "mozilla/ipc/UtilityProcessParent.h"
#include "mozilla/ipc/UtilityProcessSandboxing.h"
#include "mozilla/StaticPrefs_telemetry.h"
#include "GMPPlatform.h"
#include "GMPServiceParent.h"
#include "nsIClassifiedChannel.h"
#include "nsIXULRuntime.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"

#if defined(__APPLE__) && defined(__aarch64__)
#  define HAS_PROCESS_ENERGY
#endif

using mozilla::dom::ContentParent;
using mozilla::gfx::GPUChild;
using mozilla::gfx::GPUProcessManager;
using mozilla::ipc::ByteBuf;
using mozilla::ipc::UtilityProcessChild;
using mozilla::ipc::UtilityProcessManager;
using mozilla::ipc::UtilityProcessParent;
using FlushFOGDataPromise = mozilla::dom::ContentParent::FlushFOGDataPromise;

namespace geckoprofiler::markers {

using namespace mozilla;

struct ProcessingTimeMarker {
  static constexpr Span<const char> MarkerTypeName() {
    return MakeStringSpan("ProcessingTime");
  }
  static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
                                   int64_t aDiffMs,
                                   const ProfilerString8View& aType,
                                   const ProfilerString8View& aTrackerType) {
    aWriter.IntProperty("time", aDiffMs);
    aWriter.StringProperty("label", aType);
    if (aTrackerType.Length() > 0) {
      aWriter.StringProperty("tracker", aTrackerType);
    }
  }
  static MarkerSchema MarkerTypeDisplay() {
    using MS = MarkerSchema;
    MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
    schema.AddKeyLabelFormat("time", "Recorded Time", MS::Format::Milliseconds);
    schema.AddKeyLabelFormat("tracker", "Tracker Type", MS::Format::String);
    schema.AddKeyFormat("label", MS::Format::String, MS::PayloadFlags::Hidden);
    schema.SetTooltipLabel("{marker.name} - {marker.data.label}");
    schema.SetTableLabel("{marker.data.label}: {marker.data.time}");
    return schema;
  }
};

#ifdef HAS_PROCESS_ENERGY
struct ProcessEnergyMarker {
  static constexpr Span<const char> MarkerTypeName() {
    return MakeStringSpan("ProcessEnergy");
  }
  static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
                                   int64_t aUWh,
                                   const ProfilerString8View& aType) {
    aWriter.IntProperty("energy", aUWh);
    aWriter.StringProperty("label", aType);
  }
  static MarkerSchema MarkerTypeDisplay() {
    using MS = MarkerSchema;
    MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
    schema.AddKeyLabelFormat("energy", "Energy (µWh)", MS::Format::Integer);
    schema.AddKeyFormat("label", MS::Format::String, MS::PayloadFlags::Hidden);
    schema.SetTooltipLabel("{marker.name} - {marker.data.label}");
    schema.SetTableLabel("{marker.data.label}: {marker.data.energy}µWh");
    return schema;
  }
};
#endif

}  // namespace geckoprofiler::markers

namespace mozilla::glean {

static LazyLogModule sLog("fog");

// Echoes processtools/metrics.yaml's power.wakeups_per_thread
enum ProcessType {
  eParentActive,
  eParentInactive,
  eContentForeground,
  eContentBackground,
  eGpuProcess,
  eInferenceProcess,
  eUnknown,
};

// This static global is set within RecordPowerMetrics on the main thread,
// using information only gettable on the main thread, and is read within
// RecordThreadCpuUse on any thread.
static Atomic<ProcessType> gThisProcessType(eUnknown);

#ifdef NIGHTLY_BUILD
// It is fine to call RecordThreadCpuUse during startup before the first
// RecordPowerMetrics call. In that case the parent process will be recorded
// as inactive, and other processes will be ignored (content processes start
// in the 'prealloc' type for which we don't record per-thread CPU use data).

void RecordThreadCpuUse(const nsACString& aThreadName, uint64_t aCpuTimeMs,
                        uint64_t aWakeCount) {
  ProcessType processType = gThisProcessType;

  if (processType == ProcessType::eUnknown) {
    if (XRE_IsParentProcess()) {
      // During startup we might not have gotten a RecordPowerMetrics call.
      // That's fine. Default to eParentInactive.
      processType = eParentInactive;
    } else {
      // We are not interested in per-thread CPU use data for the current
      // process type.
      return;
    }
  }

  nsAutoCString threadName(aThreadName);
  for (size_t i = 0; i < threadName.Length(); ++i) {
    const char c = threadName.CharAt(i);

    // Valid characters.
    if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c == '-' ||
        c == '_') {
      continue;
    }

    // Should only use lower case characters
    if (c >= 'A' && c <= 'Z') {
      threadName.SetCharAt(c + ('a' - 'A'), i);
      continue;
    }

    // Replace everything else with _
    threadName.SetCharAt('_', i);
  }

  if (aCpuTimeMs != 0 &&
      MOZ_LIKELY(aCpuTimeMs < std::numeric_limits<int32_t>::max())) {
    switch (processType) {
      case eParentActive:
        power_cpu_ms_per_thread::parent_active.Get(threadName)
            .Add(int32_t(aCpuTimeMs));
        break;
      case eParentInactive:
        power_cpu_ms_per_thread::parent_inactive.Get(threadName)
            .Add(int32_t(aCpuTimeMs));
        break;
      case eContentForeground:
        power_cpu_ms_per_thread::content_foreground.Get(threadName)
            .Add(int32_t(aCpuTimeMs));
        break;
      case eContentBackground:
        power_cpu_ms_per_thread::content_background.Get(threadName)
            .Add(int32_t(aCpuTimeMs));
        break;
      case eGpuProcess:
        power_cpu_ms_per_thread::gpu_process.Get(threadName)
            .Add(int32_t(aCpuTimeMs));
        break;
      case eInferenceProcess:
        power_cpu_ms_per_thread::inference_process.Get(threadName)
            .Add(int32_t(aCpuTimeMs));
        break;
      case eUnknown:
        // Nothing to do.
        break;
    }
  }

  if (aWakeCount != 0 &&
      MOZ_LIKELY(aWakeCount < std::numeric_limits<int32_t>::max())) {
    switch (processType) {
      case eParentActive:
        power_wakeups_per_thread::parent_active.Get(threadName)
            .Add(int32_t(aWakeCount));
        break;
      case eParentInactive:
        power_wakeups_per_thread::parent_inactive.Get(threadName)
            .Add(int32_t(aWakeCount));
        break;
      case eContentForeground:
        power_wakeups_per_thread::content_foreground.Get(threadName)
            .Add(int32_t(aWakeCount));
        break;
      case eContentBackground:
        power_wakeups_per_thread::content_background.Get(threadName)
            .Add(int32_t(aWakeCount));
        break;
      case eGpuProcess:
        power_wakeups_per_thread::gpu_process.Get(threadName)
            .Add(int32_t(aWakeCount));
        break;
      case eInferenceProcess:
        power_wakeups_per_thread::inference_process.Get(threadName)
            .Add(int32_t(aWakeCount));
        break;
      case eUnknown:
        // Nothing to do.
        break;
    }
  }
}
#endif

void GetTrackerType(nsAutoCString& aTrackerType) {
  using namespace mozilla::dom;
  uint32_t trackingFlags =
      (nsIClassifiedChannel::CLASSIFIED_CRYPTOMINING |
       nsIClassifiedChannel::CLASSIFIED_FINGERPRINTING |
       nsIClassifiedChannel::CLASSIFIED_TRACKING |
       nsIClassifiedChannel::CLASSIFIED_TRACKING_AD |
       nsIClassifiedChannel::CLASSIFIED_TRACKING_ANALYTICS |
       nsIClassifiedChannel::CLASSIFIED_TRACKING_SOCIAL);
  AutoTArray<RefPtr<Document>, 32> allDocuments;
  Document::GetAllInProcessDocuments(allDocuments);
  for (auto& doc : allDocuments) {
    nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
        do_QueryInterface(doc->GetChannel());
    if (classifiedChannel) {
      uint32_t classificationFlags =
          classifiedChannel->GetThirdPartyClassificationFlags();
      trackingFlags &= classificationFlags;
      if (!trackingFlags) {
        return;
      }
    }
  }

  // The if-elseif-else chain works because the tracker types listed here are
  // currently mutually exclusive and should be maintained that way by policy.
  if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_TRACKING_AD) {
    aTrackerType = "ad";
  } else if (trackingFlags ==
             nsIClassifiedChannel::CLASSIFIED_TRACKING_ANALYTICS) {
    aTrackerType = "analytics";
  } else if (trackingFlags ==
             nsIClassifiedChannel::CLASSIFIED_TRACKING_SOCIAL) {
    aTrackerType = "social";
  } else if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_CRYPTOMINING) {
    aTrackerType = "cryptomining";
  } else if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_FINGERPRINTING) {
    aTrackerType = "fingerprinting";
  } else if (trackingFlags == nsIClassifiedChannel::CLASSIFIED_TRACKING) {
    // CLASSIFIED_TRACKING means we were not able to identify the type of
    // classification.
    aTrackerType = "unknown";
  }
}

#ifdef HAS_PROCESS_ENERGY
static int64_t GetTaskEnergy() {
  task_power_info_v2_data_t task_power_info;
  mach_msg_type_number_t count = TASK_POWER_INFO_V2_COUNT;
  kern_return_t kr = task_info(mach_task_self(), TASK_POWER_INFO_V2,
                               (task_info_t)&task_power_info, &count);
  if (kr != KERN_SUCCESS) {
    return 0;
  }

  // task_energy is in nanojoules. We want microwatt-hours.
  return task_power_info.task_energy / 3.6 / 1e6;
}
#endif

void RecordPowerMetrics() {
  static uint64_t previousCpuTime = 0, previousGpuTime = 0;
#ifdef HAS_PROCESS_ENERGY
  static int64_t previousProcessEnergy = 0;
#endif

  uint64_t cpuTime, newCpuTime = 0;
  if (NS_SUCCEEDED(GetCpuTimeSinceProcessStartInMs(&cpuTime)) &&
      cpuTime > previousCpuTime) {
    newCpuTime = cpuTime - previousCpuTime;
  }

  uint64_t gpuTime, newGpuTime = 0;
  // Avoid loading gdi32.dll for the Socket process where the GPU is never used.
  if (!XRE_IsSocketProcess() &&
      NS_SUCCEEDED(GetGpuTimeSinceProcessStartInMs(&gpuTime)) &&
      gpuTime > previousGpuTime) {
    newGpuTime = gpuTime - previousGpuTime;
  }

#ifdef HAS_PROCESS_ENERGY
  int64_t processEnergy = GetTaskEnergy();
  int64_t newProcessEnergy = processEnergy - previousProcessEnergy;
#endif

  if (!newCpuTime && !newGpuTime
#ifdef HAS_PROCESS_ENERGY
      && newProcessEnergy <= 0
#endif
  ) {
    // Nothing to record.
    return;
  }

  // Compute the process type string.
  nsAutoCString type(XRE_GetProcessTypeString());
  nsAutoCString trackerType;
  if (XRE_IsContentProcess()) {
    auto* cc = mozilla::dom::ContentChild::GetSingleton();
    if (cc) {
      type.Assign(mozilla::dom::RemoteTypePrefix(cc->GetRemoteType()));
      if (StringBeginsWith(type, WEB_REMOTE_TYPE)) {
        type.AssignLiteral("web");
        switch (cc->GetProcessPriority()) {
          case hal::PROCESS_PRIORITY_BACKGROUND:
            type.AppendLiteral(".background");
            gThisProcessType = ProcessType::eContentBackground;
            break;
          case hal::PROCESS_PRIORITY_FOREGROUND:
            type.AppendLiteral(".foreground");
            gThisProcessType = ProcessType::eContentForeground;
            break;
          case hal::PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE:
            type.AppendLiteral(".background-perceivable");
            gThisProcessType = ProcessType::eUnknown;
            break;
          case hal::PROCESS_PRIORITY_PREALLOC:
            type.Assign("prealloc");
            gThisProcessType = ProcessType::eUnknown;
            break;
          default:
            MOZ_ASSERT_UNREACHABLE("Unsuppored process type for cpu time");
            break;
        }
      } else if (type == INFERENCE_REMOTE_TYPE) {
        type.AssignLiteral("inference");
        gThisProcessType = ProcessType::eInferenceProcess;
      }
      GetTrackerType(trackerType);
    } else {
      gThisProcessType = ProcessType::eUnknown;
    }
  } else if (XRE_IsParentProcess()) {
    if (nsContentUtils::GetUserIsInteracting()) {
      type.AssignLiteral("parent.active");
      gThisProcessType = ProcessType::eParentActive;
    } else {
      type.AssignLiteral("parent.inactive");
      gThisProcessType = ProcessType::eParentInactive;
    }
    hal::WakeLockInformation info;
    GetWakeLockInfo(u"video-playing"_ns, &info);
    if (info.numLocks() != 0 && info.numHidden() < info.numLocks()) {
      type.AppendLiteral(".playing-video");
    } else {
      GetWakeLockInfo(u"audio-playing"_ns, &info);
      if (info.numLocks()) {
        type.AppendLiteral(".playing-audio");
      }
    }
  } else if (XRE_IsGPUProcess()) {
    gThisProcessType = ProcessType::eGpuProcess;
  } else {
    gThisProcessType = ProcessType::eUnknown;
  }

  if (newCpuTime) {
    // The counters are reset at least once a day. Assuming all cores are used
    // continuously, an int32 can hold the data for 24.85 cores.
    // This should be fine for now, but may overflow in the future.
    // Bug 1751277 tracks a newer, bigger counter.
    int32_t nNewCpuTime = int32_t(newCpuTime);
    if (newCpuTime < std::numeric_limits<int32_t>::max()) {
      power::total_cpu_time_ms.Add(nNewCpuTime);
      power::cpu_time_per_process_type_ms.Get(type).Add(nNewCpuTime);
      if (!trackerType.IsEmpty()) {
        power::cpu_time_per_tracker_type_ms.Get(trackerType).Add(nNewCpuTime);
      }
    } else {
      power::cpu_time_bogus_values.Add(1);
    }
    PROFILER_MARKER("Process CPU Time", OTHER, {}, ProcessingTimeMarker,
                    nNewCpuTime, type, trackerType);
    previousCpuTime += newCpuTime;
  }

  if (newGpuTime) {
    int32_t nNewGpuTime = int32_t(newGpuTime);
    if (newGpuTime < std::numeric_limits<int32_t>::max()) {
      power::total_gpu_time_ms.Add(nNewGpuTime);
      power::gpu_time_per_process_type_ms.Get(type).Add(nNewGpuTime);
    } else {
      power::gpu_time_bogus_values.Add(1);
    }
    PROFILER_MARKER("Process GPU Time", OTHER, {}, ProcessingTimeMarker,
                    nNewGpuTime, type, trackerType);
    previousGpuTime += newGpuTime;
  }

#ifdef HAS_PROCESS_ENERGY
  if (newProcessEnergy) {
    power::energy_per_process_type.Get(type).Add(newProcessEnergy);
    PROFILER_MARKER("Process Energy", OTHER, {}, ProcessEnergyMarker,
                    newProcessEnergy, type);
    previousProcessEnergy += newProcessEnergy;
  }
#endif

  profiler_record_wakeup_count(type);
}

/**
 * Flush your data ASAP, either because the parent process is asking you to
 * or because the process is about to shutdown.
 *
 * @param aResolver - The function you need to call with the bincoded,
 *                    serialized payload that the Rust impl hands you.
 */
void FlushFOGData(std::function<void(ipc::ByteBuf&&)>&& aResolver) {
  // Record power metrics right before data is sent to the parent.
  RecordPowerMetrics();

  ByteBuf buf;
  uint32_t ipcBufferSize = impl::fog_serialize_ipc_buf();
  bool ok = buf.Allocate(ipcBufferSize);
  if (!ok) {
    return;
  }
  uint32_t writtenLen = impl::fog_give_ipc_buf(buf.mData, buf.mLen);
  if (writtenLen != ipcBufferSize) {
    return;
  }
  aResolver(std::move(buf));
}

/**
 * Called by FOG on the parent process when it wants to flush all its
 * children's data.
 * @param aResolver - The function that'll be called with the results.
 */
void FlushAllChildData(
    std::function<void(nsTArray<ipc::ByteBuf>&&)>&& aResolver) {
  auto timerId = fog_ipc::flush_durations.Start();
  MOZ_LOG(sLog, LogLevel::Verbose, ("glean::FlushAllChildData: start"));

  nsTArray<ContentParent*> parents;
  ContentParent::GetAll(parents);
  nsTArray<RefPtr<FlushFOGDataPromise>> promises;
  for (auto* parent : parents) {
    promises.EmplaceBack(parent->DoFlushFOGData());
  }

  if (GPUProcessManager* gpuManager = GPUProcessManager::Get()) {
    if (GPUChild* gpuChild = gpuManager->GetGPUChild()) {
      promises.EmplaceBack(gpuChild->SendFlushFOGData());
    }
  }

  if (RDDProcessManager* rddManager = RDDProcessManager::Get()) {
    if (RDDChild* rddChild = rddManager->GetRDDChild()) {
      promises.EmplaceBack(rddChild->SendFlushFOGData());
    }
  }

  if (RefPtr<net::SocketProcessParent> socketParent =
          net::SocketProcessParent::GetSingleton()) {
    promises.EmplaceBack(socketParent->SendFlushFOGData());
  }

  if (RefPtr<mozilla::gmp::GeckoMediaPluginServiceParent> gmps =
          mozilla::gmp::GeckoMediaPluginServiceParent::GetSingleton()) {
    // There can be multiple Gecko Media Plugin processes, but iterating
    // through them requires locking a mutex and the IPCs need to be sent
    // from a different thread, so it's better to let the
    // GeckoMediaPluginServiceParent code do it for us.
    gmps->SendFlushFOGData(promises);
  }

  if (RefPtr<UtilityProcessManager> utilityManager =
          UtilityProcessManager::GetIfExists()) {
    for (RefPtr<UtilityProcessParent>& parent :
         utilityManager->GetAllProcessesProcessParent()) {
      promises.EmplaceBack(parent->SendFlushFOGData());
    }
  }

  if (promises.Length() == 0) {
    // No child processes at the moment. Resolve synchronously.
    MOZ_LOG(sLog, LogLevel::Verbose,
            ("glean::FlushAllChildData: No child processes at the moment."));
    fog_ipc::flush_durations.Cancel(std::move(timerId));
    nsTArray<ipc::ByteBuf> results;
    aResolver(std::move(results));
    return;
  }

  FlushFOGDataPromise::AllSettled(GetCurrentSerialEventTarget(), promises)
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [aResolver = std::move(aResolver), timerId,
           promiseCount = promises.Length()](
              FlushFOGDataPromise::AllSettledPromiseType::ResolveOrRejectValue&&
                  aValue) {
            fog_ipc::flush_durations.StopAndAccumulate(std::move(timerId));
            if (aValue.IsResolve()) {
              MOZ_LOG(
                  sLog, LogLevel::Verbose,
                  ("glean::FlushAllChildData: AllSettled value is resolved"));
              nsTArray<ipc::ByteBuf> results;
              auto& allValues = aValue.ResolveValue();
              for (auto& value : allValues) {
                if (value.IsResolve()) {
                  MOZ_LOG(sLog, LogLevel::Verbose,
                          ("glean::FlushAllChildData: value is resolved, "
                           "appending element to results"));
                  results.AppendElement(std::move(value.ResolveValue()));
                } else {
                  MOZ_LOG(sLog, LogLevel::Verbose,
                          ("glean::FlushAllChildData: value is rejected, "
                           "appending 1 to flush rejections"));
                  fog_ipc::flush_rejections.Add(1);
                }
              }
              aResolver(std::move(results));
            } else {
              MOZ_LOG(sLog, LogLevel::Verbose,
                      ("glean::FlushAllChildData: AllSettled value is "
                       "rejected, adding %zu to flush failures count",
                       promiseCount));
              fog_ipc::flush_failures.Add((int32_t)promiseCount);
              nsTArray<ipc::ByteBuf> results;
              aResolver(std::move(results));
            }
          });
  MOZ_LOG(sLog, LogLevel::Verbose, ("glean::FlushAllChildData: end"));
}

/**
 * A child process has sent you this buf as a treat.
 * @param buf - a bincoded serialized payload that the Rust impl understands.
 */
void FOGData(ipc::ByteBuf&& buf) {
  JOG::EnsureRuntimeMetricsRegistered();
  fog_ipc::buffer_sizes.Accumulate(buf.mLen);
  impl::fog_use_ipc_buf(buf.mData, buf.mLen);
}

/**
 * Called by FOG on a child process when it wants to send a buf to the parent.
 * @param buf - a bincoded serialized payload that the Rust impl understands.
 */
void SendFOGData(ipc::ByteBuf&& buf) {
  MOZ_LOG(sLog, LogLevel::Verbose, ("glean::SendFOGData: start"));
  switch (XRE_GetProcessType()) {
    case GeckoProcessType_Content: {
      FOGTransportChild* child = FOGTransportChild::GetSingleton();
      if (child) {
        MOZ_LOG(sLog, LogLevel::Verbose,
                ("glean::SendFOGData: "
                 "FOGTransportChild::GetSingleton()->SendFOGData called"));
        child->SendFOGData(std::move(buf));
      } else {
        MOZ_LOG(sLog, LogLevel::Warning,
                ("FOGTransportChild singleton is not initialized. Falling back "
                 "to dom::ContentChild."));
        mozilla::dom::ContentChild::GetSingleton()->SendFOGData(std::move(buf));
      }
    } break;
    case GeckoProcessType_GMPlugin: {
      mozilla::gmp::SendFOGData(std::move(buf));
    } break;
    case GeckoProcessType_GPU:
      (void)mozilla::gfx::GPUParent::GetSingleton()->SendFOGData(
          std::move(buf));
      break;
    case GeckoProcessType_RDD:
      (void)mozilla::RDDParent::GetSingleton()->SendFOGData(std::move(buf));
      break;
    case GeckoProcessType_Socket:
      (void)net::SocketProcessChild::GetSingleton()->SendFOGData(
          std::move(buf));
      break;
    case GeckoProcessType_Utility:
      (void)ipc::UtilityProcessChild::GetSingleton()->SendFOGData(
          std::move(buf));
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Unsuppored process type");
  }
  MOZ_LOG(sLog, LogLevel::Verbose, ("glean::SendFOGData: end"));
}

/**
 * Called on the parent process to ask all child processes for data,
 * sending it all down into Rust to be used.
 */
RefPtr<GenericPromise> FlushAndUseFOGData() {
  MOZ_LOG(sLog, LogLevel::Verbose, ("glean::FlushAndUseFOGData: start"));
  // Record power metrics on the parent before sending requests to child
  // processes.
  RecordPowerMetrics();

  RefPtr<GenericPromise::Private> ret = new GenericPromise::Private(__func__);
  std::function<void(nsTArray<ByteBuf>&&)> resolver =
      [ret](nsTArray<ByteBuf>&& bufs) {
        for (ByteBuf& buf : bufs) {
          FOGData(std::move(buf));
        }
        ret->Resolve(true, __func__);
      };
  FlushAllChildData(std::move(resolver));
  MOZ_LOG(sLog, LogLevel::Verbose, ("glean::FlushAndUseFOGData: end"));
  return ret;
}

void TestTriggerMetrics(uint32_t aProcessType,
                        const RefPtr<mozilla::dom::Promise>& promise) {
  switch (aProcessType) {
    case nsIXULRuntime::PROCESS_TYPE_GMPLUGIN: {
      RefPtr<mozilla::gmp::GeckoMediaPluginServiceParent> gmps(
          mozilla::gmp::GeckoMediaPluginServiceParent::GetSingleton());
      gmps->TestTriggerMetrics()->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise]() { promise->MaybeResolveWithUndefined(); },
          [promise]() { promise->MaybeRejectWithUndefined(); });
    } break;
    case nsIXULRuntime::PROCESS_TYPE_GPU:
      mozilla::gfx::GPUProcessManager::Get()->TestTriggerMetrics()->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise]() { promise->MaybeResolveWithUndefined(); },
          [promise]() { promise->MaybeRejectWithUndefined(); });
      break;
    case nsIXULRuntime::PROCESS_TYPE_RDD:
      RDDProcessManager::Get()->TestTriggerMetrics()->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise]() { promise->MaybeResolveWithUndefined(); },
          [promise]() { promise->MaybeRejectWithUndefined(); });
      break;
    case nsIXULRuntime::PROCESS_TYPE_SOCKET: {
      RefPtr<net::SocketProcessParent> socketParent(
          net::SocketProcessParent::GetSingleton());
      (void)socketParent->SendTestTriggerMetrics()->Then(
          GetCurrentSerialEventTarget(), __func__,
          [promise]() { promise->MaybeResolveWithUndefined(); },
          [promise]() { promise->MaybeRejectWithUndefined(); });
    } break;
    case nsIXULRuntime::PROCESS_TYPE_UTILITY:
      (void)ipc::UtilityProcessManager::GetSingleton()
          ->GetProcessParent(ipc::SandboxingKind::GENERIC_UTILITY)
          ->SendTestTriggerMetrics()
          ->Then(
              GetCurrentSerialEventTarget(), __func__,
              [promise]() { promise->MaybeResolveWithUndefined(); },
              [promise]() { promise->MaybeRejectWithUndefined(); });
      break;
    default:
      promise->MaybeRejectWithUndefined();
      break;
  }
}

}  // namespace mozilla::glean
