// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/devtools/protocol/system_info_handler.h"

#include <stdint.h>

#include <utility>

#include "arkweb/build/features/features.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ref.h"
#include "base/notreached.h"
#include "base/process/process_metrics.h"
#include "base/strings/utf_string_conversions.h"
#include "base/types/expected.h"
#include "build/build_config.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/gpu/gpu_data_manager_impl.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "content/public/browser/browser_child_process_host.h"
#include "content/public/browser/browser_child_process_host_iterator.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/render_process_host.h"
#include "gpu/config/gpu_feature_type.h"
#include "gpu/config/gpu_info.h"
#include "gpu/config/gpu_switches.h"
#include "media/base/video_codecs.h"

namespace content {
namespace protocol {

namespace {

using SystemInfo::GPUDevice;
using SystemInfo::GPUInfo;
using GetInfoCallback = SystemInfo::Backend::GetInfoCallback;

std::unique_ptr<SystemInfo::Size> GfxSizeToSystemInfoSize(
    const gfx::Size& size) {
  return SystemInfo::Size::Create()
      .SetWidth(size.width())
      .SetHeight(size.height())
      .Build();
}

// Give the GPU process a few seconds to provide GPU info.

// Linux and ChromeOS Debug builds need more time -- see Issue 796437,
// 1046598, and 1153667.
// Windows builds need more time -- see Issue 873112 and 1004472.
// Mac builds need more time - see Issue angleproject:6182.
#if ((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && !defined(NDEBUG)) || \
    BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_OZONE)
static constexpr int kGPUInfoWatchdogTimeoutMultiplierOS = 3;
#else
static constexpr int kGPUInfoWatchdogTimeoutMultiplierOS = 1;
#endif

// ASAN builds need more time -- see Issue 1167875 and 1242771.
#ifdef ADDRESS_SANITIZER
static constexpr int kGPUInfoWatchdogTimeoutMultiplierASAN = 3;
#else
static constexpr int kGPUInfoWatchdogTimeoutMultiplierASAN = 1;
#endif

// Base increased from 5000 to 10000 -- see Issue 1220072.
static constexpr int kGPUInfoWatchdogTimeoutMs =
    10000 * kGPUInfoWatchdogTimeoutMultiplierOS *
    kGPUInfoWatchdogTimeoutMultiplierASAN;

class AuxGPUInfoEnumerator : public gpu::GPUInfo::Enumerator {
 public:
  explicit AuxGPUInfoEnumerator(base::Value::Dict* dictionary)
      : dictionary_(*dictionary) {}

 private:
  template <typename T>
  void MaybeSetAuxAttribute(const char* name, T value) {
    if (in_aux_attributes_)
      dictionary_->Set(name, value);
  }

  void AddInt64(const char* name, int64_t value) override {
    AddInt(name, value);
  }
  void AddInt(const char* name, int value) override {
    MaybeSetAuxAttribute(name, value);
  }
  void AddString(const char* name, const std::string& value) override {
    MaybeSetAuxAttribute(name, value);
  }
  void AddBool(const char* name, bool value) override {
    MaybeSetAuxAttribute(name, value);
  }
  void AddTimeDeltaInSecondsF(const char* name,
                              const base::TimeDelta& value) override {
    MaybeSetAuxAttribute(name, value.InSecondsF());
  }

  void AddBinary(const char* name,
                 const base::span<const uint8_t>& value) override {
    // TODO(penghuang): send vulkan info to DevTools.
  }

  void BeginGPUDevice() override {}

  void EndGPUDevice() override {}

  void BeginVideoDecodeAcceleratorSupportedProfile() override {}

  void EndVideoDecodeAcceleratorSupportedProfile() override {}

  void BeginVideoEncodeAcceleratorSupportedProfile() override {}

  void EndVideoEncodeAcceleratorSupportedProfile() override {}

  void BeginOverlayInfo() override {}

  void EndOverlayInfo() override {}

  void BeginAuxAttributes() override {
    in_aux_attributes_ = true;
  }

  void EndAuxAttributes() override {
    in_aux_attributes_ = false;
  }

