Chromium Code Reviews| 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) { | |
|
brettw
2012/05/15 22:59:54
Style nit: spaces around =
glotov
2012/05/16 10:49:15
Done.
| |
| 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;) | |
|
brettw
2012/05/15 22:59:54
Style nit: don't use unsigned here.
glotov
2012/05/16 10:49:15
Done.
| |
| 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 |