// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "tools/cygprofile/cygprofile.h" #include #include #include #include #include #include #include #include #include #include #include "base/bind.h" #include "base/containers/hash_tables.h" #include "base/files/scoped_file.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/macros.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "base/synchronization/lock.h" namespace cygprofile { namespace { // Allow 8 MBytes of data for each thread log. const int kMaxBufferSize = 8 * 1024 * 1024 / sizeof(LogEntry); // Have the background internal thread do its flush every 15 sec. const int kFlushThreadIdleTimeSec = 15; const char kLogFileNamePrefix[] = "/data/local/tmp/chrome/cyglog/"; // "cyglog.PID.LWP.PPID" const char kLogFilenameFormat[] = "%scyglog.%d.%d-%d"; // Magic value of above to prevent instrumentation. Used when ThreadLog is being // constructed (to prevent reentering by malloc, for example) and by the flush // log thread (to prevent it from being logged0. ThreadLog* const kMagicBeingConstructed = reinterpret_cast(1); // Per-thread pointer to the current log object. static __thread ThreadLog* g_tls_log = NULL; // Returns light-weight process ID. On Linux, this is a system-wide unique // thread id. pid_t GetTID() { return syscall(__NR_gettid); } timespec GetCurrentTime() { timespec timestamp; clock_gettime(CLOCK_MONOTONIC, ×tamp); return timestamp; } // Sleeps for |sec| seconds. void SleepSec(int sec) { for (int secs_to_sleep = sec; secs_to_sleep != 0;) secs_to_sleep = sleep(secs_to_sleep); } // Exposes the string header that will appear at the top of every trace file. // This string contains memory mapping information for the mapped // library/executable which is used offline during symbolization. Note that // this class is meant to be instantiated once per process and lazily (during // the first flush). struct ImmutableFileHeaderLine { ImmutableFileHeaderLine() : value(MakeFileHeaderLine()) {} const std::string value; private: // Returns whether the integer representation of the hexadecimal address // stored in |line| at position |start_offset| was successfully stored in // |result|. static bool ParseAddress(const std::string& line, off_t start_offset, size_t length, uint64* result) { if (start_offset >= line.length()) return false; uint64 address; const bool ret = HexStringToUInt64( base::StringPiece(line.c_str() + start_offset, length), &address); if (!ret) return false; *result = address; return true; } // Parses /proc/self/maps and returns a two line string such as: // 758c6000-79f4b000 r-xp 00000000 b3:17 309475 libchrome.2009.0.so // secs usecs pid:threadid func static std::string MakeFileHeaderLine() { std::ifstream mapsfile("/proc/self/maps"); CHECK(mapsfile.good()); std::string result; for (std::string line; std::getline(mapsfile, line); ) { if (line.find("r-xp") == std::string::npos) continue; const size_t address_length = line.find('-'); uint64 start_address = 0; CHECK(ParseAddress(line, 0, address_length, &start_address)); uint64 end_address = 0; CHECK(ParseAddress(line, address_length + 1, address_length, &end_address)); const uintptr_t current_func_addr = reinterpret_cast( &MakeFileHeaderLine); if (current_func_addr >= start_address && current_func_addr < end_address) { result.swap(line); break; } } CHECK(!result.empty()); result.append("\nsecs\tusecs\tpid:threadid\tfunc\n"); return result; } }; base::LazyInstance::Leaky g_logs_manager = LAZY_INSTANCE_INITIALIZER; base::LazyInstance::Leaky g_file_header_line = LAZY_INSTANCE_INITIALIZER; } // namespace // Custom thread implementation that joins on destruction. Note that // base::Thread has non-trivial dependencies on e.g. AtExitManager which makes // it hard to use it early. class Thread { public: Thread(const base::Closure& thread_callback) : thread_callback_(thread_callback) { CHECK_EQ(0, pthread_create(&handle_, NULL, &Thread::EntryPoint, this)); } ~Thread() { CHECK_EQ(0, pthread_join(handle_, NULL)); } private: static void* EntryPoint(void* data) { // Disable logging on this thread. Although this routine is not instrumented // (cygprofile.gyp provides that), the called routines are and thus will // call instrumentation. CHECK(g_tls_log == NULL); // Must be 0 as this is a new thread. g_tls_log = kMagicBeingConstructed; Thread* const instance = reinterpret_cast(data); instance->thread_callback_.Run(); return NULL; } const base::Closure thread_callback_; pthread_t handle_; DISALLOW_COPY_AND_ASSIGN(Thread); }; // Single log entry recorded for each function call. LogEntry::LogEntry(const void* address) : time(GetCurrentTime()), pid(getpid()), tid(GetTID()), address(address) { } ThreadLog::ThreadLog() : tid_(GetTID()), in_use_(false), flush_callback_( base::Bind(&ThreadLog::FlushInternal, base::Unretained(this))) { } ThreadLog::ThreadLog(const FlushCallback& flush_callback) : tid_(GetTID()), in_use_(false), flush_callback_(flush_callback) { } ThreadLog::~ThreadLog() { g_tls_log = NULL; } void ThreadLog::AddEntry(void* address) { if (in_use_) return; in_use_ = true; CHECK_EQ(tid_, GetTID()); const std::pair::iterator, bool> pair = called_functions_.insert(address); const bool did_insert = pair.second; if (did_insert) { base::AutoLock auto_lock(lock_); entries_.push_back(LogEntry(address)); // Crash in a quickly understandable way instead of crashing (or maybe not // though) due to OOM. CHECK_LE(entries_.size(), kMaxBufferSize); } in_use_ = false; } void ThreadLog::TakeEntries(std::vector* destination) { base::AutoLock auto_lock(lock_); destination->swap(entries_); STLClearObject(&entries_); } void ThreadLog::Flush(std::vector* entries) const { flush_callback_.Run(entries); } void ThreadLog::FlushInternal(std::vector* entries) const { const std::string log_filename( base::StringPrintf( kLogFilenameFormat, kLogFileNamePrefix, getpid(), tid_, getppid())); const base::ScopedFILE file(fopen(log_filename.c_str(), "a")); CHECK(file.get()); const long offset = ftell(file.get()); if (offset == 0) fprintf(file.get(), "%s", g_file_header_line.Get().value.c_str()); for (std::vector::const_iterator it = entries->begin(); it != entries->end(); ++it) { fprintf(file.get(), "%ld %ld\t%d:%ld\t%p\n", it->time.tv_sec, it->time.tv_nsec / 1000, it->pid, it->tid, it->address); } STLClearObject(entries); } ThreadLogsManager::ThreadLogsManager() : wait_callback_(base::Bind(&SleepSec, kFlushThreadIdleTimeSec)) { } ThreadLogsManager::ThreadLogsManager(const base::Closure& wait_callback, const base::Closure& notify_callback) : wait_callback_(wait_callback), notify_callback_(notify_callback) { } ThreadLogsManager::~ThreadLogsManager() { // Note that the internal thread does some work until it sees |flush_thread_| // = NULL. scoped_ptr flush_thread; { base::AutoLock auto_lock(lock_); flush_thread_.swap(flush_thread); } flush_thread.reset(); // Joins the flush thread. STLDeleteContainerPointers(logs_.begin(), logs_.end()); } void ThreadLogsManager::AddLog(scoped_ptr new_log) { base::AutoLock auto_lock(lock_); if (logs_.empty()) StartInternalFlushThread_Locked(); logs_.push_back(new_log.release()); } void ThreadLogsManager::StartInternalFlushThread_Locked() { lock_.AssertAcquired(); CHECK(!flush_thread_); // Note that the |flush_thread_| joins at destruction which guarantees that it // will never outlive |this|, i.e. it's safe not to use ref-counting. flush_thread_.reset( new Thread(base::Bind(&ThreadLogsManager::FlushAllLogsOnFlushThread, base::Unretained(this)))); } // Type used below for flushing. struct LogData { LogData(ThreadLog* thread_log) : thread_log(thread_log) {} ThreadLog* const thread_log; std::vector entries; }; void ThreadLogsManager::FlushAllLogsOnFlushThread() { while (true) { { base::AutoLock auto_lock(lock_); // The |flush_thread_| field is reset during destruction. if (!flush_thread_) return; } // Sleep for a few secs and then flush all thread's buffers. There is a // danger that, when quitting Chrome, this thread may see unallocated data // and segfault. We do not care because we need logs when Chrome is working. wait_callback_.Run(); // Copy the ThreadLog pointers to avoid acquiring both the logs manager's // lock and the one for individual thread logs. std::vector thread_logs_copy; { base::AutoLock auto_lock(lock_); thread_logs_copy = logs_; } // Move the logs' data before flushing them so that the mutexes are not // acquired for too long. std::vector logs; for (std::vector::const_iterator it = thread_logs_copy.begin(); it != thread_logs_copy.end(); ++it) { ThreadLog* const thread_log = *it; LogData log_data(thread_log); logs.push_back(log_data); thread_log->TakeEntries(&logs.back().entries); } for (std::vector::iterator it = logs.begin(); it != logs.end(); ++it) { if (!it->entries.empty()) it->thread_log->Flush(&it->entries); } if (!notify_callback_.is_null()) notify_callback_.Run(); } } extern "C" { // The GCC compiler callbacks, called on every function invocation providing // addresses of caller and callee codes. void __cyg_profile_func_enter(void* this_fn, void* call_site) __attribute__((no_instrument_function)); void __cyg_profile_func_exit(void* this_fn, void* call_site) __attribute__((no_instrument_function)); void __cyg_profile_func_enter(void* this_fn, void* callee_unused) { if (g_tls_log == NULL) { g_tls_log = kMagicBeingConstructed; ThreadLog* new_log = new ThreadLog(); CHECK(new_log); g_logs_manager.Pointer()->AddLog(make_scoped_ptr(new_log)); g_tls_log = new_log; } if (g_tls_log != kMagicBeingConstructed) g_tls_log->AddEntry(this_fn); } void __cyg_profile_func_exit(void* this_fn, void* call_site) {} } // extern "C" } // namespace cygprofile