/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ART_RUNTIME_TRACE_PROFILE_H_
#define ART_RUNTIME_TRACE_PROFILE_H_

#include <unordered_set>

#include "base/locks.h"
#include "base/macros.h"
#include "base/os.h"
#include "thread.h"
#include "thread_pool.h"

namespace art HIDDEN {

class ArtMethod;

// TODO(mythria): A randomly chosen value. Tune it later based on the number of
// entries required in the buffer.
static constexpr size_t kAlwaysOnTraceBufSize = 2048;

// The typical frequency at which the timestamp counters are updated is 24576000.
// 2^23 (8388608) corresponds to about 341ms at that frequency.
static constexpr size_t kLongRunningMethodThreshold = 1 << 23;

enum class LowOverheadTraceType {
  kLongRunningMethods,
  kAllMethods,
  kNone
};

class TraceData {
 public:
  explicit TraceData(LowOverheadTraceType trace_type)
      : curr_buffer_(nullptr),
        curr_index_(0),
        trace_type_(trace_type),
        trace_end_time_(0),
        trace_dump_in_progress_(false),
        trace_dump_condition_("trace dump condition", *Locks::trace_lock_),
        trace_data_lock_("Trace Data lock", LockLevel::kGenericBottomLock) {}

  LowOverheadTraceType GetTraceType() const {
    return trace_type_;
  }

  uint64_t GetTraceEndTime() const {
    return trace_end_time_;
  }

  void SetTraceEndTime(uint64_t end_time) {
    trace_end_time_ = end_time;
  }

  // Dumps events collected in the buffers and the information about threads and methods into the
  // output stream.
  void DumpData(std::ostringstream& os);

  void AppendToLongRunningMethods(const uint8_t* buffer, size_t size);

  void AddTracedMethods(std::unordered_set<ArtMethod*>& methods) {
    MutexLock mu(Thread::Current(), trace_data_lock_);
    traced_methods_.merge(methods);
  }

  void AddTracedMethod(ArtMethod* method) {
    MutexLock mu(Thread::Current(), trace_data_lock_);
    traced_methods_.insert(method);
  }

  void AddTracedThread(Thread* thread);

  // If there is no trace dump in progress this returns immediately. Otherwise
  // it waits on a condition variable waiting for the trace dump to finish.
  void MaybeWaitForTraceDumpToFinish() REQUIRES(Locks::trace_lock_);

  // Called when a trace dump is finished to notify any waiting requests. This
  // also resets the trace_dump_in_progress_ to false.
  void SignalTraceDumpComplete() REQUIRES(Locks::trace_lock_);

  void SetTraceDumpInProgress() REQUIRES(Locks::trace_lock_) {
    trace_dump_in_progress_ = true;
  }

  bool IsTraceDumpInProgress() const REQUIRES(Locks::trace_lock_) {
    return trace_dump_in_progress_;
  }

 private:
  // This is used to hold the long running methods when the per-thread buffer overflows.
  std::unique_ptr<uint8_t> curr_buffer_ GUARDED_BY(trace_data_lock_);

  // The index of the next free space in the curr_buffer_
  size_t curr_index_ GUARDED_BY(trace_data_lock_);

  // When the curr_buffer_ becomes full, we store it in this list and allocate a new buffer.
  std::vector<std::unique_ptr<uint8_t>> overflow_buffers_ GUARDED_BY(trace_data_lock_);

  LowOverheadTraceType trace_type_;

  uint64_t trace_end_time_;

  // These hold the methods and threads see so far. These are used to generate information about
  // the methods and threads.
  std::unordered_set<ArtMethod*> traced_methods_ GUARDED_BY(trace_data_lock_);

  // Threads might exit before we dump the data, so record thread id and name when we see a new
  // thread.
  std::unordered_map<size_t, std::string> traced_threads_ GUARDED_BY(trace_data_lock_);

  // This specifies if a trace dump is in progress. We release the trace_lock_
  // when waiting for the checkpoints to finish. We shouldn't delete trace data
  // when a dump is in progress. trace_dump_in_progress_ and
  // trace_dump_condition_ are used to make sure we wait for any in progress
  // trace dumps to finish before deleting the trace data.
  bool trace_dump_in_progress_ GUARDED_BY(Locks::trace_lock_);
  ConditionVariable trace_dump_condition_ GUARDED_BY(Locks::trace_lock_);

  // Lock to synchronize access to traced_methods_, traced_threads_ and curr_buffer_ which
  // can be accessed simultaneously by multiple threads when running TraceDumpCheckpoint.
  Mutex trace_data_lock_;
};

class TraceDumpCheckpoint final : public Closure {
 public:
  TraceDumpCheckpoint(TraceData* trace_data, const std::unique_ptr<File>& trace_file)
      : barrier_(0),
        trace_data_(trace_data),
        trace_file_(trace_file),
        trace_file_lock_("trace file lock", LockLevel::kGenericBottomLock) {}

