1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/xpcom/base/VisualEventTracer.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,681 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "mozilla/VisualEventTracer.h" 1.9 +#include "mozilla/Monitor.h" 1.10 +#include "mozilla/TimeStamp.h" 1.11 +#include "nscore.h" 1.12 +#include "prthread.h" 1.13 +#include "prprf.h" 1.14 +#include "prenv.h" 1.15 +#include "plstr.h" 1.16 +#include "nsThreadUtils.h" 1.17 + 1.18 +namespace mozilla { namespace eventtracer { 1.19 + 1.20 +#ifdef MOZ_VISUAL_EVENT_TRACER 1.21 + 1.22 +namespace { 1.23 + 1.24 +const uint32_t kBatchSize = 256; 1.25 +const char kTypeChars[eventtracer::eLast] = {' ','N','S','W','E','D'}; 1.26 + 1.27 +// Flushing thread and records queue monitor 1.28 +mozilla::Monitor * gMonitor = nullptr; 1.29 + 1.30 +// gInitialized and gCapture can be accessed from multiple threads 1.31 +// simultaneously without any locking. However, since they are only ever 1.32 +// *set* from the main thread, the chance of races manifesting is small 1.33 +// and unlikely to be a problem in practice. 1.34 +bool gInitialized; 1.35 + 1.36 +// Flag to allow capturing 1.37 +bool gCapture; 1.38 + 1.39 +// Time stamp of the epoch we have started to capture 1.40 +mozilla::TimeStamp * gProfilerStart; 1.41 + 1.42 +// Duration of the log to keep up to 1.43 +mozilla::TimeDuration * gMaxBacklogTime; 1.44 + 1.45 + 1.46 +// Record of a single event 1.47 +class Record { 1.48 +public: 1.49 + Record() 1.50 + : mType(::mozilla::eventtracer::eNone) 1.51 + , mItem(nullptr) 1.52 + , mText(nullptr) 1.53 + , mText2(nullptr) 1.54 + { 1.55 + MOZ_COUNT_CTOR(Record); 1.56 + } 1.57 + 1.58 + Record& operator=(const Record & aOther) 1.59 + { 1.60 + mType = aOther.mType; 1.61 + mTime = aOther.mTime; 1.62 + mItem = aOther.mItem; 1.63 + mText = PL_strdup(aOther.mText); 1.64 + mText2 = aOther.mText2 ? PL_strdup(aOther.mText2) : nullptr; 1.65 + return *this; 1.66 + } 1.67 + 1.68 + ~Record() 1.69 + { 1.70 + PL_strfree(mText2); 1.71 + PL_strfree(mText); 1.72 + MOZ_COUNT_DTOR(Record); 1.73 + } 1.74 + 1.75 + uint32_t mType; 1.76 + TimeStamp mTime; 1.77 + void * mItem; 1.78 + char * mText; 1.79 + char * mText2; 1.80 +}; 1.81 + 1.82 +char * DupCurrentThreadName() 1.83 +{ 1.84 + if (NS_IsMainThread()) 1.85 + return PL_strdup("Main Thread"); 1.86 + 1.87 + PRThread * currentThread = PR_GetCurrentThread(); 1.88 + const char * name = PR_GetThreadName(currentThread); 1.89 + if (name) 1.90 + return PL_strdup(name); 1.91 + 1.92 + char buffer[128]; 1.93 + PR_snprintf(buffer, 127, "Nameless %p", currentThread); 1.94 + 1.95 + return PL_strdup(buffer); 1.96 +} 1.97 + 1.98 +// An array of events, each thread keeps its own private instance 1.99 +class RecordBatch { 1.100 +public: 1.101 + RecordBatch(size_t aLength = kBatchSize, 1.102 + char * aThreadName = DupCurrentThreadName()) 1.103 + : mRecordsHead(new Record[aLength]) 1.104 + , mRecordsTail(mRecordsHead + aLength) 1.105 + , mNextRecord(mRecordsHead) 1.106 + , mNextBatch(nullptr) 1.107 + , mThreadNameCopy(aThreadName) 1.108 + , mClosed(false) 1.109 + { 1.110 + MOZ_COUNT_CTOR(RecordBatch); 1.111 + } 1.112 + 1.113 + ~RecordBatch() 1.114 + { 1.115 + delete [] mRecordsHead; 1.116 + PL_strfree(mThreadNameCopy); 1.117 + MOZ_COUNT_DTOR(RecordBatch); 1.118 + } 1.119 + 1.120 + void Close() { mClosed = true; } 1.121 + 1.122 + size_t Length() const { return mNextRecord - mRecordsHead; } 1.123 + bool CanBeDeleted(const TimeStamp& aUntil) const; 1.124 + 1.125 + static RecordBatch * Register(); 1.126 + static void Close(void * data); // Registered on freeing thread data 1.127 + static RecordBatch * Clone(RecordBatch * aLog, const TimeStamp& aSince); 1.128 + static void Delete(RecordBatch * aLog); 1.129 + 1.130 + static RecordBatch * CloneLog(); 1.131 + static void GCLog(const TimeStamp& aUntil); 1.132 + static void DeleteLog(); 1.133 + 1.134 + Record * mRecordsHead; 1.135 + Record * mRecordsTail; 1.136 + Record * mNextRecord; 1.137 + 1.138 + RecordBatch * mNextBatch; 1.139 + char * mThreadNameCopy; 1.140 + bool mClosed; 1.141 +}; 1.142 + 1.143 +// Protected by gMonitor, accessed concurently 1.144 +// Linked list of batches threads want to flush on disk 1.145 +RecordBatch * gLogHead = nullptr; 1.146 +RecordBatch * gLogTail = nullptr; 1.147 + 1.148 +// Registers the batch in the linked list 1.149 +// static 1.150 +RecordBatch * 1.151 +RecordBatch::Register() 1.152 +{ 1.153 + MonitorAutoLock mon(*gMonitor); 1.154 + 1.155 + if (!gInitialized) 1.156 + return nullptr; 1.157 + 1.158 + if (gLogHead) 1.159 + RecordBatch::GCLog(TimeStamp::Now() - *gMaxBacklogTime); 1.160 + 1.161 + RecordBatch * batch = new RecordBatch(); 1.162 + if (!gLogHead) 1.163 + gLogHead = batch; 1.164 + else // gLogTail is non-null 1.165 + gLogTail->mNextBatch = batch; 1.166 + gLogTail = batch; 1.167 + 1.168 + mon.Notify(); 1.169 + return batch; 1.170 +} 1.171 + 1.172 +void 1.173 +RecordBatch::Close(void * data) 1.174 +{ 1.175 + RecordBatch * batch = static_cast<RecordBatch*>(data); 1.176 + batch->Close(); 1.177 +} 1.178 + 1.179 +// static 1.180 +RecordBatch * 1.181 +RecordBatch::Clone(RecordBatch * aOther, const TimeStamp& aSince) 1.182 +{ 1.183 + if (!aOther) 1.184 + return nullptr; 1.185 + 1.186 + size_t length = aOther->Length(); 1.187 + size_t min = 0; 1.188 + size_t max = length; 1.189 + Record * record = nullptr; 1.190 + 1.191 + // Binary search for record with time >= aSince 1.192 + size_t i; 1.193 + while (min < max) { 1.194 + i = (max + min) / 2; 1.195 + 1.196 + record = aOther->mRecordsHead + i; 1.197 + if (record->mTime >= aSince) 1.198 + max = i; 1.199 + else 1.200 + min = i+1; 1.201 + } 1.202 + i = (max + min) / 2; 1.203 + 1.204 + // How many Record's to copy? 1.205 + size_t toCopy = length - i; 1.206 + if (!toCopy) 1.207 + return RecordBatch::Clone(aOther->mNextBatch, aSince); 1.208 + 1.209 + // Clone 1.210 + RecordBatch * clone = new RecordBatch(toCopy, PL_strdup(aOther->mThreadNameCopy)); 1.211 + for (; i < length; ++i) { 1.212 + record = aOther->mRecordsHead + i; 1.213 + *clone->mNextRecord = *record; 1.214 + ++clone->mNextRecord; 1.215 + } 1.216 + clone->mNextBatch = RecordBatch::Clone(aOther->mNextBatch, aSince); 1.217 + 1.218 + return clone; 1.219 +} 1.220 + 1.221 +// static 1.222 +void 1.223 +RecordBatch::Delete(RecordBatch * aLog) 1.224 +{ 1.225 + while (aLog) { 1.226 + RecordBatch * batch = aLog; 1.227 + aLog = aLog->mNextBatch; 1.228 + delete batch; 1.229 + } 1.230 +} 1.231 + 1.232 +// static 1.233 +RecordBatch * 1.234 +RecordBatch::CloneLog() 1.235 +{ 1.236 + TimeStamp startEpoch = *gProfilerStart; 1.237 + TimeStamp backlogEpoch = TimeStamp::Now() - *gMaxBacklogTime; 1.238 + 1.239 + TimeStamp since = (startEpoch > backlogEpoch) ? startEpoch : backlogEpoch; 1.240 + 1.241 + MonitorAutoLock mon(*gMonitor); 1.242 + 1.243 + return RecordBatch::Clone(gLogHead, since); 1.244 +} 1.245 + 1.246 +// static 1.247 +void 1.248 +RecordBatch::GCLog(const TimeStamp& aUntil) 1.249 +{ 1.250 + // Garbage collect all unreferenced and old batches 1.251 + gMonitor->AssertCurrentThreadOwns(); 1.252 + 1.253 + RecordBatch *volatile * referer = &gLogHead; 1.254 + gLogTail = nullptr; 1.255 + 1.256 + RecordBatch * batch = *referer; 1.257 + while (batch) { 1.258 + if (batch->CanBeDeleted(aUntil)) { 1.259 + // The batch is completed and thus unreferenced by the thread 1.260 + // and the most recent record has time older then the time 1.261 + // we want to save records for, hence delete it. 1.262 + *referer = batch->mNextBatch; 1.263 + delete batch; 1.264 + batch = *referer; 1.265 + } 1.266 + else { 1.267 + // We walk the whole list, so this will end up filled with 1.268 + // the very last valid element of it. 1.269 + gLogTail = batch; 1.270 + // The current batch is active, examine the next in the list. 1.271 + batch = batch->mNextBatch; 1.272 + // When the next batch is found expired, we must extract it 1.273 + // from the list, shift the referer. 1.274 + referer = &((*referer)->mNextBatch); 1.275 + } 1.276 + } 1.277 +} 1.278 + 1.279 +// static 1.280 +void 1.281 +RecordBatch::DeleteLog() 1.282 +{ 1.283 + RecordBatch * batch; 1.284 + { 1.285 + MonitorAutoLock mon(*gMonitor); 1.286 + batch = gLogHead; 1.287 + gLogHead = nullptr; 1.288 + gLogTail = nullptr; 1.289 + } 1.290 + 1.291 + RecordBatch::Delete(batch); 1.292 +} 1.293 + 1.294 +bool 1.295 +RecordBatch::CanBeDeleted(const TimeStamp& aUntil) const 1.296 +{ 1.297 + if (mClosed) { 1.298 + // This flag is set when a thread releases this batch as 1.299 + // its private data. It happens when the list is full or 1.300 + // when the thread ends its job. We must not delete this 1.301 + // batch from memory while it's held by a thread. 1.302 + 1.303 + if (!Length()) { 1.304 + // There are no records, just get rid of this empty batch. 1.305 + return true; 1.306 + } 1.307 + 1.308 + if ((mNextRecord-1)->mTime <= aUntil) { 1.309 + // Is the last record older then the time we demand records 1.310 + // for? If not, this batch has expired. 1.311 + return true; 1.312 + } 1.313 + } 1.314 + 1.315 + // Not all conditions to close the batch met, keep it. 1.316 + return false; 1.317 +} 1.318 + 1.319 +// Helper class for filtering events by MOZ_PROFILING_EVENTS 1.320 +class EventFilter 1.321 +{ 1.322 +public: 1.323 + static EventFilter * Build(const char * filterVar); 1.324 + bool EventPasses(const char * eventName); 1.325 + 1.326 + ~EventFilter() 1.327 + { 1.328 + delete mNext; 1.329 + PL_strfree(mFilter); 1.330 + MOZ_COUNT_DTOR(EventFilter); 1.331 + } 1.332 + 1.333 +private: 1.334 + EventFilter(const char * eventName, EventFilter * next) 1.335 + : mFilter(PL_strdup(eventName)) 1.336 + , mNext(next) 1.337 + { 1.338 + MOZ_COUNT_CTOR(EventFilter); 1.339 + } 1.340 + 1.341 + char * mFilter; 1.342 + EventFilter * mNext; 1.343 +}; 1.344 + 1.345 +// static 1.346 +EventFilter * 1.347 +EventFilter::Build(const char * filterVar) 1.348 +{ 1.349 + if (!filterVar || !*filterVar) 1.350 + return nullptr; 1.351 + 1.352 + // Reads a comma serpatated list of events. 1.353 + 1.354 + // Copied from nspr logging code (read of NSPR_LOG_MODULES) 1.355 + char eventName[64]; 1.356 + int pos = 0, count, delta = 0; 1.357 + 1.358 + // Read up to a comma or EOF -> get name of an event first in the list 1.359 + count = sscanf(filterVar, "%63[^,]%n", eventName, &delta); 1.360 + if (count == 0) 1.361 + return nullptr; 1.362 + 1.363 + pos = delta; 1.364 + 1.365 + // Skip a comma, if present, accept spaces around it 1.366 + count = sscanf(filterVar + pos, " , %n", &delta); 1.367 + if (count != EOF) 1.368 + pos += delta; 1.369 + 1.370 + // eventName contains name of the first event in the list 1.371 + // second argument recursively parses the rest of the list string and 1.372 + // fills mNext of the just created EventFilter object chaining the objects 1.373 + return new EventFilter(eventName, Build(filterVar + pos)); 1.374 +} 1.375 + 1.376 +bool 1.377 +EventFilter::EventPasses(const char * eventName) 1.378 +{ 1.379 + if (!strcmp(eventName, mFilter)) 1.380 + return true; 1.381 + 1.382 + if (mNext) 1.383 + return mNext->EventPasses(eventName); 1.384 + 1.385 + return false; 1.386 +} 1.387 + 1.388 +// State and control variables, initialized in Init() method, after it 1.389 +// immutable and read concurently. 1.390 +EventFilter * gEventFilter = nullptr; 1.391 +unsigned gThreadPrivateIndex; 1.392 + 1.393 +// static 1.394 +bool CheckEventFilters(uint32_t aType, void * aItem, const char * aText) 1.395 +{ 1.396 + if (!gEventFilter) 1.397 + return true; 1.398 + 1.399 + if (aType == eName) 1.400 + return true; 1.401 + 1.402 + return gEventFilter->EventPasses(aText); 1.403 +} 1.404 + 1.405 +} // anon namespace 1.406 + 1.407 +#endif //MOZ_VISUAL_EVENT_TRACER 1.408 + 1.409 +// static 1.410 +void Init() 1.411 +{ 1.412 +#ifdef MOZ_VISUAL_EVENT_TRACER 1.413 + const char * logEvents = PR_GetEnv("MOZ_PROFILING_EVENTS"); 1.414 + if (logEvents && *logEvents) 1.415 + gEventFilter = EventFilter::Build(logEvents); 1.416 + 1.417 + PRStatus status = PR_NewThreadPrivateIndex(&gThreadPrivateIndex, &RecordBatch::Close); 1.418 + if (status != PR_SUCCESS) 1.419 + return; 1.420 + 1.421 + gMonitor = new mozilla::Monitor("Profiler"); 1.422 + if (!gMonitor) 1.423 + return; 1.424 + 1.425 + gProfilerStart = new mozilla::TimeStamp(); 1.426 + gMaxBacklogTime = new mozilla::TimeDuration(); 1.427 + 1.428 + gInitialized = true; 1.429 +#endif 1.430 +} 1.431 + 1.432 +// static 1.433 +void Shutdown() 1.434 +{ 1.435 +#ifdef MOZ_VISUAL_EVENT_TRACER 1.436 + gCapture = false; 1.437 + gInitialized = false; 1.438 + 1.439 + RecordBatch::DeleteLog(); 1.440 + 1.441 + if (gMonitor) { 1.442 + delete gMonitor; 1.443 + gMonitor = nullptr; 1.444 + } 1.445 + 1.446 + if (gEventFilter) { 1.447 + delete gEventFilter; 1.448 + gEventFilter = nullptr; 1.449 + } 1.450 + 1.451 + if (gProfilerStart) { 1.452 + delete gProfilerStart; 1.453 + gProfilerStart = nullptr; 1.454 + } 1.455 + 1.456 + if (gMaxBacklogTime) { 1.457 + delete gMaxBacklogTime; 1.458 + gMaxBacklogTime = nullptr; 1.459 + } 1.460 +#endif 1.461 +} 1.462 + 1.463 +// static 1.464 +void Mark(uint32_t aType, void * aItem, const char * aText, const char * aText2) 1.465 +{ 1.466 +#ifdef MOZ_VISUAL_EVENT_TRACER 1.467 + if (!gInitialized || !gCapture) 1.468 + return; 1.469 + 1.470 + if (aType == eNone) 1.471 + return; 1.472 + 1.473 + if (!CheckEventFilters(aType, aItem, aText)) // Events use just aText 1.474 + return; 1.475 + 1.476 + RecordBatch * threadLogPrivate = static_cast<RecordBatch *>( 1.477 + PR_GetThreadPrivate(gThreadPrivateIndex)); 1.478 + if (!threadLogPrivate) { 1.479 + threadLogPrivate = RecordBatch::Register(); 1.480 + if (!threadLogPrivate) 1.481 + return; 1.482 + 1.483 + PR_SetThreadPrivate(gThreadPrivateIndex, threadLogPrivate); 1.484 + } 1.485 + 1.486 + Record * record = threadLogPrivate->mNextRecord; 1.487 + record->mType = aType; 1.488 + record->mTime = mozilla::TimeStamp::Now(); 1.489 + record->mItem = aItem; 1.490 + record->mText = PL_strdup(aText); 1.491 + record->mText2 = aText2 ? PL_strdup(aText2) : nullptr; 1.492 + 1.493 + ++threadLogPrivate->mNextRecord; 1.494 + if (threadLogPrivate->mNextRecord == threadLogPrivate->mRecordsTail) { 1.495 + // Calls RecordBatch::Close(threadLogPrivate) that marks 1.496 + // the batch as OK to be deleted from memory when no longer needed. 1.497 + PR_SetThreadPrivate(gThreadPrivateIndex, nullptr); 1.498 + } 1.499 +#endif 1.500 +} 1.501 + 1.502 + 1.503 +#ifdef MOZ_VISUAL_EVENT_TRACER 1.504 + 1.505 +// The scriptable classes 1.506 + 1.507 +class VisualEventTracerLog : public nsIVisualEventTracerLog 1.508 +{ 1.509 + NS_DECL_ISUPPORTS 1.510 + NS_DECL_NSIVISUALEVENTTRACERLOG 1.511 + 1.512 + VisualEventTracerLog(RecordBatch* aBatch) 1.513 + : mBatch(aBatch) 1.514 + , mProfilerStart(*gProfilerStart) 1.515 + {} 1.516 + 1.517 + virtual ~VisualEventTracerLog(); 1.518 + 1.519 +protected: 1.520 + RecordBatch * mBatch; 1.521 + TimeStamp mProfilerStart; 1.522 +}; 1.523 + 1.524 +NS_IMPL_ISUPPORTS(VisualEventTracerLog, nsIVisualEventTracerLog) 1.525 + 1.526 +VisualEventTracerLog::~VisualEventTracerLog() 1.527 +{ 1.528 + RecordBatch::Delete(mBatch); 1.529 +} 1.530 + 1.531 +NS_IMETHODIMP 1.532 +VisualEventTracerLog::GetJSONString(nsACString & _retval) 1.533 +{ 1.534 + nsCString buffer; 1.535 + 1.536 + buffer.Assign(NS_LITERAL_CSTRING("{\n\"version\": 1,\n\"records\":[\n")); 1.537 + 1.538 + RecordBatch * batch = mBatch; 1.539 + while (batch) { 1.540 + if (batch != mBatch) { 1.541 + // This is not the first batch we are writting, add comma 1.542 + buffer.Append(NS_LITERAL_CSTRING(",\n")); 1.543 + } 1.544 + 1.545 + buffer.Append(NS_LITERAL_CSTRING("{\"thread\":\"")); 1.546 + buffer.Append(batch->mThreadNameCopy); 1.547 + buffer.Append(NS_LITERAL_CSTRING("\",\"log\":[\n")); 1.548 + 1.549 + static const int kBufferSize = 2048; 1.550 + char buf[kBufferSize]; 1.551 + 1.552 + for (Record * record = batch->mRecordsHead; 1.553 + record < batch->mNextRecord; 1.554 + ++record) { 1.555 + 1.556 + // mType carries both type and flags, separate type 1.557 + // as lower 16 bits and flags as higher 16 bits. 1.558 + // The json format expects this separated. 1.559 + uint32_t type = record->mType & 0xffffUL; 1.560 + uint32_t flags = record->mType >> 16; 1.561 + PR_snprintf(buf, kBufferSize, 1.562 + "{\"e\":\"%c\",\"t\":%llu,\"f\":%d,\"i\":\"%p\",\"n\":\"%s%s\"}%s\n", 1.563 + kTypeChars[type], 1.564 + static_cast<uint64_t>((record->mTime - mProfilerStart).ToMilliseconds()), 1.565 + flags, 1.566 + record->mItem, 1.567 + record->mText, 1.568 + record->mText2 ? record->mText2 : "", 1.569 + (record == batch->mNextRecord - 1) ? "" : ","); 1.570 + 1.571 + buffer.Append(buf); 1.572 + } 1.573 + 1.574 + buffer.Append(NS_LITERAL_CSTRING("]}\n")); 1.575 + 1.576 + RecordBatch * next = batch->mNextBatch; 1.577 + batch = next; 1.578 + } 1.579 + 1.580 + buffer.Append(NS_LITERAL_CSTRING("]}\n")); 1.581 + _retval.Assign(buffer); 1.582 + 1.583 + return NS_OK; 1.584 +} 1.585 + 1.586 +nsresult 1.587 +VisualEventTracerLog::WriteToProfilingFile() 1.588 +{ 1.589 + const char* filename = PR_GetEnv("MOZ_TRACE_FILE"); 1.590 + if (!filename) { 1.591 + return NS_OK; 1.592 + } 1.593 + 1.594 + PRFileDesc* fd = PR_Open(filename, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 1.595 + 0644); 1.596 + if (!fd) { 1.597 + return NS_ERROR_FILE_ACCESS_DENIED; 1.598 + } 1.599 + 1.600 + nsCString json; 1.601 + GetJSONString(json); 1.602 + 1.603 + int32_t bytesWritten = PR_Write(fd, json.get(), json.Length()); 1.604 + PR_Close(fd); 1.605 + 1.606 + if (bytesWritten < json.Length()) { 1.607 + return NS_ERROR_UNEXPECTED; 1.608 + } 1.609 + 1.610 + return NS_OK; 1.611 +} 1.612 + 1.613 +NS_IMPL_ISUPPORTS(VisualEventTracer, nsIVisualEventTracer) 1.614 + 1.615 +NS_IMETHODIMP 1.616 +VisualEventTracer::Start(const uint32_t aMaxBacklogSeconds) 1.617 +{ 1.618 + if (!gInitialized) 1.619 + return NS_ERROR_UNEXPECTED; 1.620 + 1.621 + if (gCapture) { 1.622 + NS_WARNING("VisualEventTracer has already been started"); 1.623 + return NS_ERROR_ALREADY_INITIALIZED; 1.624 + } 1.625 + 1.626 + *gMaxBacklogTime = TimeDuration::FromMilliseconds(aMaxBacklogSeconds * 1000); 1.627 + 1.628 + *gProfilerStart = mozilla::TimeStamp::Now(); 1.629 + { 1.630 + MonitorAutoLock mon(*gMonitor); 1.631 + RecordBatch::GCLog(*gProfilerStart); 1.632 + } 1.633 + gCapture = true; 1.634 + 1.635 + MOZ_EVENT_TRACER_MARK(this, "trace::start"); 1.636 + 1.637 + return NS_OK; 1.638 +} 1.639 + 1.640 +NS_IMETHODIMP 1.641 +VisualEventTracer::Stop() 1.642 +{ 1.643 + if (!gInitialized) 1.644 + return NS_ERROR_UNEXPECTED; 1.645 + 1.646 + if (!gCapture) { 1.647 + NS_WARNING("VisualEventTracer is not runing"); 1.648 + return NS_ERROR_NOT_INITIALIZED; 1.649 + } 1.650 + 1.651 + MOZ_EVENT_TRACER_MARK(this, "trace::stop"); 1.652 + 1.653 + gCapture = false; 1.654 + 1.655 + nsresult rv = NS_OK; 1.656 + if (PR_GetEnv("MOZ_TRACE_FILE")) { 1.657 + nsCOMPtr<nsIVisualEventTracerLog> tracelog; 1.658 + rv = Snapshot(getter_AddRefs(tracelog)); 1.659 + if (NS_SUCCEEDED(rv)) { 1.660 + rv = tracelog->WriteToProfilingFile(); 1.661 + } 1.662 + } 1.663 + 1.664 + return rv; 1.665 +} 1.666 + 1.667 +NS_IMETHODIMP 1.668 +VisualEventTracer::Snapshot(nsIVisualEventTracerLog ** _result) 1.669 +{ 1.670 + if (!gInitialized) 1.671 + return NS_ERROR_UNEXPECTED; 1.672 + 1.673 + RecordBatch * batch = RecordBatch::CloneLog(); 1.674 + 1.675 + nsRefPtr<VisualEventTracerLog> log = new VisualEventTracerLog(batch); 1.676 + log.forget(_result); 1.677 + 1.678 + return NS_OK; 1.679 +} 1.680 + 1.681 +#endif 1.682 + 1.683 +} // eventtracer 1.684 +} // mozilla