// Copyright 2016 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/arc/metrics/arc_metrics_service.h"

#include <string>
#include <utility>

#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/session_manager_client.h"
#include "components/arc/arc_bridge_service.h"

namespace {

const int kRequestProcessListPeriodInMinutes = 5;
const char kArcProcessNamePrefix[] = "org.chromium.arc.";
const char kGmsProcessNamePrefix[] = "com.google.android.gms";
const char kBootProgressEnableScreen[] = "boot_progress_enable_screen";

}  // namespace

namespace arc {

ArcMetricsService::ArcMetricsService(ArcBridgeService* bridge_service)
    : ArcService(bridge_service),
      binding_(this),
      process_observer_(this),
      oom_kills_monitor_handle_(OomKillsMonitor::StartMonitoring()),
      weak_ptr_factory_(this) {
  arc_bridge_service()->metrics()->AddObserver(this);
  arc_bridge_service()->process()->AddObserver(&process_observer_);
}

ArcMetricsService::~ArcMetricsService() {
  DCHECK(CalledOnValidThread());
  arc_bridge_service()->process()->RemoveObserver(&process_observer_);
  arc_bridge_service()->metrics()->RemoveObserver(this);
}

bool ArcMetricsService::CalledOnValidThread() {
  // Make sure access to the Chrome clipboard is happening in the UI thread.
  return thread_checker_.CalledOnValidThread();
}

void ArcMetricsService::OnInstanceReady() {
  VLOG(2) << "Start metrics service.";
  // Retrieve ARC start time from session manager.
  chromeos::SessionManagerClient* session_manager_client =
      chromeos::DBusThreadManager::Get()->GetSessionManagerClient();
  session_manager_client->GetArcStartTime(
      base::Bind(&ArcMetricsService::OnArcStartTimeRetrieved,
                 weak_ptr_factory_.GetWeakPtr()));
}

void ArcMetricsService::OnInstanceClosed() {
  VLOG(2) << "Close metrics service.";
  DCHECK(CalledOnValidThread());
  if (binding_.is_bound())
    binding_.Unbind();
}

void ArcMetricsService::OnProcessInstanceReady() {
  VLOG(2) << "Start updating process list.";
  timer_.Start(FROM_HERE,
               base::TimeDelta::FromMinutes(kRequestProcessListPeriodInMinutes),
               this, &ArcMetricsService::RequestProcessList);
}

void ArcMetricsService::OnProcessInstanceClosed() {
  VLOG(2) << "Stop updating process list.";
  timer_.Stop();
}

void ArcMetricsService::RequestProcessList() {
  mojom::ProcessInstance* process_instance =
      arc_bridge_service()->process()->GetInstanceForMethod(
          "RequestProcessList");
  if (!process_instance)
    return;
  VLOG(2) << "RequestProcessList";
  process_instance->RequestProcessList(base::Bind(
      &ArcMetricsService::ParseProcessList, weak_ptr_factory_.GetWeakPtr()));
}

void ArcMetricsService::ParseProcessList(
    std::vector<mojom::RunningAppProcessInfoPtr> processes) {
  int running_app_count = 0;
  for (const auto& process : processes) {
    const std::string& process_name = process->process_name;
    const mojom::ProcessState& process_state = process->process_state;

    // Processes like the ARC launcher and intent helper are always running
    // and not counted as apps running by users. With the same reasoning,
    // GMS (Google Play Services) and its related processes are skipped as
    // well. The process_state check below filters out system processes,
    // services, apps that are cached because they've run before.
    if (base::StartsWith(process_name, kArcProcessNamePrefix,
                         base::CompareCase::SENSITIVE) ||
        base::StartsWith(process_name, kGmsProcessNamePrefix,
                         base::CompareCase::SENSITIVE) ||
        process_state != mojom::ProcessState::TOP) {
      VLOG(2) << "Skipped " << process_name << " " << process_state;
    } else {
      ++running_app_count;
    }
  }

  UMA_HISTOGRAM_COUNTS_100("Arc.AppCount", running_app_count);
}

void ArcMetricsService::OnArcStartTimeRetrieved(
    bool success,
    base::TimeTicks arc_start_time) {
  DCHECK(CalledOnValidThread());
  if (!success) {
    LOG(ERROR) << "Failed to retrieve ARC start timeticks.";
    return;
  }
  auto* instance =
      arc_bridge_service()->metrics()->GetInstanceForMethod("Init");
  if (!instance)
    return;

  // The binding of host interface is deferred until the ARC start time is
  // retrieved here because it prevents race condition of the ARC start
  // time availability in ReportBootProgress().
  if (!binding_.is_bound()) {
    mojom::MetricsHostPtr host_ptr;
    binding_.Bind(mojo::GetProxy(&host_ptr));
    instance->Init(std::move(host_ptr));
  }
  arc_start_time_ = arc_start_time;
  VLOG(2) << "ARC start @" << arc_start_time_;
}

void ArcMetricsService::ReportBootProgress(
    std::vector<mojom::BootProgressEventPtr> events) {
  DCHECK(CalledOnValidThread());
  int64_t arc_start_time_in_ms =
      (arc_start_time_ - base::TimeTicks()).InMilliseconds();
  for (const auto& event : events) {
    VLOG(2) << "Report boot progress event:" << event->event << "@"
            << event->uptimeMillis;
    std::string title = "Arc." + event->event;
    base::TimeDelta elapsed_time = base::TimeDelta::FromMilliseconds(
        event->uptimeMillis - arc_start_time_in_ms);
    // Note: This leaks memory, which is expected behavior.
    base::HistogramBase* histogram = base::Histogram::FactoryTimeGet(
        title, base::TimeDelta::FromMilliseconds(1),
        base::TimeDelta::FromSeconds(30), 50,
        base::HistogramBase::kUmaTargetedHistogramFlag);
    histogram->AddTime(elapsed_time);
    if (event->event.compare(kBootProgressEnableScreen) == 0)
      UMA_HISTOGRAM_CUSTOM_TIMES("Arc.AndroidBootTime", elapsed_time,
                                 base::TimeDelta::FromMilliseconds(1),
                                 base::TimeDelta::FromSeconds(30), 50);
  }
}

ArcMetricsService::ProcessObserver::ProcessObserver(
    ArcMetricsService* arc_metrics_service)
    : arc_metrics_service_(arc_metrics_service) {}

ArcMetricsService::ProcessObserver::~ProcessObserver() = default;

void ArcMetricsService::ProcessObserver::OnInstanceReady() {
  arc_metrics_service_->OnProcessInstanceReady();
}

void ArcMetricsService::ProcessObserver::OnInstanceClosed() {
  arc_metrics_service_->OnProcessInstanceClosed();
}

}  // namespace arc