  const raw_ref<protocol::DictionaryValue, DanglingUntriaged> dictionary_;
  bool in_aux_attributes_ = false;
};

std::unique_ptr<GPUDevice> GPUDeviceToProtocol(
    const gpu::GPUInfo::GPUDevice& device) {
  return GPUDevice::Create()
      .SetVendorId(device.vendor_id)
      .SetDeviceId(device.device_id)
#if BUILDFLAG(IS_WIN)
      .SetSubSysId(device.sub_sys_id)
      .SetRevision(device.revision)
#endif
      .SetVendorString(device.vendor_string)
      .SetDeviceString(device.device_string)
      .SetDriverVendor(device.driver_vendor)
      .SetDriverVersion(device.driver_version)
      .Build();
}

std::unique_ptr<SystemInfo::VideoDecodeAcceleratorCapability>
VideoDecodeAcceleratorSupportedProfileToProtocol(
    const gpu::VideoDecodeAcceleratorSupportedProfile& profile) {
  return SystemInfo::VideoDecodeAcceleratorCapability::Create()
      .SetProfile(media::GetProfileName(
          static_cast<media::VideoCodecProfile>(profile.profile)))
      .SetMaxResolution(GfxSizeToSystemInfoSize(profile.max_resolution))
      .SetMinResolution(GfxSizeToSystemInfoSize(profile.min_resolution))
      .Build();
}

std::unique_ptr<SystemInfo::VideoEncodeAcceleratorCapability>
VideoEncodeAcceleratorSupportedProfileToProtocol(
    const gpu::VideoEncodeAcceleratorSupportedProfile& profile) {
  return SystemInfo::VideoEncodeAcceleratorCapability::Create()
      .SetProfile(media::GetProfileName(
          static_cast<media::VideoCodecProfile>(profile.profile)))
      .SetMaxResolution(GfxSizeToSystemInfoSize(profile.max_resolution))
      .SetMaxFramerateNumerator(profile.max_framerate_numerator)
      .SetMaxFramerateDenominator(profile.max_framerate_denominator)
      .Build();
}

void SendGetInfoResponse(std::unique_ptr<GetInfoCallback> callback) {
  gpu::GPUInfo gpu_info = GpuDataManagerImpl::GetInstance()->GetGPUInfo();
  auto devices = std::make_unique<protocol::Array<GPUDevice>>();
  // The active device should be the 0th device
  for (size_t i = 0; i < gpu_info.secondary_gpus.size(); ++i) {
    if (gpu_info.secondary_gpus[i].active)
      devices->emplace_back(GPUDeviceToProtocol(gpu_info.secondary_gpus[i]));
  }
  devices->emplace_back(GPUDeviceToProtocol(gpu_info.gpu));
  for (size_t i = 0; i < gpu_info.secondary_gpus.size(); ++i) {
    if (gpu_info.secondary_gpus[i].active)
      continue;
    devices->emplace_back(GPUDeviceToProtocol(gpu_info.secondary_gpus[i]));
  }
  auto aux_attributes = std::make_unique<base::Value::Dict>();
  AuxGPUInfoEnumerator enumerator(aux_attributes.get());
  gpu_info.EnumerateFields(&enumerator);
  aux_attributes->Set("processCrashCount", GpuProcessHost::GetGpuCrashCount());
  aux_attributes->Set(
      "visibilityCallbackCallCount",
      static_cast<int>(gpu_info.visibility_callback_call_count));

  auto feature_status = std::make_unique<base::Value::Dict>(
      std::move(GetFeatureStatus().GetDict()));
  auto driver_bug_workarounds =
      std::make_unique<protocol::Array<std::string>>(GetDriverBugWorkarounds());

  auto decoding_profiles = std::make_unique<
      protocol::Array<SystemInfo::VideoDecodeAcceleratorCapability>>();
  for (const auto& profile :
       gpu_info.video_decode_accelerator_supported_profiles) {
    decoding_profiles->emplace_back(
        VideoDecodeAcceleratorSupportedProfileToProtocol(profile));
  }

  auto encoding_profiles = std::make_unique<
      protocol::Array<SystemInfo::VideoEncodeAcceleratorCapability>>();
  for (const auto& profile :
       gpu_info.video_encode_accelerator_supported_profiles) {
    encoding_profiles->emplace_back(
        VideoEncodeAcceleratorSupportedProfileToProtocol(profile));
  }

  std::unique_ptr<GPUInfo> gpu =
      GPUInfo::Create()
          .SetDevices(std::move(devices))
          .SetAuxAttributes(std::move(aux_attributes))
          .SetFeatureStatus(std::move(feature_status))
          .SetDriverBugWorkarounds(std::move(driver_bug_workarounds))
          .SetVideoDecoding(std::move(decoding_profiles))
          .SetVideoEncoding(std::move(encoding_profiles))
          .Build();

  base::CommandLine* command = base::CommandLine::ForCurrentProcess();
#if BUILDFLAG(IS_WIN)
  std::string command_string =
      base::WideToUTF8(command->GetCommandLineString());
#else
  std::string command_string = command->GetCommandLineString();
#endif

  callback->sendSuccess(std::move(gpu), gpu_info.machine_model_name,
                        gpu_info.machine_model_version, command_string);
}

}  // namespace

class SystemInfoHandlerGpuObserver : public content::GpuDataManagerObserver {
 public:
  explicit SystemInfoHandlerGpuObserver(
      std::unique_ptr<GetInfoCallback> callback)
      : callback_(std::move(callback)) {
    GetUIThreadTaskRunner({})->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&SystemInfoHandlerGpuObserver::ObserverWatchdogCallback,
                       weak_factory_.GetWeakPtr()),
        base::Milliseconds(kGPUInfoWatchdogTimeoutMs));

