// Copyright 2017 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 "services/resource_coordinator/public/cpp/memory_instrumentation/client_process_impl.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/containers/flat_map.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_piece.h"
#include "base/synchronization/lock.h"
#include "base/trace_event/memory_dump_request_args.h"
#include "build/build_config.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/coordinator.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/tracing_observer.h"
#include "services/resource_coordinator/public/mojom/memory_instrumentation/memory_instrumentation.mojom.h"
#include "services/service_manager/public/cpp/bind_source_info.h"
#include "services/service_manager/public/cpp/connector.h"

namespace memory_instrumentation {

// static
void ClientProcessImpl::CreateInstance(const Config& config) {
  static ClientProcessImpl* instance = nullptr;
  if (!instance) {
    instance = new ClientProcessImpl(config);
  } else {
    NOTREACHED();
  }
}

ClientProcessImpl::ClientProcessImpl(const Config& config)
    : binding_(this), process_type_(config.process_type), task_runner_() {
  // |config.connector| can be null in tests.
  if (config.connector) {
    config.connector->BindInterface(config.service_name,
                                    mojo::MakeRequest(&coordinator_));
    mojom::ClientProcessPtr process;
    binding_.Bind(mojo::MakeRequest(&process));
    coordinator_->RegisterClientProcess(std::move(process),
                                        config.process_type);

    // Initialize the public-facing MemoryInstrumentation helper.
    MemoryInstrumentation::CreateInstance(config.connector,
                                          config.service_name);
  } else {
    config.coordinator_for_testing->BindCoordinatorRequest(
        mojo::MakeRequest(&coordinator_), service_manager::BindSourceInfo());
  }

  task_runner_ = base::ThreadTaskRunnerHandle::Get();

  // TODO(primiano): this is a temporary workaround to tell the
  // base::MemoryDumpManager that it is special and should coordinate periodic
  // dumps for tracing. Remove this once the periodic dump scheduler is moved
  // from base to the service. MDM should not care about being the coordinator.
  bool is_coordinator_process =
      config.process_type == mojom::ProcessType::BROWSER;
  base::trace_event::MemoryDumpManager::GetInstance()->Initialize(
      base::BindRepeating(
          &ClientProcessImpl::RequestGlobalMemoryDump_NoCallback,
          base::Unretained(this)),
      is_coordinator_process);

  tracing_observer_ = std::make_unique<TracingObserver>(
      base::trace_event::TraceLog::GetInstance(),
      base::trace_event::MemoryDumpManager::GetInstance());
}

ClientProcessImpl::~ClientProcessImpl() {}

void ClientProcessImpl::RequestChromeMemoryDump(
    const base::trace_event::MemoryDumpRequestArgs& args,
    RequestChromeMemoryDumpCallback callback) {
  DCHECK(!callback.is_null());
  most_recent_chrome_memory_dump_guid_ = args.dump_guid;
  auto it_and_inserted =
      pending_chrome_callbacks_.emplace(args.dump_guid, std::move(callback));
  DCHECK(it_and_inserted.second) << "Duplicated request id " << args.dump_guid;
  base::trace_event::MemoryDumpManager::GetInstance()->CreateProcessDump(
      args, base::Bind(&ClientProcessImpl::OnChromeMemoryDumpDone,
                       base::Unretained(this)));
}

void ClientProcessImpl::OnChromeMemoryDumpDone(
    bool success,
    uint64_t dump_guid,
    std::unique_ptr<base::trace_event::ProcessMemoryDump> process_memory_dump) {
  DCHECK(success || !process_memory_dump);

  auto callback_it = pending_chrome_callbacks_.find(dump_guid);
  DCHECK(callback_it != pending_chrome_callbacks_.end());

  auto callback = std::move(callback_it->second);
  pending_chrome_callbacks_.erase(callback_it);

  auto it = delayed_os_memory_dump_callbacks_.find(dump_guid);
  if (it != delayed_os_memory_dump_callbacks_.end()) {
    for (auto& args : it->second) {
      PerformOSMemoryDump(std::move(args));
    }
    delayed_os_memory_dump_callbacks_.erase(it);
  }

  if (!process_memory_dump) {
    std::move(callback).Run(false, dump_guid, nullptr);
    return;
  }
  std::move(callback).Run(success, dump_guid, std::move(process_memory_dump));
}

void ClientProcessImpl::RequestGlobalMemoryDump_NoCallback(
    base::trace_event::MemoryDumpType dump_type,
    base::trace_event::MemoryDumpLevelOfDetail level_of_detail) {
  if (!task_runner_->RunsTasksInCurrentSequence()) {
    task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&ClientProcessImpl::RequestGlobalMemoryDump_NoCallback,
                       base::Unretained(this), dump_type, level_of_detail));
    return;
  }

  coordinator_->RequestGlobalMemoryDumpAndAppendToTrace(
      dump_type, level_of_detail,
      mojom::Coordinator::RequestGlobalMemoryDumpAndAppendToTraceCallback());
}

void ClientProcessImpl::RequestOSMemoryDump(
    mojom::MemoryMapOption mmap_option,
    const std::vector<base::ProcessId>& pids,
    RequestOSMemoryDumpCallback callback) {
  OSMemoryDumpArgs args;
  args.mmap_option = mmap_option;
  args.pids = pids;
  args.callback = std::move(callback);

#if defined(OS_MACOSX)
  // If the most recent chrome memory dump hasn't finished, wait for that to
  // finish.
  if (most_recent_chrome_memory_dump_guid_.has_value()) {
    uint64_t guid = most_recent_chrome_memory_dump_guid_.value();
    auto it = pending_chrome_callbacks_.find(guid);
    if (it != pending_chrome_callbacks_.end()) {
      delayed_os_memory_dump_callbacks_[guid].push_back(std::move(args));
      return;
    }
  }
#endif
  PerformOSMemoryDump(std::move(args));
}

void ClientProcessImpl::PerformOSMemoryDump(OSMemoryDumpArgs args) {
  bool global_success = true;
  base::flat_map<base::ProcessId, mojom::RawOSMemDumpPtr> results;
  for (const base::ProcessId& pid : args.pids) {
    mojom::RawOSMemDumpPtr result = mojom::RawOSMemDump::New();
    result->platform_private_footprint = mojom::PlatformPrivateFootprint::New();
    bool success = OSMetrics::FillOSMemoryDump(pid, result.get());
    if (args.mmap_option != mojom::MemoryMapOption::NONE) {
      success = success && OSMetrics::FillProcessMemoryMaps(
                               pid, args.mmap_option, result.get());
    }
    if (success)
      results[pid] = std::move(result);
    global_success = global_success && success;
  }
  std::move(args.callback).Run(global_success, std::move(results));
}

ClientProcessImpl::Config::Config(service_manager::Connector* connector,
                                  const std::string& service_name,
                                  mojom::ProcessType process_type)
    : connector(connector),
      service_name(service_name),
      process_type(process_type) {}

ClientProcessImpl::Config::~Config() {}

ClientProcessImpl::OSMemoryDumpArgs::OSMemoryDumpArgs() = default;
ClientProcessImpl::OSMemoryDumpArgs::OSMemoryDumpArgs(OSMemoryDumpArgs&&) =
    default;
ClientProcessImpl::OSMemoryDumpArgs::~OSMemoryDumpArgs() = default;

}  // namespace memory_instrumentation
