Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(405)

Side by Side Diff: base/cygprofile/cygprofile.cc

Issue 8770054: Tool to log the execution of Chrome. Initial add. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | base/cygprofile/cygprofile.gyp » ('j') | chrome/nacl.gypi » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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(&timestamp, 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
OLDNEW
« no previous file with comments | « no previous file | base/cygprofile/cygprofile.gyp » ('j') | chrome/nacl.gypi » ('J')

Powered by Google App Engine
This is Rietveld 408576698