1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "tools/cygprofile/cygprofile.h"
10 #include <sys/syscall.h>
12 #include <sys/types.h>
19 #include "base/bind.h"
20 #include "base/containers/hash_tables.h"
21 #include "base/files/scoped_file.h"
22 #include "base/lazy_instance.h"
23 #include "base/logging.h"
24 #include "base/macros.h"
25 #include "base/stl_util.h"
26 #include "base/strings/string_number_conversions.h"
27 #include "base/strings/string_piece.h"
28 #include "base/strings/stringprintf.h"
29 #include "base/synchronization/lock.h"
31 namespace cygprofile
{
34 // Allow 8 MBytes of data for each thread log.
35 const int kMaxBufferSize
= 8 * 1024 * 1024 / sizeof(LogEntry
);
37 // Have the background internal thread do its flush every 15 sec.
38 const int kFlushThreadIdleTimeSec
= 15;
40 const char kLogFileNamePrefix
[] = "/data/local/tmp/chrome/cyglog/";
42 // "cyglog.PID.LWP.PPID"
43 const char kLogFilenameFormat
[] = "%scyglog.%d.%d-%d";
45 // Magic value of above to prevent instrumentation. Used when ThreadLog is being
46 // constructed (to prevent reentering by malloc, for example) and by the flush
47 // log thread (to prevent it from being logged0.
48 ThreadLog
* const kMagicBeingConstructed
= reinterpret_cast<ThreadLog
*>(1);
50 // Per-thread pointer to the current log object.
51 static __thread ThreadLog
* g_tls_log
= NULL
;
53 // Returns light-weight process ID. On Linux, this is a system-wide unique
56 return syscall(__NR_gettid
);
59 timespec
GetCurrentTime() {
61 clock_gettime(CLOCK_MONOTONIC
, ×tamp
);
65 // Sleeps for |sec| seconds.
66 void SleepSec(int sec
) {
67 for (int secs_to_sleep
= sec
; secs_to_sleep
!= 0;)
68 secs_to_sleep
= sleep(secs_to_sleep
);
71 // Exposes the string header that will appear at the top of every trace file.
72 // This string contains memory mapping information for the mapped
73 // library/executable which is used offline during symbolization. Note that
74 // this class is meant to be instantiated once per process and lazily (during
76 struct ImmutableFileHeaderLine
{
77 ImmutableFileHeaderLine() : value(MakeFileHeaderLine()) {}
79 const std::string value
;
82 // Returns whether the integer representation of the hexadecimal address
83 // stored in |line| at position |start_offset| was successfully stored in
85 static bool ParseAddress(const std::string
& line
,
89 if (start_offset
>= line
.length())
93 const bool ret
= HexStringToUInt64(
94 base::StringPiece(line
.c_str() + start_offset
, length
), &address
);
102 // Parses /proc/self/maps and returns a two line string such as:
103 // 758c6000-79f4b000 r-xp 00000000 b3:17 309475 libchrome.2009.0.so
104 // secs usecs pid:threadid func
105 static std::string
MakeFileHeaderLine() {
106 std::ifstream
mapsfile("/proc/self/maps");
107 CHECK(mapsfile
.good());
110 for (std::string line
; std::getline(mapsfile
, line
); ) {
111 if (line
.find("r-xp") == std::string::npos
)
114 const size_t address_length
= line
.find('-');
115 uint64 start_address
= 0;
116 CHECK(ParseAddress(line
, 0, address_length
, &start_address
));
118 uint64 end_address
= 0;
119 CHECK(ParseAddress(line
, address_length
+ 1, address_length
,
122 const uintptr_t current_func_addr
= reinterpret_cast<uintptr_t>(
123 &MakeFileHeaderLine
);
124 if (current_func_addr
>= start_address
&&
125 current_func_addr
< end_address
) {
130 CHECK(!result
.empty());
131 result
.append("\nsecs\tusecs\tpid:threadid\tfunc\n");
136 base::LazyInstance
<ThreadLogsManager
>::Leaky g_logs_manager
=
137 LAZY_INSTANCE_INITIALIZER
;
139 base::LazyInstance
<ImmutableFileHeaderLine
>::Leaky g_file_header_line
=
140 LAZY_INSTANCE_INITIALIZER
;
144 // Custom thread implementation that joins on destruction. Note that
145 // base::Thread has non-trivial dependencies on e.g. AtExitManager which makes
146 // it hard to use it early.
149 Thread(const base::Closure
& thread_callback
)
150 : thread_callback_(thread_callback
) {
151 CHECK_EQ(0, pthread_create(&handle_
, NULL
, &Thread::EntryPoint
, this));
155 CHECK_EQ(0, pthread_join(handle_
, NULL
));
159 static void* EntryPoint(void* data
) {
160 // Disable logging on this thread. Although this routine is not instrumented
161 // (cygprofile.gyp provides that), the called routines are and thus will
162 // call instrumentation.
163 CHECK(g_tls_log
== NULL
); // Must be 0 as this is a new thread.
164 g_tls_log
= kMagicBeingConstructed
;
166 Thread
* const instance
= reinterpret_cast<Thread
*>(data
);
167 instance
->thread_callback_
.Run();
171 const base::Closure thread_callback_
;
174 DISALLOW_COPY_AND_ASSIGN(Thread
);
177 // Single log entry recorded for each function call.
178 LogEntry::LogEntry(const void* address
)
179 : time(GetCurrentTime()),
185 ThreadLog::ThreadLog()
189 base::Bind(&ThreadLog::FlushInternal
, base::Unretained(this))) {
192 ThreadLog::ThreadLog(const FlushCallback
& flush_callback
)
195 flush_callback_(flush_callback
) {
198 ThreadLog::~ThreadLog() {
202 void ThreadLog::AddEntry(void* address
) {
207 CHECK_EQ(tid_
, GetTID());
208 const std::pair
<base::hash_set
<void*>::iterator
, bool> pair
=
209 called_functions_
.insert(address
);
210 const bool did_insert
= pair
.second
;
213 base::AutoLock
auto_lock(lock_
);
214 entries_
.push_back(LogEntry(address
));
215 // Crash in a quickly understandable way instead of crashing (or maybe not
216 // though) due to OOM.
217 CHECK_LE(entries_
.size(), kMaxBufferSize
);
223 void ThreadLog::TakeEntries(std::vector
<LogEntry
>* destination
) {
224 base::AutoLock
auto_lock(lock_
);
225 destination
->swap(entries_
);
226 STLClearObject(&entries_
);
229 void ThreadLog::Flush(std::vector
<LogEntry
>* entries
) const {
230 flush_callback_
.Run(entries
);
233 void ThreadLog::FlushInternal(std::vector
<LogEntry
>* entries
) const {
234 const std::string
log_filename(
236 kLogFilenameFormat
, kLogFileNamePrefix
, getpid(), tid_
, getppid()));
237 const base::ScopedFILE
file(fopen(log_filename
.c_str(), "a"));
240 const long offset
= ftell(file
.get());
242 fprintf(file
.get(), "%s", g_file_header_line
.Get().value
.c_str());
244 for (std::vector
<LogEntry
>::const_iterator it
= entries
->begin();
245 it
!= entries
->end(); ++it
) {
246 fprintf(file
.get(), "%ld %ld\t%d:%ld\t%p\n", it
->time
.tv_sec
,
247 it
->time
.tv_nsec
/ 1000, it
->pid
, it
->tid
, it
->address
);
250 STLClearObject(entries
);
253 ThreadLogsManager::ThreadLogsManager()
254 : wait_callback_(base::Bind(&SleepSec
, kFlushThreadIdleTimeSec
)) {
257 ThreadLogsManager::ThreadLogsManager(const base::Closure
& wait_callback
,
258 const base::Closure
& notify_callback
)
260 : wait_callback_(wait_callback
),
261 notify_callback_(notify_callback
) {
264 ThreadLogsManager::~ThreadLogsManager() {
265 // Note that the internal thread does some work until it sees |flush_thread_|
267 scoped_ptr
<Thread
> flush_thread
;
269 base::AutoLock
auto_lock(lock_
);
270 flush_thread_
.swap(flush_thread
);
272 flush_thread
.reset(); // Joins the flush thread.
274 STLDeleteContainerPointers(logs_
.begin(), logs_
.end());
277 void ThreadLogsManager::AddLog(scoped_ptr
<ThreadLog
> new_log
) {
278 base::AutoLock
auto_lock(lock_
);
281 StartInternalFlushThread_Locked();
283 logs_
.push_back(new_log
.release());
286 void ThreadLogsManager::StartInternalFlushThread_Locked() {
287 lock_
.AssertAcquired();
288 CHECK(!flush_thread_
);
289 // Note that the |flush_thread_| joins at destruction which guarantees that it
290 // will never outlive |this|, i.e. it's safe not to use ref-counting.
292 new Thread(base::Bind(&ThreadLogsManager::FlushAllLogsOnFlushThread
,
293 base::Unretained(this))));
296 // Type used below for flushing.
298 LogData(ThreadLog
* thread_log
) : thread_log(thread_log
) {}
300 ThreadLog
* const thread_log
;
301 std::vector
<LogEntry
> entries
;
304 void ThreadLogsManager::FlushAllLogsOnFlushThread() {
307 base::AutoLock
auto_lock(lock_
);
308 // The |flush_thread_| field is reset during destruction.
312 // Sleep for a few secs and then flush all thread's buffers. There is a
313 // danger that, when quitting Chrome, this thread may see unallocated data
314 // and segfault. We do not care because we need logs when Chrome is working.
315 wait_callback_
.Run();
317 // Copy the ThreadLog pointers to avoid acquiring both the logs manager's
318 // lock and the one for individual thread logs.
319 std::vector
<ThreadLog
*> thread_logs_copy
;
321 base::AutoLock
auto_lock(lock_
);
322 thread_logs_copy
= logs_
;
325 // Move the logs' data before flushing them so that the mutexes are not
326 // acquired for too long.
327 std::vector
<LogData
> logs
;
328 for (std::vector
<ThreadLog
*>::const_iterator it
=
329 thread_logs_copy
.begin();
330 it
!= thread_logs_copy
.end(); ++it
) {
331 ThreadLog
* const thread_log
= *it
;
332 LogData
log_data(thread_log
);
333 logs
.push_back(log_data
);
334 thread_log
->TakeEntries(&logs
.back().entries
);
337 for (std::vector
<LogData
>::iterator it
= logs
.begin();
338 it
!= logs
.end(); ++it
) {
339 if (!it
->entries
.empty())
340 it
->thread_log
->Flush(&it
->entries
);
343 if (!notify_callback_
.is_null())
344 notify_callback_
.Run();
350 // The GCC compiler callbacks, called on every function invocation providing
351 // addresses of caller and callee codes.
352 void __cyg_profile_func_enter(void* this_fn
, void* call_site
)
353 __attribute__((no_instrument_function
));
354 void __cyg_profile_func_exit(void* this_fn
, void* call_site
)
355 __attribute__((no_instrument_function
));
357 void __cyg_profile_func_enter(void* this_fn
, void* callee_unused
) {
358 if (g_tls_log
== NULL
) {
359 g_tls_log
= kMagicBeingConstructed
;
360 ThreadLog
* new_log
= new ThreadLog();
362 g_logs_manager
.Pointer()->AddLog(make_scoped_ptr(new_log
));
366 if (g_tls_log
!= kMagicBeingConstructed
)
367 g_tls_log
->AddEntry(this_fn
);
370 void __cyg_profile_func_exit(void* this_fn
, void* call_site
) {}
373 } // namespace cygprofile