1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
|
/*
* 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_
|