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

#include "third_party/blink/renderer/controller/highest_pmf_reporter.h"

#include <limits>
#include "base/metrics/histogram_functions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_runner.h"
#include "base/time/default_tick_clock.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/page/page.h"

namespace blink {

namespace {

constexpr size_t kMaxReportCount = 4;

constexpr std::array<const char*, kMaxReportCount> kHighestPmfMetricNames = {
    "Memory.Experimental.Renderer.HighestPrivateMemoryFootprint.0to2min",
    "Memory.Experimental.Renderer.HighestPrivateMemoryFootprint.2to4min",
    "Memory.Experimental.Renderer.HighestPrivateMemoryFootprint.4to8min",
    "Memory.Experimental.Renderer.HighestPrivateMemoryFootprint.8to16min"};

constexpr std::array<base::TimeDelta, kMaxReportCount> kTimeToReport = {
    base::Minutes(2), base::Minutes(4), base::Minutes(8), base::Minutes(16)};

}  // namespace

void HighestPmfReporter::Initialize(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
  DEFINE_STATIC_LOCAL(HighestPmfReporter, reporter, (std::move(task_runner)));
  (void)reporter;
}

HighestPmfReporter::HighestPmfReporter(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
    : HighestPmfReporter(std::move(task_runner),
                         base::DefaultTickClock::GetInstance()) {}

HighestPmfReporter::HighestPmfReporter(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
    const base::TickClock* clock)
    : task_runner_(std::move(task_runner)), clock_(clock) {
  MemoryUsageMonitor::Instance().AddObserver(this);
}

bool HighestPmfReporter::FirstNavigationStarted() {
  if (first_navigation_detected_)
    return false;

  for (Page* page : Page::OrdinaryPages()) {
    Frame* frame = page->MainFrame();
    if (!frame)
      continue;

    auto* local_frame = DynamicTo<LocalFrame>(frame);
    if (!local_frame)
      continue;

    DocumentLoader* loader = local_frame->Loader().GetDocumentLoader();
    if (!loader)
      continue;

    if (!loader->GetTiming().NavigationStart().is_null()) {
      first_navigation_detected_ = true;
      return true;
    }
  }
  return false;
}

void HighestPmfReporter::OnMemoryPing(MemoryUsage usage) {
  DCHECK(IsMainThread());
  if (FirstNavigationStarted()) {
    task_runner_->PostDelayedTask(
        FROM_HERE,
        blink::BindOnce(&HighestPmfReporter::OnReportMetrics,
                        blink::Unretained(this)),
        kTimeToReport[0]);
  }

  if (current_highest_pmf_ > usage.private_footprint_bytes)
    return;

  current_highest_pmf_ = usage.private_footprint_bytes;
  peak_resident_bytes_at_current_highest_pmf_ = usage.peak_resident_bytes;
  webpage_counts_at_current_highest_pmf_ = Page::OrdinaryPages().size();

  // TODO(tasak): Report the highest memory footprint throughout renderer's
  // lifetime.
}

void HighestPmfReporter::OnReportMetrics() {
  DCHECK(IsMainThread());
  ReportMetrics();

  // The following code is not accurate, because OnReportMetrics will be late
  // when renderer is slow (e.g. caused by near-OOM or heavy tasks is running
  // or ...). However such signal getting late by minutes is unlikely, so it's
  // ok to say "this is good enough".
  current_highest_pmf_ = 0.0;
  peak_resident_bytes_at_current_highest_pmf_ = 0.0;
  webpage_counts_at_current_highest_pmf_ = 0;
  report_count_++;
  if (report_count_ >= kMaxReportCount) {
    // Stop observing the MemoryUsageMonitor once there's no more histogram to
    // report.
    MemoryUsageMonitor::Instance().RemoveObserver(this);
    return;
  }

  base::TimeDelta delay =
      kTimeToReport[report_count_] - kTimeToReport[report_count_ - 1];
  task_runner_->PostDelayedTask(
      FROM_HERE,
      blink::BindOnce(&HighestPmfReporter::OnReportMetrics,
                      blink::Unretained(this)),
      delay);
}

void HighestPmfReporter::ReportMetrics() {
  base::UmaHistogramMemoryMB(kHighestPmfMetricNames[report_count_],
                             base::saturated_cast<base::Histogram::Sample32>(
                                 current_highest_pmf_ / 1024 / 1024));
}

}  // namespace blink