    GpuDataManagerImpl::GetInstance()->AddObserver(this);
    OnGpuInfoUpdate();
  }

  void OnGpuInfoUpdate() override {
    if (!GpuDataManagerImpl::GetInstance()->IsGpuFeatureInfoAvailable())
      return;
    base::CommandLine* command = base::CommandLine::ForCurrentProcess();
    // Only wait for DX12/Vulkan info if requested at Chrome start up.
    if (!command->HasSwitch(
            switches::kDisableGpuProcessForDX12InfoCollection) &&
        command->HasSwitch(switches::kNoDelayForDX12VulkanInfoCollection) &&
        !GpuDataManagerImpl::GetInstance()->IsDx12VulkanVersionAvailable())
      return;
    UnregisterAndSendResponse();
  }

  void OnGpuProcessCrashed() override { UnregisterAndSendResponse(); }

  void ObserverWatchdogCallback() {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    NOTREACHED() << "Gathering system GPU info took more than "
                 << (kGPUInfoWatchdogTimeoutMs / 1000) << " seconds.";
  }

  void UnregisterAndSendResponse() {
    GpuDataManagerImpl::GetInstance()->RemoveObserver(this);
    SendGetInfoResponse(std::move(callback_));
    delete this;
  }

 private:
  std::unique_ptr<GetInfoCallback> callback_;
  base::WeakPtrFactory<SystemInfoHandlerGpuObserver> weak_factory_{this};
};

SystemInfoHandler::SystemInfoHandler(bool is_browser_session)
    : DevToolsDomainHandler(SystemInfo::Metainfo::domainName),
      is_browser_session_(is_browser_session) {}

SystemInfoHandler::~SystemInfoHandler() = default;

void SystemInfoHandler::Wire(UberDispatcher* dispatcher) {
  SystemInfo::Dispatcher::wire(dispatcher, this);
}

void SystemInfoHandler::GetInfo(std::unique_ptr<GetInfoCallback> callback) {
  if (!is_browser_session_) {
    callback->sendFailure(Response::ServerError(
        "SystemInfo.getInfo is only supported on the browser target"));
    return;
  }

  // We will be able to get more information from the GpuDataManager.
  // Register a transient observer with it to call us back when the
  // information is available.
  new SystemInfoHandlerGpuObserver(std::move(callback));
}

namespace {

std::unique_ptr<base::ProcessMetrics> CreateProcessMetrics(
    base::ProcessHandle handle) {
#if BUILDFLAG(IS_MAC)
  return base::ProcessMetrics::CreateProcessMetrics(
      handle, content::BrowserChildProcessHost::GetPortProvider());
#else
  return base::ProcessMetrics::CreateProcessMetrics(handle);
#endif
}

std::unique_ptr<protocol::SystemInfo::ProcessInfo> MakeProcessInfo(
    const base::Process& process,
    const String& process_type) {
  std::unique_ptr<base::ProcessMetrics> pm =
      CreateProcessMetrics(process.Handle());
  const base::TimeDelta cpu_usage =
      pm->GetCumulativeCPUUsage().value_or(base::TimeDelta());

  return SystemInfo::ProcessInfo::Create()
      .SetId(process.Pid())
      .SetType(process_type)
      .SetCpuTime(cpu_usage.InSecondsF())
      .Build();
}

void AddBrowserProcessInfo(
    protocol::Array<protocol::SystemInfo::ProcessInfo>* process_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  process_info->emplace_back(
      MakeProcessInfo(base::Process::Current(), "browser"));
}

void AddRendererProcessInfo(
    protocol::Array<protocol::SystemInfo::ProcessInfo>* process_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
       !it.IsAtEnd(); it.Advance()) {
    RenderProcessHost* host = it.GetCurrentValue();
    if (host->GetProcess().IsValid()) {
      process_info->emplace_back(
          MakeProcessInfo(host->GetProcess(), "renderer"));
    }
  }
}

void AddChildProcessInfo(
    protocol::Array<protocol::SystemInfo::ProcessInfo>* process_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  for (BrowserChildProcessHostIterator it; !it.Done(); ++it) {
    const ChildProcessData& process_data = it.GetData();
    const base::Process& process = process_data.GetProcess();
    if (process.IsValid()) {
      process_info->emplace_back(
          MakeProcessInfo(process, process_data.metrics_name));
    }
  }
}

}  // namespace

void SystemInfoHandler::GetProcessInfo(
    std::unique_ptr<GetProcessInfoCallback> callback) {
  if (!is_browser_session_) {
    callback->sendFailure(Response::ServerError(
        "SystemInfo.getProcessInfo is only supported on the browser target"));
    return;
  }

  auto process_info =
      std::make_unique<protocol::Array<SystemInfo::ProcessInfo>>();

  AddBrowserProcessInfo(process_info.get());
  AddRendererProcessInfo(process_info.get());
  AddChildProcessInfo(process_info.get());
  callback->sendSuccess(std::move(process_info));
}

Response SystemInfoHandler::GetFeatureState(const String& in_featureState,
                                            bool* featureEnabled) {
  return Response::InvalidParams("Unknown feature");
}

}  // namespace protocol
}  // namespace content