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/logging.h" | |
| 38 #include "base/memory/singleton.h" | |
| 39 #include "base/synchronization/lock.h" | |
| 40 | |
| 41 namespace cygprofile { | |
| 42 | |
| 43 extern "C" { | |
| 44 | |
| 45 // Note that these are linked internally by the compiler. Don't call | |
| 46 // them directly! | |
| 47 void __cyg_profile_func_enter(void* this_fn, void* call_site) | |
| 48 __attribute__((no_instrument_function)); | |
| 49 void __cyg_profile_func_exit(void* this_fn, void* call_site) | |
| 50 __attribute__((no_instrument_function)); | |
| 51 | |
| 52 } | |
| 53 | |
| 54 // Single log entry layout. | |
| 55 struct CygLogEntry { | |
| 56 time_t seconds; | |
| 57 long int usec; | |
| 58 pid_t pid; | |
| 59 pthread_t tid; | |
| 60 const void* this_fn; | |
| 61 CygLogEntry(time_t _seconds, long int _usec, | |
|
Dmitry Polukhin
2011/12/12 06:33:02
Please don't add _ before name. Actually you can u
glotov
2011/12/13 19:45:43
Good. Will try.
| |
| 62 pid_t _pid, pthread_t _tid, const void* _this_fn) | |
| 63 : seconds(_seconds), usec(_usec), | |
| 64 pid(_pid), tid(_tid), this_fn(_this_fn) {} | |
| 65 }; | |
| 66 | |
| 67 // Common data for the process. Singleton. | |
| 68 class CygCommon { | |
| 69 public: | |
| 70 static CygCommon* GetInstance(); | |
| 71 std::string header() const { return header_line_; } | |
| 72 private: | |
| 73 CygCommon(); | |
| 74 std::string header_line_; | |
| 75 friend struct DefaultSingletonTraits<CygCommon>; | |
| 76 | |
| 77 DISALLOW_COPY_AND_ASSIGN(CygCommon); | |
| 78 }; | |
| 79 | |
| 80 // Returns light-weight process ID. On linux, this is a system-wide | |
| 81 // unique thread id. | |
| 82 static pid_t GetLwp() { | |
| 83 return syscall(__NR_gettid); | |
| 84 } | |
| 85 | |
| 86 // A per-thread structure representing the log itself. | |
| 87 class CygTlsLog { | |
| 88 public: | |
| 89 CygTlsLog() | |
| 90 : in_use_(false), lwp_(GetLwp()), pthread_self_(pthread_self()) { } | |
| 91 | |
| 92 // Enter a log entity. | |
| 93 void LogEnter(void* this_fn); | |
| 94 | |
| 95 // Add newly created CygTlsLog object to the list of all such objects. | |
| 96 // Needed for the timer callback: it will enumerate each object and flush. | |
| 97 static void AddNewLog(CygTlsLog* newlog); | |
| 98 | |
| 99 // Starts a thread in this process that periodically flushes all the threads. | |
| 100 // Must be called once per process. | |
| 101 static void StartFlushLogThread(); | |
| 102 | |
| 103 private: | |
| 104 static const int kBufMaxSize; | |
| 105 static const char kLogFilenameFmt[]; | |
| 106 | |
| 107 // Flush the log to file. Create file if needed. | |
| 108 // Must be called with locked log_mutex_. | |
| 109 void FlushLog(); | |
| 110 | |
| 111 // Fork hooks. Needed to keep data in consistent state during fork(). | |
| 112 static void AtForkPrepare(); | |
| 113 static void AtForkParent(); | |
| 114 static void AtForkChild(); | |
| 115 | |
| 116 // Thread callback to flush all logs periodically. | |
| 117 static void* FlushLogThread(void*); | |
| 118 | |
| 119 std::string log_filename_; | |
| 120 std::vector<CygLogEntry> buf_; | |
| 121 | |
| 122 // A lock that guards buf_ usage between per-thread instrumentation | |
| 123 // routine and timer flush callback. So the contention could happen | |
| 124 // only during the flush, every 5 secs. | |
| 125 base::Lock log_mutex_; | |
| 126 | |
| 127 // Current thread is inside the instrumentation routine. | |
| 128 bool in_use_; | |
| 129 | |
| 130 // Keeps track of all functions that have been logged on this thread | |
| 131 // so we do not record dublicates. | |
| 132 std::unordered_set<void*> functions_called_; | |
| 133 | |
| 134 // Thread identifier as Linux kernel shows it. For debugging purposes. | |
| 135 // LWP (light-weight process) is a unique ID of the thread in the system, | |
| 136 // unlike pthread_self() which is the same for fork()-ed threads. | |
| 137 pid_t lwp_; | |
| 138 pthread_t pthread_self_; | |
| 139 | |
| 140 // Storage of all logs within the process. std::list is better, but it fails | |
| 141 // when used before main(). The static data are wrapped into functions for | |
| 142 // the purpose to be able to use it before runtime startup finishes. | |
| 143 static std::vector<CygTlsLog*>& all_logs() { | |
| 144 static std::vector<CygTlsLog*> all_logs_; | |
|
Dmitry Polukhin
2011/12/12 06:33:02
I wouldn't use statics with c-tor/d-tor in this co
glotov
2011/12/13 19:45:43
Good point. Worth doing.
I thought about using La
| |
| 145 return all_logs_; | |
| 146 } | |
| 147 static base::Lock& all_logs_mutex() { | |
| 148 static base::Lock all_logs_mutex_; | |
|
Dmitry Polukhin
2011/12/12 06:33:02
I would use pthread primitives directly with with
glotov
2011/12/13 19:45:43
What do you mean?
On 2011/12/12 06:33:02, Dmitry
dpolukhin
2011/12/14 07:10:51
It is static variable with c-tor/d-tor I recommend
glotov
2012/04/05 13:50:20
Done.
| |
| 149 return all_logs_mutex_; | |
| 150 } | |
| 151 | |
| 152 DISALLOW_COPY_AND_ASSIGN(CygTlsLog); | |
| 153 }; | |
| 154 | |
| 155 // Per-thread pointer to the current log object. | |
| 156 __thread CygTlsLog* tls_current_log = NULL; | |
| 157 | |
| 158 // Magic value of above to prevent the instrumentation. Used when CygTlsLog is | |
| 159 // being constructed (to prevent reentering by malloc, for example) and by | |
| 160 // the FlushLogThread (to prevent it being logged - see comment in its code). | |
| 161 CygTlsLog* const kMagicBeingConstructed = reinterpret_cast<CygTlsLog*>(1); | |
| 162 | |
| 163 // Number of entries in the per-thread log buffer before we flush. | |
| 164 // Note, that we also flush by timer so not all thread logs may grow up to this. | |
| 165 const int CygTlsLog::kBufMaxSize = 3000; | |
| 166 | |
| 167 // "cyglog.PID.LWP.pthread_self" | |
| 168 const char CygTlsLog::kLogFilenameFmt[] = "/var/log/chrome/cyglog.%d.%d.%ld"; | |
| 169 | |
| 170 CygCommon* CygCommon::GetInstance() { | |
| 171 return Singleton<CygCommon>::get(); | |
| 172 } | |
| 173 | |
| 174 CygCommon::CygCommon() { | |
| 175 // Determine our module addresses. | |
| 176 std::ifstream mapsfile("/proc/self/maps"); | |
| 177 CHECK(mapsfile.good()); | |
| 178 static const int kMaxLineSize = 512; | |
| 179 char line[kMaxLineSize]; | |
| 180 void (*this_fn)(void) = | |
| 181 reinterpret_cast<void(*)()>(__cyg_profile_func_enter); | |
| 182 while (mapsfile.getline(line, kMaxLineSize)) { | |
| 183 const std::string str_line = line; | |
| 184 size_t permindex = str_line.find("r-xp"); | |
| 185 if (permindex != std::string::npos) { | |
| 186 int dashindex = str_line.find("-"); | |
| 187 int spaceindex = str_line.find(" "); | |
| 188 char* p; | |
| 189 void* start = reinterpret_cast<void*> | |
| 190 (strtol((str_line.substr(0, dashindex)).c_str(), | |
| 191 &p, 16)); | |
| 192 CHECK(*p == 0); // Could not determine start address. | |
| 193 void* end = reinterpret_cast<void*> | |
| 194 (strtol((str_line.substr(dashindex + 1, | |
| 195 spaceindex - dashindex - 1)).c_str(), | |
| 196 &p, 16)); | |
| 197 CHECK(*p == 0); // Could not determine end address. | |
| 198 | |
| 199 if (this_fn >= start && this_fn < end) | |
| 200 header_line_ = str_line; | |
| 201 } | |
| 202 } | |
| 203 mapsfile.close(); | |
| 204 header_line_.append("\nsecs\tmsecs\tpid:threadid\tfunc\n"); | |
| 205 } | |
| 206 | |
| 207 void CygTlsLog::LogEnter(void* this_fn) { | |
| 208 if (in_use_) | |
| 209 return; | |
| 210 in_use_ = true; | |
| 211 | |
| 212 if (functions_called_.find(this_fn) == | |
| 213 functions_called_.end()) { | |
| 214 functions_called_.insert(this_fn); | |
| 215 base::AutoLock lock(log_mutex_); | |
| 216 if (buf_.capacity() < kBufMaxSize) | |
| 217 buf_.reserve(kBufMaxSize); | |
| 218 struct timeval timestamp; | |
| 219 gettimeofday(×tamp, NULL); | |
| 220 buf_.push_back(CygLogEntry(time(NULL), timestamp.tv_usec, | |
| 221 getpid(), pthread_self(), this_fn)); | |
| 222 if (buf_.size() == kBufMaxSize) { | |
| 223 FlushLog(); | |
| 224 } | |
| 225 } | |
| 226 | |
| 227 in_use_ = false; | |
| 228 } | |
| 229 | |
| 230 void CygTlsLog::AtForkPrepare() { | |
| 231 CHECK(tls_current_log); | |
| 232 CHECK(tls_current_log->lwp_ == GetLwp()); | |
| 233 CHECK(tls_current_log->pthread_self_ == pthread_self()); | |
| 234 all_logs_mutex().Acquire(); | |
| 235 } | |
| 236 | |
| 237 void CygTlsLog::AtForkParent() { | |
| 238 CHECK(tls_current_log); | |
| 239 CHECK(tls_current_log->lwp_ == GetLwp()); | |
| 240 CHECK(tls_current_log->pthread_self_ == pthread_self()); | |
| 241 all_logs_mutex().Release(); | |
| 242 } | |
| 243 | |
| 244 void CygTlsLog::AtForkChild() { | |
| 245 CHECK(tls_current_log); | |
| 246 | |
| 247 // Update the IDs of this new thread of the new process. | |
| 248 // Note that the process may (and Chrome main process forks zygote this way) | |
| 249 // call exec(self) after we return (to launch new shiny self). If done like | |
| 250 // that, PID and LWP will remain the same, but pthread_self() changes. | |
| 251 pid_t lwp = GetLwp(); | |
| 252 CHECK(tls_current_log->lwp_ != lwp); // LWP is system-wide unique thread ID. | |
| 253 tls_current_log->lwp_ = lwp; | |
| 254 | |
| 255 CHECK(tls_current_log->pthread_self_ == pthread_self()); | |
| 256 | |
| 257 // Leave the only current thread tls object because fork() clones only the | |
| 258 // current thread (the one that called fork) to the child process. | |
| 259 all_logs().clear(); | |
| 260 all_logs().push_back(tls_current_log); | |
| 261 CHECK(all_logs().size() == 1); | |
| 262 | |
| 263 // Clear log filename so it will be re-calculated with the new PIDs. | |
| 264 tls_current_log->log_filename_.clear(); | |
| 265 | |
| 266 // Create the thread that will periodically flush all logs for this process. | |
| 267 StartFlushLogThread(); | |
| 268 | |
| 269 // We do not update log header line (CygCommon data) as it will be the same | |
| 270 // because the new process is just a forked copy. | |
| 271 all_logs_mutex().Release(); | |
| 272 } | |
| 273 | |
| 274 void CygTlsLog::StartFlushLogThread() { | |
| 275 pthread_t tid; | |
| 276 CHECK(!pthread_create(&tid, NULL, &CygTlsLog::FlushLogThread, NULL)); | |
| 277 } | |
| 278 | |
| 279 void CygTlsLog::AddNewLog(CygTlsLog* newlog) { | |
| 280 CHECK(tls_current_log == kMagicBeingConstructed); | |
| 281 base::AutoLock lock(all_logs_mutex()); | |
| 282 if (all_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().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, getpid(), lwp_, pthread_self_); | |
| 311 log_filename_ = buf; | |
| 312 unlink(log_filename_.c_str()); | |
| 313 } | |
| 314 | |
| 315 int file = open(log_filename_.c_str(), O_CREAT | O_WRONLY | O_APPEND, 00600); | |
| 316 CHECK(file != -1); | |
| 317 | |
| 318 if (first_log_write) | |
| 319 WriteLogLine(file, "%s", CygCommon::GetInstance()->header().c_str()); | |
| 320 | |
| 321 for (int i=0; i != buf_.size(); ++i) { | |
| 322 const CygLogEntry& p = buf_[i]; | |
| 323 WriteLogLine(file, "%ld %ld\t%d:%ld\t%p\n", | |
| 324 p.seconds, p.usec, p.pid, p.tid, p.this_fn); | |
| 325 } | |
| 326 | |
| 327 close(file); | |
| 328 buf_.clear(); | |
| 329 } | |
| 330 | |
| 331 void* CygTlsLog::FlushLogThread(void*) { | |
| 332 // Disable logging this thread. Although this routine is not instrumented | |
| 333 // (cygprofile.gyp provides that), the called routines are and thus will | |
| 334 // call instrumentation. | |
| 335 CHECK(tls_current_log == NULL); // Must be 0 as this is a new thread. | |
| 336 tls_current_log = kMagicBeingConstructed; | |
| 337 | |
| 338 // Run this loop infinitely: sleep 30 secs and the flush all thread's | |
| 339 // buffers. There is a danger that, when quitting Chrome, this thread may | |
| 340 // see unallocated data and segfault. We do not care because we need logs | |
| 341 // when Chrome is working. | |
| 342 while (true) { | |
| 343 for(unsigned int secs_to_sleep = 30; secs_to_sleep != 0;) | |
| 344 secs_to_sleep = sleep(secs_to_sleep); | |
| 345 | |
| 346 base::AutoLock lock(all_logs_mutex()); | |
| 347 for (int i = 0; i != all_logs().size(); ++i) { | |
| 348 CygTlsLog* current_log = all_logs()[i]; | |
| 349 base::AutoLock current_lock(current_log->log_mutex_); | |
| 350 if (current_log->buf_.size()) { | |
| 351 current_log->FlushLog(); | |
| 352 } else { | |
| 353 // The thread's log is still empty. Probably the thread finished prior | |
| 354 // to previous timer fired - deallocate its buffer. Even if the thread | |
| 355 // ever resumes, it will allocate its buffer again in | |
| 356 // std::vector::push_back(). | |
| 357 current_log->buf_.clear(); | |
| 358 } | |
| 359 } | |
| 360 } | |
| 361 } | |
| 362 | |
| 363 // Gcc Compiler callback, called on every function invocation providing | |
| 364 // addresses of caller and callee codes. | |
| 365 void __cyg_profile_func_enter(void* this_fn, void* callee_unused) { | |
|
Dmitry Polukhin
2011/12/12 06:33:02
Is it called in single thread context only?
glotov
2011/12/13 19:45:43
What do you mean? Compiler just adds calls to this
dpolukhin
2011/12/14 07:10:51
Please ignore this comment. tls_current_log is thr
glotov
2012/04/05 13:50:20
Done.
| |
| 366 if (tls_current_log == NULL) { | |
| 367 tls_current_log = kMagicBeingConstructed; | |
| 368 CygTlsLog* newlog = new CygTlsLog; | |
| 369 CHECK(newlog); | |
| 370 CygTlsLog::AddNewLog(newlog); | |
| 371 tls_current_log = newlog; | |
| 372 } | |
| 373 if (tls_current_log != kMagicBeingConstructed) { | |
| 374 tls_current_log->LogEnter(this_fn); | |
| 375 } | |
| 376 } | |
| 377 | |
| 378 // Gcc Compiler callback, called after every function invocation providing | |
| 379 // addresses of caller and callee codes. | |
| 380 void __cyg_profile_func_exit(void* this_fn, void* call_site) { | |
| 381 } | |
| 382 | |
| 383 } // namespace cygprofile | |
| OLD | NEW |