OLD | NEW |
(Empty) | |
| 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. |
| 4 // |
| 5 // Tool to log the execution of the process (Chrome). Writes logs containing |
| 6 // time and address of the callback being called for the first time. |
| 7 // |
| 8 // To speed up the logging, buffering logs is implemented. Every thread have its |
| 9 // own buffer and log file so the contention between threads is minimal. As a |
| 10 // side-effect, functions called might be mentioned in many thread logs. |
| 11 // |
| 12 // To provide the log is accurate, the timer thread is created in every process. |
| 13 // It periodically flushes logs for the case some thread are slow or stopped |
| 14 // before flushing their logs. |
| 15 // |
| 16 // Note for the ChromeOS Chrome. Remove renderer process from the sandbox (add |
| 17 // --no-sandbox option to running Chrome in /sbin/session_manager_setup.sh). |
| 18 // Otherwise renderer will not be able to write logs (and will assert on that). |
| 19 // |
| 20 // Also note that the instrumentation code is self-activated. It begins to |
| 21 // record the log data when it is called first, including the run-time startup. |
| 22 // Have it in mind when modifying it, in particular do not use global objects |
| 23 // with constructors as they are called during startup (too late for us). |
| 24 |
| 25 #include <fcntl.h> |
| 26 #include <fstream> |
| 27 #include <pthread.h> |
| 28 #include <stdarg.h> |
| 29 #include <string> |
| 30 #include <sys/stat.h> |
| 31 #include <sys/syscall.h> |
| 32 #include <sys/time.h> |
| 33 #include <sys/types.h> |
| 34 #include <unordered_set> |
| 35 #include <vector> |
| 36 |
| 37 #include "base/lazy_instance.h" |
| 38 #include "base/logging.h" |
| 39 #include "base/memory/singleton.h" |
| 40 #include "base/synchronization/lock.h" |
| 41 |
| 42 namespace cygprofile { |
| 43 |
| 44 extern "C" { |
| 45 |
| 46 // Note that these are linked internally by the compiler. Don't call |
| 47 // them directly! |
| 48 void __cyg_profile_func_enter(void* this_fn, void* call_site) |
| 49 __attribute__((no_instrument_function)); |
| 50 void __cyg_profile_func_exit(void* this_fn, void* call_site) |
| 51 __attribute__((no_instrument_function)); |
| 52 |
| 53 } |
| 54 |
| 55 // Single log entry layout. |
| 56 struct CygLogEntry { |
| 57 time_t seconds; |
| 58 long int usec; |
| 59 pid_t pid; |
| 60 pthread_t tid; |
| 61 const void* this_fn; |
| 62 CygLogEntry(time_t seconds, long int usec, |
| 63 pid_t pid, pthread_t tid, const void* this_fn) |
| 64 : seconds(seconds), usec(usec), |
| 65 pid(pid), tid(tid), this_fn(this_fn) {} |
| 66 }; |
| 67 |
| 68 // Common data for the process. Singleton. |
| 69 class CygCommon { |
| 70 public: |
| 71 static CygCommon* GetInstance(); |
| 72 std::string header() const { return header_line_; } |
| 73 private: |
| 74 CygCommon(); |
| 75 std::string header_line_; |
| 76 friend struct DefaultSingletonTraits<CygCommon>; |
| 77 |
| 78 DISALLOW_COPY_AND_ASSIGN(CygCommon); |
| 79 }; |
| 80 |
| 81 // Returns light-weight process ID. On linux, this is a system-wide |
| 82 // unique thread id. |
| 83 static pid_t GetLwp() { |
| 84 return syscall(__NR_gettid); |
| 85 } |
| 86 |
| 87 // A per-thread structure representing the log itself. |
| 88 class CygTlsLog { |
| 89 public: |
| 90 CygTlsLog() |
| 91 : in_use_(false), lwp_(GetLwp()), pthread_self_(pthread_self()) { } |
| 92 |
| 93 // Enter a log entity. |
| 94 void LogEnter(void* this_fn); |
| 95 |
| 96 // Add newly created CygTlsLog object to the list of all such objects. |
| 97 // Needed for the timer callback: it will enumerate each object and flush. |
| 98 static void AddNewLog(CygTlsLog* newlog); |
| 99 |
| 100 // Starts a thread in this process that periodically flushes all the threads. |
| 101 // Must be called once per process. |
| 102 static void StartFlushLogThread(); |
| 103 |
| 104 private: |
| 105 static const int kBufMaxSize; |
| 106 static const char kLogFilenameFmt[]; |
| 107 |
| 108 // Flush the log to file. Create file if needed. |
| 109 // Must be called with locked log_mutex_. |
| 110 void FlushLog(); |
| 111 |
| 112 // Fork hooks. Needed to keep data in consistent state during fork(). |
| 113 static void AtForkPrepare(); |
| 114 static void AtForkParent(); |
| 115 static void AtForkChild(); |
| 116 |
| 117 // Thread callback to flush all logs periodically. |
| 118 static void* FlushLogThread(void*); |
| 119 |
| 120 std::string log_filename_; |
| 121 std::vector<CygLogEntry> buf_; |
| 122 |
| 123 // A lock that guards buf_ usage between per-thread instrumentation |
| 124 // routine and timer flush callback. So the contention could happen |
| 125 // only during the flush, every 30 secs. |
| 126 base::Lock log_mutex_; |
| 127 |
| 128 // Current thread is inside the instrumentation routine. |
| 129 bool in_use_; |
| 130 |
| 131 // Keeps track of all functions that have been logged on this thread |
| 132 // so we do not record dublicates. |
| 133 std::unordered_set<void*> functions_called_; |
| 134 |
| 135 // Thread identifier as Linux kernel shows it. For debugging purposes. |
| 136 // LWP (light-weight process) is a unique ID of the thread in the system, |
| 137 // unlike pthread_self() which is the same for fork()-ed threads. |
| 138 pid_t lwp_; |
| 139 pthread_t pthread_self_; |
| 140 |
| 141 DISALLOW_COPY_AND_ASSIGN(CygTlsLog); |
| 142 }; |
| 143 |
| 144 // Storage for logs for all threads in the process. |
| 145 // Using std::list may be better, but it fails when used before main(). |
| 146 struct AllLogs { |
| 147 std::vector<CygTlsLog*> logs; |
| 148 base::Lock mutex; |
| 149 }; |
| 150 |
| 151 base::LazyInstance<AllLogs>::Leaky all_logs_ = LAZY_INSTANCE_INITIALIZER; |
| 152 |
| 153 // Per-thread pointer to the current log object. |
| 154 __thread CygTlsLog* tls_current_log = NULL; |
| 155 |
| 156 // Magic value of above to prevent the instrumentation. Used when CygTlsLog is |
| 157 // being constructed (to prevent reentering by malloc, for example) and by |
| 158 // the FlushLogThread (to prevent it being logged - see comment in its code). |
| 159 CygTlsLog* const kMagicBeingConstructed = reinterpret_cast<CygTlsLog*>(1); |
| 160 |
| 161 // Number of entries in the per-thread log buffer before we flush. |
| 162 // Note, that we also flush by timer so not all thread logs may grow up to this. |
| 163 const int CygTlsLog::kBufMaxSize = 3000; |
| 164 |
| 165 // "cyglog.PID.LWP.pthread_self.PPID" |
| 166 const char CygTlsLog::kLogFilenameFmt[] = "/var/log/chrome/cyglog.%d.%d.%ld-%d"; |
| 167 |
| 168 CygCommon* CygCommon::GetInstance() { |
| 169 return Singleton<CygCommon>::get(); |
| 170 } |
| 171 |
| 172 CygCommon::CygCommon() { |
| 173 // Determine our module addresses. |
| 174 std::ifstream mapsfile("/proc/self/maps"); |
| 175 CHECK(mapsfile.good()); |
| 176 static const int kMaxLineSize = 512; |
| 177 char line[kMaxLineSize]; |
| 178 void (*this_fn)(void) = |
| 179 reinterpret_cast<void(*)()>(__cyg_profile_func_enter); |
| 180 while (mapsfile.getline(line, kMaxLineSize)) { |
| 181 const std::string str_line = line; |
| 182 size_t permindex = str_line.find("r-xp"); |
| 183 if (permindex != std::string::npos) { |
| 184 int dashindex = str_line.find("-"); |
| 185 int spaceindex = str_line.find(" "); |
| 186 char* p; |
| 187 void* start = reinterpret_cast<void*> |
| 188 (strtol((str_line.substr(0, dashindex)).c_str(), |
| 189 &p, 16)); |
| 190 CHECK(*p == 0); // Could not determine start address. |
| 191 void* end = reinterpret_cast<void*> |
| 192 (strtol((str_line.substr(dashindex + 1, |
| 193 spaceindex - dashindex - 1)).c_str(), |
| 194 &p, 16)); |
| 195 CHECK(*p == 0); // Could not determine end address. |
| 196 |
| 197 if (this_fn >= start && this_fn < end) |
| 198 header_line_ = str_line; |
| 199 } |
| 200 } |
| 201 mapsfile.close(); |
| 202 header_line_.append("\nsecs\tmsecs\tpid:threadid\tfunc\n"); |
| 203 } |
| 204 |
| 205 void CygTlsLog::LogEnter(void* this_fn) { |
| 206 if (in_use_) |
| 207 return; |
| 208 in_use_ = true; |
| 209 |
| 210 if (functions_called_.find(this_fn) == |
| 211 functions_called_.end()) { |
| 212 functions_called_.insert(this_fn); |
| 213 base::AutoLock lock(log_mutex_); |
| 214 if (buf_.capacity() < kBufMaxSize) |
| 215 buf_.reserve(kBufMaxSize); |
| 216 struct timeval timestamp; |
| 217 gettimeofday(×tamp, NULL); |
| 218 buf_.push_back(CygLogEntry(time(NULL), timestamp.tv_usec, |
| 219 getpid(), pthread_self(), this_fn)); |
| 220 if (buf_.size() == kBufMaxSize) { |
| 221 FlushLog(); |
| 222 } |
| 223 } |
| 224 |
| 225 in_use_ = false; |
| 226 } |
| 227 |
| 228 void CygTlsLog::AtForkPrepare() { |
| 229 CHECK(tls_current_log); |
| 230 CHECK(tls_current_log->lwp_ == GetLwp()); |
| 231 CHECK(tls_current_log->pthread_self_ == pthread_self()); |
| 232 all_logs_.Get().mutex.Acquire(); |
| 233 } |
| 234 |
| 235 void CygTlsLog::AtForkParent() { |
| 236 CHECK(tls_current_log); |
| 237 CHECK(tls_current_log->lwp_ == GetLwp()); |
| 238 CHECK(tls_current_log->pthread_self_ == pthread_self()); |
| 239 all_logs_.Get().mutex.Release(); |
| 240 } |
| 241 |
| 242 void CygTlsLog::AtForkChild() { |
| 243 CHECK(tls_current_log); |
| 244 |
| 245 // Update the IDs of this new thread of the new process. |
| 246 // Note that the process may (and Chrome main process forks zygote this way) |
| 247 // call exec(self) after we return (to launch new shiny self). If done like |
| 248 // that, PID and LWP will remain the same, but pthread_self() changes. |
| 249 pid_t lwp = GetLwp(); |
| 250 CHECK(tls_current_log->lwp_ != lwp); // LWP is system-wide unique thread ID. |
| 251 tls_current_log->lwp_ = lwp; |
| 252 |
| 253 CHECK(tls_current_log->pthread_self_ == pthread_self()); |
| 254 |
| 255 // Leave the only current thread tls object because fork() clones only the |
| 256 // current thread (the one that called fork) to the child process. |
| 257 AllLogs& all_logs = all_logs_.Get(); |
| 258 all_logs.logs.clear(); |
| 259 all_logs.logs.push_back(tls_current_log); |
| 260 CHECK(all_logs.logs.size() == 1); |
| 261 |
| 262 // Clear log filename so it will be re-calculated with the new PIDs. |
| 263 tls_current_log->log_filename_.clear(); |
| 264 |
| 265 // Create the thread that will periodically flush all logs for this process. |
| 266 StartFlushLogThread(); |
| 267 |
| 268 // We do not update log header line (CygCommon data) as it will be the same |
| 269 // because the new process is just a forked copy. |
| 270 all_logs.mutex.Release(); |
| 271 } |
| 272 |
| 273 void CygTlsLog::StartFlushLogThread() { |
| 274 pthread_t tid; |
| 275 CHECK(!pthread_create(&tid, NULL, &CygTlsLog::FlushLogThread, NULL)); |
| 276 } |
| 277 |
| 278 void CygTlsLog::AddNewLog(CygTlsLog* newlog) { |
| 279 CHECK(tls_current_log == kMagicBeingConstructed); |
| 280 AllLogs& all_logs = all_logs_.Get(); |
| 281 base::AutoLock lock(all_logs.mutex); |
| 282 if (all_logs.logs.empty()) { |
| 283 CHECK(!pthread_atfork(CygTlsLog::AtForkPrepare, |
| 284 CygTlsLog::AtForkParent, |
| 285 CygTlsLog::AtForkChild)); |
| 286 |
| 287 // The very first process starts its flush thread here. Forked processes |
| 288 // will do it in AtForkChild(). |
| 289 StartFlushLogThread(); |
| 290 } |
| 291 all_logs.logs.push_back(newlog); |
| 292 } |
| 293 |
| 294 // Printf-style routine to write to open file. |
| 295 static void WriteLogLine(int fd, const char* fmt, ...) { |
| 296 va_list arg_ptr; |
| 297 va_start(arg_ptr, fmt); |
| 298 char msg[160]; |
| 299 int len = vsnprintf(msg, sizeof(msg), fmt, arg_ptr); |
| 300 int rc = write(fd, msg, (len > sizeof(msg))? sizeof(msg): len); |
| 301 va_end(arg_ptr); |
| 302 }; |
| 303 |
| 304 void CygTlsLog::FlushLog() { |
| 305 bool first_log_write = false; |
| 306 |
| 307 if (log_filename_.empty()) { |
| 308 first_log_write = true; |
| 309 char buf[80]; |
| 310 snprintf(buf, sizeof(buf), kLogFilenameFmt, |
| 311 getpid(), lwp_, pthread_self_, getppid()); |
| 312 log_filename_ = buf; |
| 313 unlink(log_filename_.c_str()); |
| 314 } |
| 315 |
| 316 int file = open(log_filename_.c_str(), O_CREAT | O_WRONLY | O_APPEND, 00600); |
| 317 CHECK(file != -1); |
| 318 |
| 319 if (first_log_write) |
| 320 WriteLogLine(file, "%s", CygCommon::GetInstance()->header().c_str()); |
| 321 |
| 322 for (int i=0; i != buf_.size(); ++i) { |
| 323 const CygLogEntry& p = buf_[i]; |
| 324 WriteLogLine(file, "%ld %ld\t%d:%ld\t%p\n", |
| 325 p.seconds, p.usec, p.pid, p.tid, p.this_fn); |
| 326 } |
| 327 |
| 328 close(file); |
| 329 buf_.clear(); |
| 330 } |
| 331 |
| 332 void* CygTlsLog::FlushLogThread(void*) { |
| 333 // Disable logging this thread. Although this routine is not instrumented |
| 334 // (cygprofile.gyp provides that), the called routines are and thus will |
| 335 // call instrumentation. |
| 336 CHECK(tls_current_log == NULL); // Must be 0 as this is a new thread. |
| 337 tls_current_log = kMagicBeingConstructed; |
| 338 |
| 339 // Run this loop infinitely: sleep 30 secs and the flush all thread's |
| 340 // buffers. There is a danger that, when quitting Chrome, this thread may |
| 341 // see unallocated data and segfault. We do not care because we need logs |
| 342 // when Chrome is working. |
| 343 while (true) { |
| 344 for(unsigned int secs_to_sleep = 30; secs_to_sleep != 0;) |
| 345 secs_to_sleep = sleep(secs_to_sleep); |
| 346 |
| 347 AllLogs& all_logs = all_logs_.Get(); |
| 348 base::AutoLock lock(all_logs.mutex); |
| 349 for (int i = 0; i != all_logs.logs.size(); ++i) { |
| 350 CygTlsLog* current_log = all_logs.logs[i]; |
| 351 base::AutoLock current_lock(current_log->log_mutex_); |
| 352 if (current_log->buf_.size()) { |
| 353 current_log->FlushLog(); |
| 354 } else { |
| 355 // The thread's log is still empty. Probably the thread finished prior |
| 356 // to previous timer fired - deallocate its buffer. Even if the thread |
| 357 // ever resumes, it will allocate its buffer again in |
| 358 // std::vector::push_back(). |
| 359 current_log->buf_.clear(); |
| 360 } |
| 361 } |
| 362 } |
| 363 } |
| 364 |
| 365 // Gcc Compiler callback, called on every function invocation providing |
| 366 // addresses of caller and callee codes. |
| 367 void __cyg_profile_func_enter(void* this_fn, void* callee_unused) { |
| 368 if (tls_current_log == NULL) { |
| 369 tls_current_log = kMagicBeingConstructed; |
| 370 CygTlsLog* newlog = new CygTlsLog; |
| 371 CHECK(newlog); |
| 372 CygTlsLog::AddNewLog(newlog); |
| 373 tls_current_log = newlog; |
| 374 } |
| 375 if (tls_current_log != kMagicBeingConstructed) { |
| 376 tls_current_log->LogEnter(this_fn); |
| 377 } |
| 378 } |
| 379 |
| 380 // Gcc Compiler callback, called after every function invocation providing |
| 381 // addresses of caller and callee codes. |
| 382 void __cyg_profile_func_exit(void* this_fn, void* call_site) { |
| 383 } |
| 384 |
| 385 } // namespace cygprofile |
OLD | NEW |