  void Run(Thread* thread) override REQUIRES_SHARED(Locks::mutator_lock_);
  void WaitForThreadsToRunThroughCheckpoint(size_t threads_running_checkpoint);
  void FinishTraceDump(std::ostringstream& os);

 private:
  // The barrier to be passed through and for the requestor to wait upon.
  Barrier barrier_;

  // Trace data to record the data from each thread.
  TraceData* trace_data_;

  // Trace file to flush the data. If the trace_file_ is empty then the data is recorded in the
  // trace_data_.
  const std::unique_ptr<File>& trace_file_ GUARDED_BY(trace_file_lock_);

  // Lock to synchronize access to trace_file_. We need to write the data of
  // each thread as a block so we hold a lock while flushing the data.
  Mutex trace_file_lock_;
};

// This class implements low-overhead tracing. This feature is available only when
// always_enable_profile_code is enabled which is a build time flag defined in
// build/flags/art-flags.aconfig. When this flag is enabled, AOT and JITed code can record events
// on each method execution. When a profile is started, method entry / exit events are recorded in
// a per-thread circular buffer. When requested the recorded events in the buffer are dumped into a
// file. The buffers are released when the profile is stopped.
class TraceProfiler {
 public:
  // Starts profiling by allocating a per-thread buffer for all the threads.
  static void Start();

  // Starts recording long running methods. A long running method means any
  // method that executes for more than kLongRunningMethodDuration.
  static void StartTraceLongRunningMethods(uint64_t trace_duration_ns);

  // Releases all the buffers.
  static void Stop();

  // Dumps the recorded events in the buffer from all threads in the specified file.
  static void Dump(int fd);
  static void Dump(const char* trace_filename);

  // Get the long running methods as a string. This is used in the sigquit handler to record
  // information about long running methods.
  static std::string GetLongRunningMethodsString();

  // Called when thread is exiting to release the allocated buffer.
  static void ReleaseThreadBuffer(Thread* self) REQUIRES(Locks::trace_lock_);

  static bool IsTraceProfileInProgress() REQUIRES(Locks::trace_lock_);

  // Allocates a buffer for the specified thread.
  static void AllocateBuffer(Thread* thread);

  // Used to flush the long running method buffer when it is full. This method flushes all methods
  // that have already seen an exit and records them into a string. If we don't have sufficient free
  // entries after this processing (for example: if we have a really deep call stack) then we record
  // a placeholder method exit event and flush all events.
  static void FlushBufferAndRecordTraceEvent(ArtMethod* method, Thread* thread, bool is_entry);

  static LowOverheadTraceType GetTraceType();

  // Callback that is run when the specified duration for the long running trace has elapsed. If the
  // trace is still running then tracing is stopped and all buffers are released. If the trace
  // has already stopped then this request is ignored.
  static void TraceTimeElapsed();

 private:
  // Starts tracing.
  static void Start(LowOverheadTraceType trace_type, uint64_t trace_duration_ns);

  // Dumps the tracing data into the specified trace_file
  static void Dump(std::unique_ptr<File>&& trace_file, std::ostringstream& os);

  // Stops tracing.
  static void StopLocked() REQUIRES(Locks::trace_lock_);

  // This method goes over all the events in the thread_buffer and stores the encoded event in the
  // buffer. It returns the number of bytes written into the buffer.
  // This also records the ArtMethods from the events in the thread_buffer in a set. This set is
  // used to dump the information about the methods once buffers from all threads have been
  // processed.
  static size_t DumpBuffer(uint32_t thread_id,
                           uintptr_t* thread_buffer,
                           uint8_t* buffer /* out */,
                           std::unordered_set<ArtMethod*>& methods /* out */);

  // Dumps all the trace events from the thread into the buffer. Also records the ArtMethods from
  // the events which is then used to record information about these methods.
  static size_t DumpLongRunningMethodBuffer(uint32_t thread_id,
                                            uintptr_t* method_trace_entries,
                                            uintptr_t* end_trace_entries,
                                            uint8_t* buffer,
                                            std::unordered_set<ArtMethod*>& methods);

  static bool profile_in_progress_ GUARDED_BY(Locks::trace_lock_);

  static TraceData* trace_data_ GUARDED_BY(Locks::trace_lock_);

  friend class TraceDumpCheckpoint;
  DISALLOW_COPY_AND_ASSIGN(TraceProfiler);
};

}  // namespace art

#endif  // ART_RUNTIME_TRACE_PROFILE_H_
