/*
 * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2023 Google LLC. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

#include "runtime/cpuTimeCounters.hpp"
#include "runtime/atomic.hpp"

const char* CPUTimeGroups::to_string(CPUTimeType val) {
  switch (val) {
    case CPUTimeType::gc_total:
      return "total_gc_cpu_time";
    case CPUTimeType::gc_parallel_workers:
      return "gc_parallel_workers";
    case CPUTimeType::gc_conc_mark:
      return "gc_conc_mark";
    case CPUTimeType::gc_conc_refine:
      return "gc_conc_refine";
    case CPUTimeType::gc_service:
      return "gc_service";
    case CPUTimeType::vm:
      return "vm";
    case CPUTimeType::conc_dedup:
      return "conc_dedup";
    default:
      ShouldNotReachHere();
      return "";
  };
}

bool CPUTimeGroups::is_gc_counter(CPUTimeType val) {
  switch (val) {
    case CPUTimeType::gc_parallel_workers:
    case CPUTimeType::gc_conc_mark:
    case CPUTimeType::gc_conc_refine:
    case CPUTimeType::gc_service:
      return true;
    default:
      return false;
  }
  ShouldNotReachHere();
}

CPUTimeCounters* CPUTimeCounters::_instance = nullptr;

CPUTimeCounters::CPUTimeCounters() :
    _cpu_time_counters(),
    _gc_total_cpu_time_diff(0) {
}

void CPUTimeCounters::inc_gc_total_cpu_time(jlong diff) {
  CPUTimeCounters* instance = CPUTimeCounters::get_instance();
  Atomic::add(&(instance->_gc_total_cpu_time_diff), diff);
}

void CPUTimeCounters::publish_gc_total_cpu_time() {
  CPUTimeCounters* instance = CPUTimeCounters::get_instance();
  // Ensure that we are only incrementing atomically by using Atomic::cmpxchg
  // to set the value to zero after we obtain the new CPU time difference.
  jlong old_value;
  jlong fetched_value = Atomic::load(&(instance->_gc_total_cpu_time_diff));
  jlong new_value = 0;
  do {
    old_value = fetched_value;
    fetched_value = Atomic::cmpxchg(&(instance->_gc_total_cpu_time_diff), old_value, new_value);
  } while (old_value != fetched_value);
  get_counter(CPUTimeGroups::CPUTimeType::gc_total)->inc(fetched_value);
}

void CPUTimeCounters::create_counter(CounterNS ns, CPUTimeGroups::CPUTimeType name) {
  if (UsePerfData && os::is_thread_cpu_time_supported()) {
    EXCEPTION_MARK;
    CPUTimeCounters* instance = CPUTimeCounters::get_instance();
    instance->_cpu_time_counters[static_cast<int>(name)] =
                PerfDataManager::create_counter(ns, CPUTimeGroups::to_string(name),
                                                PerfData::U_Ticks, CHECK);
  }
}

void CPUTimeCounters::create_counter(CPUTimeGroups::CPUTimeType group) {
  CPUTimeCounters::create_counter(SUN_THREADS_CPUTIME, group);
}

PerfCounter* CPUTimeCounters::get_counter(CPUTimeGroups::CPUTimeType name) {
  return CPUTimeCounters::get_instance()->_cpu_time_counters[static_cast<int>(name)];
}

void CPUTimeCounters::update_counter(CPUTimeGroups::CPUTimeType name, jlong total) {
  CPUTimeCounters* instance = CPUTimeCounters::get_instance();
  PerfCounter* counter = instance->get_counter(name);
  jlong prev_value = counter->get_value();
  jlong net_cpu_time = total - prev_value;
  counter->inc(net_cpu_time);
  if (CPUTimeGroups::is_gc_counter(name)) {
    instance->inc_gc_total_cpu_time(net_cpu_time);
  }
}

ThreadTotalCPUTimeClosure::~ThreadTotalCPUTimeClosure() {
  CPUTimeCounters::update_counter(_name, _total);
}

void ThreadTotalCPUTimeClosure::do_thread(Thread* thread) {
  // The default code path (fast_thread_cpu_time()) asserts that
  // pthread_getcpuclockid() and clock_gettime() must return 0. Thus caller
  // must ensure the thread exists and has not terminated.
  _total += os::thread_cpu_time(thread);
}
