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