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 |