blob: cd6fdc01ead2df4f178ba35fdf5be892a3db81a2 [file] [log] [blame]
/*
* 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_