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

#include "components/performance_manager/graph/process_node_impl_describer.h"

#include "base/i18n/time_formatting.h"
#include "base/numerics/safe_conversions.h"
#include "base/process/process.h"
#include "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/task_traits.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/performance_manager/public/graph/node_data_describer_registry.h"
#include "content/public/common/child_process_host.h"

#if defined(OS_WIN)
#include "base/win/win_util.h"
#endif

namespace performance_manager {

namespace {

const char kDescriberName[] = "ProcessNodeImpl";

base::Value GetProcessValueDict(const base::Process& process) {
  base::Value ret(base::Value::Type::DICTIONARY);

  // On Windows, handle is a void *. On Fuchsia it's an int. On other platforms
  // it is equal to the pid, so don't bother to record it.
#if defined(OS_WIN)
  ret.SetIntKey("handle", base::win::HandleToUint32(process.Handle()));
#elif defined(OS_FUCHSIA)
  ret.SetIntKey("handle", process.Handle());
#endif

  // Most processes are not current, so only show the outliers.
  if (process.is_current()) {
    ret.SetBoolKey("is_current", true);
  }

#if defined(OS_CHROMEOS)
  if (process.GetPidInNamespace() != base::kNullProcessId) {
    ret.SetIntKey("pid_in_namespace", process.GetPidInNamespace());
  }
#endif

#if defined(OS_WIN)
  // Creation time is always available on Windows, even for dead processes.
  // On other platforms it is available only for valid processes (see below).
  ret.SetStringKey("creation_time", base::TimeFormatTimeOfDayWithMilliseconds(
                                        process.CreationTime()));
#endif

  if (process.IsValid()) {
    // These properties can only be accessed for valid processes.
    ret.SetIntKey("os_priority", process.GetPriority());
#if !defined(OS_APPLE)
    ret.SetBoolKey("is_backgrounded", process.IsProcessBackgrounded());
#endif
#if !defined(OS_ANDROID) && !defined(OS_WIN)
    ret.SetStringKey("creation_time", base::TimeFormatTimeOfDayWithMilliseconds(
                                          process.CreationTime()));
#endif
#if defined(OS_WIN)
    // Most processes are running, so only show the outliers.
    if (!process.IsRunning()) {
      ret.SetBoolKey("is_running", false);
    }
#endif
  } else {
    ret.SetBoolKey("is_valid", false);
  }

  return ret;
}

}  // namespace

void ProcessNodeImplDescriber::OnPassedToGraph(Graph* graph) {
  graph->GetNodeDataDescriberRegistry()->RegisterDescriber(this,
                                                           kDescriberName);
}

void ProcessNodeImplDescriber::OnTakenFromGraph(Graph* graph) {
  graph->GetNodeDataDescriberRegistry()->UnregisterDescriber(this);
}

base::Value ProcessNodeImplDescriber::DescribeProcessNodeData(
    const ProcessNode* node) const {
  const ProcessNodeImpl* impl = ProcessNodeImpl::FromNode(node);

  base::Value ret(base::Value::Type::DICTIONARY);

  if (impl->private_footprint_kb()) {
    ret.SetIntKey("private_footprint_kb",
                  base::saturated_cast<int>(impl->private_footprint_kb()));
  }

  if (impl->resident_set_kb()) {
    ret.SetIntKey("resident_set_kb",
                  base::saturated_cast<int>(impl->resident_set_kb()));
  }

  constexpr RenderProcessHostId kInvalidRenderProcessHostId =
      RenderProcessHostId(content::ChildProcessHost::kInvalidUniqueID);
  if (impl->GetRenderProcessId() != kInvalidRenderProcessHostId) {
    ret.SetIntKey("render_process_id", impl->GetRenderProcessId().value());
  }

  // The content function returns "Tab" for renderers - whereas "Renderer" is
  // the common vernacular here.
  std::string process_type =
      content::GetProcessTypeNameInEnglish(impl->process_type());
  if (impl->process_type() == content::PROCESS_TYPE_RENDERER)
    process_type = "Renderer";
  ret.SetStringKey("process_type", process_type);

  ret.SetStringKey("pid", base::NumberToString(impl->process_id()));

  ret.SetKey("process", GetProcessValueDict(impl->process()));

  ret.SetStringKey("launch_time", base::TimeFormatTimeOfDayWithMilliseconds(
                                      impl->launch_time()));

  if (impl->exit_status()) {
    ret.SetIntKey("exit_status", impl->exit_status().value());
  }

  ret.SetBoolKey("main_thread_task_load_is_low",
                 impl->main_thread_task_load_is_low());

  ret.SetStringKey("priority", base::TaskPriorityToString(impl->priority()));

  return ret;
}

}  // namespace performance_manager
