xpcom/base/VisualEventTracer.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:36f8d952f5b6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include "mozilla/VisualEventTracer.h"
6 #include "mozilla/Monitor.h"
7 #include "mozilla/TimeStamp.h"
8 #include "nscore.h"
9 #include "prthread.h"
10 #include "prprf.h"
11 #include "prenv.h"
12 #include "plstr.h"
13 #include "nsThreadUtils.h"
14
15 namespace mozilla { namespace eventtracer {
16
17 #ifdef MOZ_VISUAL_EVENT_TRACER
18
19 namespace {
20
21 const uint32_t kBatchSize = 256;
22 const char kTypeChars[eventtracer::eLast] = {' ','N','S','W','E','D'};
23
24 // Flushing thread and records queue monitor
25 mozilla::Monitor * gMonitor = nullptr;
26
27 // gInitialized and gCapture can be accessed from multiple threads
28 // simultaneously without any locking. However, since they are only ever
29 // *set* from the main thread, the chance of races manifesting is small
30 // and unlikely to be a problem in practice.
31 bool gInitialized;
32
33 // Flag to allow capturing
34 bool gCapture;
35
36 // Time stamp of the epoch we have started to capture
37 mozilla::TimeStamp * gProfilerStart;
38
39 // Duration of the log to keep up to
40 mozilla::TimeDuration * gMaxBacklogTime;
41
42
43 // Record of a single event
44 class Record {
45 public:
46 Record()
47 : mType(::mozilla::eventtracer::eNone)
48 , mItem(nullptr)
49 , mText(nullptr)
50 , mText2(nullptr)
51 {
52 MOZ_COUNT_CTOR(Record);
53 }
54
55 Record& operator=(const Record & aOther)
56 {
57 mType = aOther.mType;
58 mTime = aOther.mTime;
59 mItem = aOther.mItem;
60 mText = PL_strdup(aOther.mText);
61 mText2 = aOther.mText2 ? PL_strdup(aOther.mText2) : nullptr;
62 return *this;
63 }
64
65 ~Record()
66 {
67 PL_strfree(mText2);
68 PL_strfree(mText);
69 MOZ_COUNT_DTOR(Record);
70 }
71
72 uint32_t mType;
73 TimeStamp mTime;
74 void * mItem;
75 char * mText;
76 char * mText2;
77 };
78
79 char * DupCurrentThreadName()
80 {
81 if (NS_IsMainThread())
82 return PL_strdup("Main Thread");
83
84 PRThread * currentThread = PR_GetCurrentThread();
85 const char * name = PR_GetThreadName(currentThread);
86 if (name)
87 return PL_strdup(name);
88
89 char buffer[128];
90 PR_snprintf(buffer, 127, "Nameless %p", currentThread);
91
92 return PL_strdup(buffer);
93 }
94
95 // An array of events, each thread keeps its own private instance
96 class RecordBatch {
97 public:
98 RecordBatch(size_t aLength = kBatchSize,
99 char * aThreadName = DupCurrentThreadName())
100 : mRecordsHead(new Record[aLength])
101 , mRecordsTail(mRecordsHead + aLength)
102 , mNextRecord(mRecordsHead)
103 , mNextBatch(nullptr)
104 , mThreadNameCopy(aThreadName)
105 , mClosed(false)
106 {
107 MOZ_COUNT_CTOR(RecordBatch);
108 }
109
110 ~RecordBatch()
111 {
112 delete [] mRecordsHead;
113 PL_strfree(mThreadNameCopy);
114 MOZ_COUNT_DTOR(RecordBatch);
115 }
116
117 void Close() { mClosed = true; }
118
119 size_t Length() const { return mNextRecord - mRecordsHead; }
120 bool CanBeDeleted(const TimeStamp& aUntil) const;
121
122 static RecordBatch * Register();
123 static void Close(void * data); // Registered on freeing thread data
124 static RecordBatch * Clone(RecordBatch * aLog, const TimeStamp& aSince);
125 static void Delete(RecordBatch * aLog);
126
127 static RecordBatch * CloneLog();
128 static void GCLog(const TimeStamp& aUntil);
129 static void DeleteLog();
130
131 Record * mRecordsHead;
132 Record * mRecordsTail;
133 Record * mNextRecord;
134
135 RecordBatch * mNextBatch;
136 char * mThreadNameCopy;
137 bool mClosed;
138 };
139
140 // Protected by gMonitor, accessed concurently
141 // Linked list of batches threads want to flush on disk
142 RecordBatch * gLogHead = nullptr;
143 RecordBatch * gLogTail = nullptr;
144
145 // Registers the batch in the linked list
146 // static
147 RecordBatch *
148 RecordBatch::Register()
149 {
150 MonitorAutoLock mon(*gMonitor);
151
152 if (!gInitialized)
153 return nullptr;
154
155 if (gLogHead)
156 RecordBatch::GCLog(TimeStamp::Now() - *gMaxBacklogTime);
157
158 RecordBatch * batch = new RecordBatch();
159 if (!gLogHead)
160 gLogHead = batch;
161 else // gLogTail is non-null
162 gLogTail->mNextBatch = batch;
163 gLogTail = batch;
164
165 mon.Notify();
166 return batch;
167 }
168
169 void
170 RecordBatch::Close(void * data)
171 {
172 RecordBatch * batch = static_cast<RecordBatch*>(data);
173 batch->Close();
174 }
175
176 // static
177 RecordBatch *
178 RecordBatch::Clone(RecordBatch * aOther, const TimeStamp& aSince)
179 {
180 if (!aOther)
181 return nullptr;
182
183 size_t length = aOther->Length();
184 size_t min = 0;
185 size_t max = length;
186 Record * record = nullptr;
187
188 // Binary search for record with time >= aSince
189 size_t i;
190 while (min < max) {
191 i = (max + min) / 2;
192
193 record = aOther->mRecordsHead + i;
194 if (record->mTime >= aSince)
195 max = i;
196 else
197 min = i+1;
198 }
199 i = (max + min) / 2;
200
201 // How many Record's to copy?
202 size_t toCopy = length - i;
203 if (!toCopy)
204 return RecordBatch::Clone(aOther->mNextBatch, aSince);
205
206 // Clone
207 RecordBatch * clone = new RecordBatch(toCopy, PL_strdup(aOther->mThreadNameCopy));
208 for (; i < length; ++i) {
209 record = aOther->mRecordsHead + i;
210 *clone->mNextRecord = *record;
211 ++clone->mNextRecord;
212 }
213 clone->mNextBatch = RecordBatch::Clone(aOther->mNextBatch, aSince);
214
215 return clone;
216 }
217
218 // static
219 void
220 RecordBatch::Delete(RecordBatch * aLog)
221 {
222 while (aLog) {
223 RecordBatch * batch = aLog;
224 aLog = aLog->mNextBatch;
225 delete batch;
226 }
227 }
228
229 // static
230 RecordBatch *
231 RecordBatch::CloneLog()
232 {
233 TimeStamp startEpoch = *gProfilerStart;
234 TimeStamp backlogEpoch = TimeStamp::Now() - *gMaxBacklogTime;
235
236 TimeStamp since = (startEpoch > backlogEpoch) ? startEpoch : backlogEpoch;
237
238 MonitorAutoLock mon(*gMonitor);
239
240 return RecordBatch::Clone(gLogHead, since);
241 }
242
243 // static
244 void
245 RecordBatch::GCLog(const TimeStamp& aUntil)
246 {
247 // Garbage collect all unreferenced and old batches
248 gMonitor->AssertCurrentThreadOwns();
249
250 RecordBatch *volatile * referer = &gLogHead;
251 gLogTail = nullptr;
252
253 RecordBatch * batch = *referer;
254 while (batch) {
255 if (batch->CanBeDeleted(aUntil)) {
256 // The batch is completed and thus unreferenced by the thread
257 // and the most recent record has time older then the time
258 // we want to save records for, hence delete it.
259 *referer = batch->mNextBatch;
260 delete batch;
261 batch = *referer;
262 }
263 else {
264 // We walk the whole list, so this will end up filled with
265 // the very last valid element of it.
266 gLogTail = batch;
267 // The current batch is active, examine the next in the list.
268 batch = batch->mNextBatch;
269 // When the next batch is found expired, we must extract it
270 // from the list, shift the referer.
271 referer = &((*referer)->mNextBatch);
272 }
273 }
274 }
275
276 // static
277 void
278 RecordBatch::DeleteLog()
279 {
280 RecordBatch * batch;
281 {
282 MonitorAutoLock mon(*gMonitor);
283 batch = gLogHead;
284 gLogHead = nullptr;
285 gLogTail = nullptr;
286 }
287
288 RecordBatch::Delete(batch);
289 }
290
291 bool
292 RecordBatch::CanBeDeleted(const TimeStamp& aUntil) const
293 {
294 if (mClosed) {
295 // This flag is set when a thread releases this batch as
296 // its private data. It happens when the list is full or
297 // when the thread ends its job. We must not delete this
298 // batch from memory while it's held by a thread.
299
300 if (!Length()) {
301 // There are no records, just get rid of this empty batch.
302 return true;
303 }
304
305 if ((mNextRecord-1)->mTime <= aUntil) {
306 // Is the last record older then the time we demand records
307 // for? If not, this batch has expired.
308 return true;
309 }
310 }
311
312 // Not all conditions to close the batch met, keep it.
313 return false;
314 }
315
316 // Helper class for filtering events by MOZ_PROFILING_EVENTS
317 class EventFilter
318 {
319 public:
320 static EventFilter * Build(const char * filterVar);
321 bool EventPasses(const char * eventName);
322
323 ~EventFilter()
324 {
325 delete mNext;
326 PL_strfree(mFilter);
327 MOZ_COUNT_DTOR(EventFilter);
328 }
329
330 private:
331 EventFilter(const char * eventName, EventFilter * next)
332 : mFilter(PL_strdup(eventName))
333 , mNext(next)
334 {
335 MOZ_COUNT_CTOR(EventFilter);
336 }
337
338 char * mFilter;
339 EventFilter * mNext;
340 };
341
342 // static
343 EventFilter *
344 EventFilter::Build(const char * filterVar)
345 {
346 if (!filterVar || !*filterVar)
347 return nullptr;
348
349 // Reads a comma serpatated list of events.
350
351 // Copied from nspr logging code (read of NSPR_LOG_MODULES)
352 char eventName[64];
353 int pos = 0, count, delta = 0;
354
355 // Read up to a comma or EOF -> get name of an event first in the list
356 count = sscanf(filterVar, "%63[^,]%n", eventName, &delta);
357 if (count == 0)
358 return nullptr;
359
360 pos = delta;
361
362 // Skip a comma, if present, accept spaces around it
363 count = sscanf(filterVar + pos, " , %n", &delta);
364 if (count != EOF)
365 pos += delta;
366
367 // eventName contains name of the first event in the list
368 // second argument recursively parses the rest of the list string and
369 // fills mNext of the just created EventFilter object chaining the objects
370 return new EventFilter(eventName, Build(filterVar + pos));
371 }
372
373 bool
374 EventFilter::EventPasses(const char * eventName)
375 {
376 if (!strcmp(eventName, mFilter))
377 return true;
378
379 if (mNext)
380 return mNext->EventPasses(eventName);
381
382 return false;
383 }
384
385 // State and control variables, initialized in Init() method, after it
386 // immutable and read concurently.
387 EventFilter * gEventFilter = nullptr;
388 unsigned gThreadPrivateIndex;
389
390 // static
391 bool CheckEventFilters(uint32_t aType, void * aItem, const char * aText)
392 {
393 if (!gEventFilter)
394 return true;
395
396 if (aType == eName)
397 return true;
398
399 return gEventFilter->EventPasses(aText);
400 }
401
402 } // anon namespace
403
404 #endif //MOZ_VISUAL_EVENT_TRACER
405
406 // static
407 void Init()
408 {
409 #ifdef MOZ_VISUAL_EVENT_TRACER
410 const char * logEvents = PR_GetEnv("MOZ_PROFILING_EVENTS");
411 if (logEvents && *logEvents)
412 gEventFilter = EventFilter::Build(logEvents);
413
414 PRStatus status = PR_NewThreadPrivateIndex(&gThreadPrivateIndex, &RecordBatch::Close);
415 if (status != PR_SUCCESS)
416 return;
417
418 gMonitor = new mozilla::Monitor("Profiler");
419 if (!gMonitor)
420 return;
421
422 gProfilerStart = new mozilla::TimeStamp();
423 gMaxBacklogTime = new mozilla::TimeDuration();
424
425 gInitialized = true;
426 #endif
427 }
428
429 // static
430 void Shutdown()
431 {
432 #ifdef MOZ_VISUAL_EVENT_TRACER
433 gCapture = false;
434 gInitialized = false;
435
436 RecordBatch::DeleteLog();
437
438 if (gMonitor) {
439 delete gMonitor;
440 gMonitor = nullptr;
441 }
442
443 if (gEventFilter) {
444 delete gEventFilter;
445 gEventFilter = nullptr;
446 }
447
448 if (gProfilerStart) {
449 delete gProfilerStart;
450 gProfilerStart = nullptr;
451 }
452
453 if (gMaxBacklogTime) {
454 delete gMaxBacklogTime;
455 gMaxBacklogTime = nullptr;
456 }
457 #endif
458 }
459
460 // static
461 void Mark(uint32_t aType, void * aItem, const char * aText, const char * aText2)
462 {
463 #ifdef MOZ_VISUAL_EVENT_TRACER
464 if (!gInitialized || !gCapture)
465 return;
466
467 if (aType == eNone)
468 return;
469
470 if (!CheckEventFilters(aType, aItem, aText)) // Events use just aText
471 return;
472
473 RecordBatch * threadLogPrivate = static_cast<RecordBatch *>(
474 PR_GetThreadPrivate(gThreadPrivateIndex));
475 if (!threadLogPrivate) {
476 threadLogPrivate = RecordBatch::Register();
477 if (!threadLogPrivate)
478 return;
479
480 PR_SetThreadPrivate(gThreadPrivateIndex, threadLogPrivate);
481 }
482
483 Record * record = threadLogPrivate->mNextRecord;
484 record->mType = aType;
485 record->mTime = mozilla::TimeStamp::Now();
486 record->mItem = aItem;
487 record->mText = PL_strdup(aText);
488 record->mText2 = aText2 ? PL_strdup(aText2) : nullptr;
489
490 ++threadLogPrivate->mNextRecord;
491 if (threadLogPrivate->mNextRecord == threadLogPrivate->mRecordsTail) {
492 // Calls RecordBatch::Close(threadLogPrivate) that marks
493 // the batch as OK to be deleted from memory when no longer needed.
494 PR_SetThreadPrivate(gThreadPrivateIndex, nullptr);
495 }
496 #endif
497 }
498
499
500 #ifdef MOZ_VISUAL_EVENT_TRACER
501
502 // The scriptable classes
503
504 class VisualEventTracerLog : public nsIVisualEventTracerLog
505 {
506 NS_DECL_ISUPPORTS
507 NS_DECL_NSIVISUALEVENTTRACERLOG
508
509 VisualEventTracerLog(RecordBatch* aBatch)
510 : mBatch(aBatch)
511 , mProfilerStart(*gProfilerStart)
512 {}
513
514 virtual ~VisualEventTracerLog();
515
516 protected:
517 RecordBatch * mBatch;
518 TimeStamp mProfilerStart;
519 };
520
521 NS_IMPL_ISUPPORTS(VisualEventTracerLog, nsIVisualEventTracerLog)
522
523 VisualEventTracerLog::~VisualEventTracerLog()
524 {
525 RecordBatch::Delete(mBatch);
526 }
527
528 NS_IMETHODIMP
529 VisualEventTracerLog::GetJSONString(nsACString & _retval)
530 {
531 nsCString buffer;
532
533 buffer.Assign(NS_LITERAL_CSTRING("{\n\"version\": 1,\n\"records\":[\n"));
534
535 RecordBatch * batch = mBatch;
536 while (batch) {
537 if (batch != mBatch) {
538 // This is not the first batch we are writting, add comma
539 buffer.Append(NS_LITERAL_CSTRING(",\n"));
540 }
541
542 buffer.Append(NS_LITERAL_CSTRING("{\"thread\":\""));
543 buffer.Append(batch->mThreadNameCopy);
544 buffer.Append(NS_LITERAL_CSTRING("\",\"log\":[\n"));
545
546 static const int kBufferSize = 2048;
547 char buf[kBufferSize];
548
549 for (Record * record = batch->mRecordsHead;
550 record < batch->mNextRecord;
551 ++record) {
552
553 // mType carries both type and flags, separate type
554 // as lower 16 bits and flags as higher 16 bits.
555 // The json format expects this separated.
556 uint32_t type = record->mType & 0xffffUL;
557 uint32_t flags = record->mType >> 16;
558 PR_snprintf(buf, kBufferSize,
559 "{\"e\":\"%c\",\"t\":%llu,\"f\":%d,\"i\":\"%p\",\"n\":\"%s%s\"}%s\n",
560 kTypeChars[type],
561 static_cast<uint64_t>((record->mTime - mProfilerStart).ToMilliseconds()),
562 flags,
563 record->mItem,
564 record->mText,
565 record->mText2 ? record->mText2 : "",
566 (record == batch->mNextRecord - 1) ? "" : ",");
567
568 buffer.Append(buf);
569 }
570
571 buffer.Append(NS_LITERAL_CSTRING("]}\n"));
572
573 RecordBatch * next = batch->mNextBatch;
574 batch = next;
575 }
576
577 buffer.Append(NS_LITERAL_CSTRING("]}\n"));
578 _retval.Assign(buffer);
579
580 return NS_OK;
581 }
582
583 nsresult
584 VisualEventTracerLog::WriteToProfilingFile()
585 {
586 const char* filename = PR_GetEnv("MOZ_TRACE_FILE");
587 if (!filename) {
588 return NS_OK;
589 }
590
591 PRFileDesc* fd = PR_Open(filename, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
592 0644);
593 if (!fd) {
594 return NS_ERROR_FILE_ACCESS_DENIED;
595 }
596
597 nsCString json;
598 GetJSONString(json);
599
600 int32_t bytesWritten = PR_Write(fd, json.get(), json.Length());
601 PR_Close(fd);
602
603 if (bytesWritten < json.Length()) {
604 return NS_ERROR_UNEXPECTED;
605 }
606
607 return NS_OK;
608 }
609
610 NS_IMPL_ISUPPORTS(VisualEventTracer, nsIVisualEventTracer)
611
612 NS_IMETHODIMP
613 VisualEventTracer::Start(const uint32_t aMaxBacklogSeconds)
614 {
615 if (!gInitialized)
616 return NS_ERROR_UNEXPECTED;
617
618 if (gCapture) {
619 NS_WARNING("VisualEventTracer has already been started");
620 return NS_ERROR_ALREADY_INITIALIZED;
621 }
622
623 *gMaxBacklogTime = TimeDuration::FromMilliseconds(aMaxBacklogSeconds * 1000);
624
625 *gProfilerStart = mozilla::TimeStamp::Now();
626 {
627 MonitorAutoLock mon(*gMonitor);
628 RecordBatch::GCLog(*gProfilerStart);
629 }
630 gCapture = true;
631
632 MOZ_EVENT_TRACER_MARK(this, "trace::start");
633
634 return NS_OK;
635 }
636
637 NS_IMETHODIMP
638 VisualEventTracer::Stop()
639 {
640 if (!gInitialized)
641 return NS_ERROR_UNEXPECTED;
642
643 if (!gCapture) {
644 NS_WARNING("VisualEventTracer is not runing");
645 return NS_ERROR_NOT_INITIALIZED;
646 }
647
648 MOZ_EVENT_TRACER_MARK(this, "trace::stop");
649
650 gCapture = false;
651
652 nsresult rv = NS_OK;
653 if (PR_GetEnv("MOZ_TRACE_FILE")) {
654 nsCOMPtr<nsIVisualEventTracerLog> tracelog;
655 rv = Snapshot(getter_AddRefs(tracelog));
656 if (NS_SUCCEEDED(rv)) {
657 rv = tracelog->WriteToProfilingFile();
658 }
659 }
660
661 return rv;
662 }
663
664 NS_IMETHODIMP
665 VisualEventTracer::Snapshot(nsIVisualEventTracerLog ** _result)
666 {
667 if (!gInitialized)
668 return NS_ERROR_UNEXPECTED;
669
670 RecordBatch * batch = RecordBatch::CloneLog();
671
672 nsRefPtr<VisualEventTracerLog> log = new VisualEventTracerLog(batch);
673 log.forget(_result);
674
675 return NS_OK;
676 }
677
678 #endif
679
680 } // eventtracer
681 } // mozilla

mercurial