// Copyright 2016 the V8 project 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 "src/compiler-dispatcher/compiler-dispatcher-tracer.h"

#include "src/isolate.h"
#include "src/utils.h"

namespace v8 {
namespace internal {

namespace {

double MonotonicallyIncreasingTimeInMs() {
  return V8::GetCurrentPlatform()->MonotonicallyIncreasingTime() *
         static_cast<double>(base::Time::kMillisecondsPerSecond);
}

const double kEstimatedRuntimeWithoutData = 1.0;

}  // namespace

CompilerDispatcherTracer::Scope::Scope(CompilerDispatcherTracer* tracer,
                                       ScopeID scope_id, size_t num)
    : tracer_(tracer), scope_id_(scope_id), num_(num) {
  start_time_ = MonotonicallyIncreasingTimeInMs();
}

CompilerDispatcherTracer::Scope::~Scope() {
  double elapsed = MonotonicallyIncreasingTimeInMs() - start_time_;
  switch (scope_id_) {
    case ScopeID::kPrepareToParse:
      tracer_->RecordPrepareToParse(elapsed);
      break;
    case ScopeID::kParse:
      tracer_->RecordParse(elapsed, num_);
      break;
    case ScopeID::kFinalizeParsing:
      tracer_->RecordFinalizeParsing(elapsed);
      break;
    case ScopeID::kAnalyze:
      tracer_->RecordAnalyze(elapsed);
      break;
    case ScopeID::kPrepareToCompile:
      tracer_->RecordPrepareToCompile(elapsed);
      break;
    case ScopeID::kCompile:
      tracer_->RecordCompile(elapsed);
      break;
    case ScopeID::kFinalizeCompiling:
      tracer_->RecordFinalizeCompiling(elapsed);
      break;
  }
}

// static
const char* CompilerDispatcherTracer::Scope::Name(ScopeID scope_id) {
  switch (scope_id) {
    case ScopeID::kPrepareToParse:
      return "V8.BackgroundCompile_PrepareToParse";
    case ScopeID::kParse:
      return "V8.BackgroundCompile_Parse";
    case ScopeID::kFinalizeParsing:
      return "V8.BackgroundCompile_FinalizeParsing";
    case ScopeID::kAnalyze:
      return "V8.BackgroundCompile_Analyze";
    case ScopeID::kPrepareToCompile:
      return "V8.BackgroundCompile_PrepareToCompile";
    case ScopeID::kCompile:
      return "V8.BackgroundCompile_Compile";
    case ScopeID::kFinalizeCompiling:
      return "V8.BackgroundCompile_FinalizeCompiling";
  }
  UNREACHABLE();
}

CompilerDispatcherTracer::CompilerDispatcherTracer(Isolate* isolate)
    : runtime_call_stats_(nullptr) {
  // isolate might be nullptr during unittests.
  if (isolate) {
    runtime_call_stats_ = isolate->counters()->runtime_call_stats();
  }
}

CompilerDispatcherTracer::~CompilerDispatcherTracer() {}

void CompilerDispatcherTracer::RecordPrepareToParse(double duration_ms) {
  base::LockGuard<base::Mutex> lock(&mutex_);
  prepare_parse_events_.Push(duration_ms);
}

void CompilerDispatcherTracer::RecordParse(double duration_ms,
                                           size_t source_length) {
  base::LockGuard<base::Mutex> lock(&mutex_);
  parse_events_.Push(std::make_pair(source_length, duration_ms));
}

void CompilerDispatcherTracer::RecordFinalizeParsing(double duration_ms) {
  base::LockGuard<base::Mutex> lock(&mutex_);
  finalize_parsing_events_.Push(duration_ms);
}

void CompilerDispatcherTracer::RecordAnalyze(double duration_ms) {
  base::LockGuard<base::Mutex> lock(&mutex_);
  analyze_events_.Push(duration_ms);
}

void CompilerDispatcherTracer::RecordPrepareToCompile(double duration_ms) {
  base::LockGuard<base::Mutex> lock(&mutex_);
  prepare_compile_events_.Push(duration_ms);
}

void CompilerDispatcherTracer::RecordCompile(double duration_ms) {
  base::LockGuard<base::Mutex> lock(&mutex_);
  compile_events_.Push(duration_ms);
}

void CompilerDispatcherTracer::RecordFinalizeCompiling(double duration_ms) {
  base::LockGuard<base::Mutex> lock(&mutex_);
  finalize_compiling_events_.Push(duration_ms);
}

double CompilerDispatcherTracer::EstimatePrepareToParseInMs() const {
  base::LockGuard<base::Mutex> lock(&mutex_);
  return Average(prepare_parse_events_);
}

double CompilerDispatcherTracer::EstimateParseInMs(size_t source_length) const {
  base::LockGuard<base::Mutex> lock(&mutex_);
  return Estimate(parse_events_, source_length);
}

double CompilerDispatcherTracer::EstimateFinalizeParsingInMs() const {
  base::LockGuard<base::Mutex> lock(&mutex_);
  return Average(finalize_parsing_events_);
}

double CompilerDispatcherTracer::EstimateAnalyzeInMs() const {
  base::LockGuard<base::Mutex> lock(&mutex_);
  return Average(analyze_events_);
}

double CompilerDispatcherTracer::EstimatePrepareToCompileInMs() const {
  base::LockGuard<base::Mutex> lock(&mutex_);
  return Average(prepare_compile_events_);
}

double CompilerDispatcherTracer::EstimateCompileInMs() const {
  base::LockGuard<base::Mutex> lock(&mutex_);
  return Average(compile_events_);
}

double CompilerDispatcherTracer::EstimateFinalizeCompilingInMs() const {
  base::LockGuard<base::Mutex> lock(&mutex_);
  return Average(finalize_compiling_events_);
}

void CompilerDispatcherTracer::DumpStatistics() const {
  PrintF(
      "CompilerDispatcherTracer: "
      "prepare_parsing=%.2lfms parsing=%.2lfms/kb finalize_parsing=%.2lfms "
      "analyze=%.2lfms prepare_compiling=%.2lfms compiling=%.2lfms/kb "
      "finalize_compiling=%.2lfms\n",
      EstimatePrepareToParseInMs(), EstimateParseInMs(1 * KB),
      EstimateFinalizeParsingInMs(), EstimateAnalyzeInMs(),
      EstimatePrepareToCompileInMs(), EstimateCompileInMs(),
      EstimateFinalizeCompilingInMs());
}

double CompilerDispatcherTracer::Average(
    const base::RingBuffer<double>& buffer) {
  if (buffer.Count() == 0) return 0.0;
  double sum = buffer.Sum([](double a, double b) { return a + b; }, 0.0);
  return sum / buffer.Count();
}

double CompilerDispatcherTracer::Estimate(
    const base::RingBuffer<std::pair<size_t, double>>& buffer, size_t num) {
  if (buffer.Count() == 0) return kEstimatedRuntimeWithoutData;
  std::pair<size_t, double> sum = buffer.Sum(
      [](std::pair<size_t, double> a, std::pair<size_t, double> b) {
        return std::make_pair(a.first + b.first, a.second + b.second);
      },
      std::make_pair(0, 0.0));
  return num * (sum.second / sum.first);
}

}  // namespace internal
}  // namespace v8
