DDMediaLogs.cpp (25641B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "DDMediaLogs.h" 8 9 #include "DDLogUtils.h" 10 #include "mozilla/JSONStringWriteFuncs.h" 11 #include "nsIThread.h" 12 #include "nsIThreadManager.h" 13 14 namespace mozilla { 15 16 /* static */ DDMediaLogs::ConstructionResult DDMediaLogs::New() { 17 nsCOMPtr<nsIThread> mThread; 18 nsresult rv = 19 NS_NewNamedThread("DDMediaLogs", getter_AddRefs(mThread), nullptr, 20 {.stackSize = nsIThreadManager::kThreadPoolStackSize}); 21 if (NS_WARN_IF(NS_FAILED(rv))) { 22 return {rv, nullptr}; 23 } 24 25 return {rv, new DDMediaLogs(std::move(mThread))}; 26 } 27 28 DDMediaLogs::DDMediaLogs(nsCOMPtr<nsIThread>&& aThread) 29 : mMediaLogs(1), mMutex("DDMediaLogs"), mThread(std::move(aThread)) { 30 mMediaLogs.SetLength(1); 31 mMediaLogs[0].mMediaElement = nullptr; 32 DDL_INFO("DDMediaLogs constructed, processing thread: %p", mThread.get()); 33 } 34 35 DDMediaLogs::~DDMediaLogs() { 36 // Perform end-of-life processing, ensure the processing thread is shutdown. 37 Shutdown(/* aPanic = */ false); 38 } 39 40 void DDMediaLogs::Panic() { Shutdown(/* aPanic = */ true); } 41 42 void DDMediaLogs::Shutdown(bool aPanic) { 43 nsCOMPtr<nsIThread> thread; 44 { 45 MutexAutoLock lock(mMutex); 46 thread.swap(mThread); 47 } 48 if (!thread) { 49 // Already shutdown, nothing more to do. 50 return; 51 } 52 53 DDL_INFO("DDMediaLogs::Shutdown will shutdown thread: %p", thread.get()); 54 // Will block until pending tasks have completed, and thread is dead. 55 thread->Shutdown(); 56 57 if (aPanic) { 58 mMessagesQueue.PopAll([](const DDLogMessage&) {}); 59 MutexAutoLock lock(mMutex); 60 mLifetimes.Clear(); 61 mMediaLogs.Clear(); 62 mObjectLinks.Clear(); 63 mPendingPromises.Clear(); 64 return; 65 } 66 67 // Final processing is only necessary to output to MOZ_LOG=DDLoggerEnd, 68 // so there's no point doing any of it if that MOZ_LOG is not enabled. 69 if (MOZ_LOG_TEST(sDecoderDoctorLoggerEndLog, mozilla::LogLevel::Info)) { 70 DDL_DEBUG("Perform final DDMediaLogs processing..."); 71 // The processing thread is dead, so we can safely call ProcessLog() 72 // directly from this thread. 73 ProcessLog(); 74 75 for (const DDMediaLog& mediaLog : mMediaLogs) { 76 if (mediaLog.mMediaElement) { 77 DDLE_INFO("---"); 78 } 79 DDLE_INFO("--- Log for HTMLMediaElement[%p] ---", mediaLog.mMediaElement); 80 for (const DDLogMessage& message : mediaLog.mMessages) { 81 DDLE_LOG(message.mCategory <= DDLogCategory::_Unlink 82 ? mozilla::LogLevel::Debug 83 : mozilla::LogLevel::Info, 84 "%s", message.Print(mLifetimes).get()); 85 } 86 DDLE_DEBUG("--- End log for HTMLMediaElement[%p] ---", 87 mediaLog.mMediaElement); 88 } 89 } 90 } 91 92 DDMediaLog& DDMediaLogs::LogForUnassociatedMessages() { 93 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 94 return mMediaLogs[0]; 95 } 96 const DDMediaLog& DDMediaLogs::LogForUnassociatedMessages() const { 97 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 98 return mMediaLogs[0]; 99 } 100 101 DDMediaLog* DDMediaLogs::GetLogFor(const dom::HTMLMediaElement* aMediaElement) { 102 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 103 if (!aMediaElement) { 104 return &LogForUnassociatedMessages(); 105 } 106 for (DDMediaLog& log : mMediaLogs) { 107 if (log.mMediaElement == aMediaElement) { 108 return &log; 109 } 110 } 111 return nullptr; 112 } 113 114 DDMediaLog& DDMediaLogs::LogFor(const dom::HTMLMediaElement* aMediaElement) { 115 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 116 DDMediaLog* log = GetLogFor(aMediaElement); 117 if (!log) { 118 log = mMediaLogs.AppendElement(); 119 log->mMediaElement = aMediaElement; 120 } 121 return *log; 122 } 123 124 void DDMediaLogs::SetMediaElement(DDLifetime& aLifetime, 125 const dom::HTMLMediaElement* aMediaElement) { 126 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 127 DDMediaLog& log = LogFor(aMediaElement); 128 129 // List of lifetimes that are to be linked to aMediaElement. 130 nsTArray<DDLifetime*> lifetimes; 131 // We start with the given lifetime. 132 lifetimes.AppendElement(&aLifetime); 133 for (size_t i = 0; i < lifetimes.Length(); ++i) { 134 DDLifetime& lifetime = *lifetimes[i]; 135 // Link the lifetime to aMediaElement. 136 lifetime.mMediaElement = aMediaElement; 137 // Classified lifetime's tag is a positive index from the DDMediaLog. 138 lifetime.mTag = ++log.mLifetimeCount; 139 DDL_DEBUG("%s -> HTMLMediaElement[%p]", lifetime.Printf().get(), 140 aMediaElement); 141 142 // Go through the lifetime's existing linked lifetimes, if any is not 143 // already linked to aMediaElement, add it to the list so it will get 144 // linked in a later loop. 145 for (auto& link : mObjectLinks) { 146 if (lifetime.IsAliveAt(link.mLinkingIndex)) { 147 if (lifetime.mObject == link.mParent) { 148 DDLifetime* childLifetime = 149 mLifetimes.FindLifetime(link.mChild, link.mLinkingIndex); 150 if (childLifetime && !childLifetime->mMediaElement && 151 !lifetimes.Contains(childLifetime)) { 152 lifetimes.AppendElement(childLifetime); 153 } 154 } else if (lifetime.mObject == link.mChild) { 155 DDLifetime* parentLifetime = 156 mLifetimes.FindLifetime(link.mParent, link.mLinkingIndex); 157 if (parentLifetime && !parentLifetime->mMediaElement && 158 !lifetimes.Contains(parentLifetime)) { 159 lifetimes.AppendElement(parentLifetime); 160 } 161 } 162 } 163 } 164 } 165 166 // Now we need to move yet-unclassified messages related to the just-set 167 // elements, to the appropriate MediaElement list. 168 DDMediaLog::LogMessages& messages = log.mMessages; 169 DDMediaLog::LogMessages& messages0 = LogForUnassociatedMessages().mMessages; 170 for (size_t i = 0; i < messages0.Length(); 171 /* increment inside the loop */) { 172 DDLogMessage& message = messages0[i]; 173 bool found = false; 174 for (const DDLifetime* lifetime : lifetimes) { 175 if (lifetime->mObject == message.mObject) { 176 found = true; 177 break; 178 } 179 } 180 if (found) { 181 messages.AppendElement(std::move(message)); 182 messages0.RemoveElementAt(i); 183 // No increment, as we've removed this element; next element is now at 184 // the same index. 185 } else { 186 // Not touching this element, increment index to go to next element. 187 ++i; 188 } 189 } 190 } 191 192 DDLifetime& DDMediaLogs::FindOrCreateLifetime(const DDLogObject& aObject, 193 DDMessageIndex aIndex, 194 const DDTimeStamp& aTimeStamp) { 195 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 196 // Try to find lifetime corresponding to message object. 197 DDLifetime* lifetime = mLifetimes.FindLifetime(aObject, aIndex); 198 if (!lifetime) { 199 // No lifetime yet, create one. 200 lifetime = &(mLifetimes.CreateLifetime(aObject, aIndex, aTimeStamp)); 201 if (MOZ_UNLIKELY(aObject.TypeName() == 202 DDLoggedTypeTraits<dom::HTMLMediaElement>::Name())) { 203 const dom::HTMLMediaElement* mediaElement = 204 static_cast<const dom::HTMLMediaElement*>(aObject.Pointer()); 205 SetMediaElement(*lifetime, mediaElement); 206 DDL_DEBUG("%s -> new lifetime: %s with MediaElement %p", 207 aObject.Printf().get(), lifetime->Printf().get(), mediaElement); 208 } else { 209 DDL_DEBUG("%s -> new lifetime: %s", aObject.Printf().get(), 210 lifetime->Printf().get()); 211 } 212 } 213 214 return *lifetime; 215 } 216 217 void DDMediaLogs::LinkLifetimes(DDLifetime& aParentLifetime, 218 const char* aLinkName, 219 DDLifetime& aChildLifetime, 220 DDMessageIndex aIndex) { 221 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 222 mObjectLinks.AppendElement(DDObjectLink{ 223 aParentLifetime.mObject, aChildLifetime.mObject, aLinkName, aIndex}); 224 if (aParentLifetime.mMediaElement) { 225 if (!aChildLifetime.mMediaElement) { 226 SetMediaElement(aChildLifetime, aParentLifetime.mMediaElement); 227 } 228 } else if (aChildLifetime.mMediaElement) { 229 if (!aParentLifetime.mMediaElement) { 230 SetMediaElement(aParentLifetime, aChildLifetime.mMediaElement); 231 } 232 } 233 } 234 235 void DDMediaLogs::UnlinkLifetime(DDLifetime& aLifetime, DDMessageIndex aIndex) { 236 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 237 for (DDObjectLink& link : mObjectLinks) { 238 if ((link.mParent == aLifetime.mObject || 239 link.mChild == aLifetime.mObject) && 240 aLifetime.IsAliveAt(link.mLinkingIndex) && !link.mUnlinkingIndex) { 241 link.mUnlinkingIndex = Some(aIndex); 242 } 243 } 244 }; 245 246 void DDMediaLogs::UnlinkLifetimes(DDLifetime& aParentLifetime, 247 DDLifetime& aChildLifetime, 248 DDMessageIndex aIndex) { 249 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 250 for (DDObjectLink& link : mObjectLinks) { 251 if ((link.mParent == aParentLifetime.mObject && 252 link.mChild == aChildLifetime.mObject) && 253 aParentLifetime.IsAliveAt(link.mLinkingIndex) && 254 aChildLifetime.IsAliveAt(link.mLinkingIndex) && !link.mUnlinkingIndex) { 255 link.mUnlinkingIndex = Some(aIndex); 256 } 257 } 258 } 259 260 void DDMediaLogs::DestroyLifetimeLinks(const DDLifetime& aLifetime) { 261 mObjectLinks.RemoveElementsBy([&](DDObjectLink& link) { 262 return (link.mParent == aLifetime.mObject || 263 link.mChild == aLifetime.mObject) && 264 aLifetime.IsAliveAt(link.mLinkingIndex); 265 }); 266 } 267 268 size_t DDMediaLogs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 269 size_t size = aMallocSizeOf(this) + 270 // This will usually be called after processing, so negligible 271 // external data should still be present in the queue. 272 mMessagesQueue.ShallowSizeOfExcludingThis(aMallocSizeOf) + 273 mLifetimes.SizeOfExcludingThis(aMallocSizeOf) + 274 mMediaLogs.ShallowSizeOfExcludingThis(aMallocSizeOf) + 275 mObjectLinks.ShallowSizeOfExcludingThis(aMallocSizeOf) + 276 mPendingPromises.ShallowSizeOfExcludingThis(aMallocSizeOf); 277 for (const DDMediaLog& log : mMediaLogs) { 278 size += log.SizeOfExcludingThis(aMallocSizeOf); 279 } 280 return size; 281 } 282 283 void DDMediaLogs::ProcessBuffer() { 284 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 285 286 mMessagesQueue.PopAll([this](const DDLogMessage& message) { 287 DDL_DEBUG("Processing: %s", message.Print().Data()); 288 289 // Either this message will carry a new object for which to create a 290 // lifetime, or we'll find an existing one. 291 DDLifetime& lifetime = FindOrCreateLifetime(message.mObject, message.mIndex, 292 message.mTimeStamp); 293 294 // Copy the message contents (without the mValid flag) to the 295 // appropriate MediaLog corresponding to the message's object lifetime. 296 LogFor(lifetime.mMediaElement) 297 .mMessages.AppendElement(static_cast<const DDLogMessage&>(message)); 298 299 switch (message.mCategory) { 300 case DDLogCategory::_Construction: 301 // The FindOrCreateLifetime above will have set a construction time, 302 // so there's nothing more we need to do here. 303 MOZ_ASSERT(lifetime.mConstructionTimeStamp); 304 break; 305 306 case DDLogCategory::_DerivedConstruction: 307 // The FindOrCreateLifetime above will have set a construction time. 308 MOZ_ASSERT(lifetime.mConstructionTimeStamp); 309 // A derived construction must come with the base object. 310 MOZ_ASSERT(message.mValue.is<DDLogObject>()); 311 { 312 const DDLogObject& base = message.mValue.as<DDLogObject>(); 313 DDLifetime& baseLifetime = 314 FindOrCreateLifetime(base, message.mIndex, message.mTimeStamp); 315 // FindOrCreateLifetime could have moved `lifetime`. 316 DDLifetime* lifetime2 = 317 mLifetimes.FindLifetime(message.mObject, message.mIndex); 318 MOZ_ASSERT(lifetime2); 319 // Assume there's no multiple-inheritance (at least for the types 320 // we're watching.) 321 if (baseLifetime.mDerivedObject.Pointer()) { 322 DDL_WARN( 323 "base '%s' was already derived as '%s', now deriving as '%s'", 324 baseLifetime.Printf().get(), 325 baseLifetime.mDerivedObject.Printf().get(), 326 lifetime2->Printf().get()); 327 } 328 baseLifetime.mDerivedObject = lifetime2->mObject; 329 baseLifetime.mDerivedObjectLinkingIndex = message.mIndex; 330 // Link the base and derived objects, to ensure they go to the same 331 // log. 332 LinkLifetimes(*lifetime2, "is-a", baseLifetime, message.mIndex); 333 } 334 break; 335 336 case DDLogCategory::_Destruction: 337 lifetime.mDestructionIndex = message.mIndex; 338 lifetime.mDestructionTimeStamp = message.mTimeStamp; 339 UnlinkLifetime(lifetime, message.mIndex); 340 break; 341 342 case DDLogCategory::_Link: 343 MOZ_ASSERT(message.mValue.is<DDLogObject>()); 344 { 345 const DDLogObject& child = message.mValue.as<DDLogObject>(); 346 DDLifetime& childLifetime = 347 FindOrCreateLifetime(child, message.mIndex, message.mTimeStamp); 348 // FindOrCreateLifetime could have moved `lifetime`. 349 DDLifetime* lifetime2 = 350 mLifetimes.FindLifetime(message.mObject, message.mIndex); 351 MOZ_ASSERT(lifetime2); 352 LinkLifetimes(*lifetime2, message.mLabel, childLifetime, 353 message.mIndex); 354 } 355 break; 356 357 case DDLogCategory::_Unlink: 358 MOZ_ASSERT(message.mValue.is<DDLogObject>()); 359 { 360 const DDLogObject& child = message.mValue.as<DDLogObject>(); 361 DDLifetime& childLifetime = 362 FindOrCreateLifetime(child, message.mIndex, message.mTimeStamp); 363 // FindOrCreateLifetime could have moved `lifetime`. 364 DDLifetime* lifetime2 = 365 mLifetimes.FindLifetime(message.mObject, message.mIndex); 366 MOZ_ASSERT(lifetime2); 367 UnlinkLifetimes(*lifetime2, childLifetime, message.mIndex); 368 } 369 break; 370 371 default: 372 // Anything else: Nothing more to do. 373 break; 374 } 375 }); 376 } 377 378 void DDMediaLogs::FulfillPromises() { 379 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 380 381 MozPromiseHolder<LogMessagesPromise> promiseHolder; 382 const dom::HTMLMediaElement* mediaElement = nullptr; 383 { 384 // Grab the first pending promise (if any). 385 // Note that we don't pop it yet, so we don't potentially leave the list 386 // empty and therefore allow another processing task to be dispatched. 387 MutexAutoLock lock(mMutex); 388 if (mPendingPromises.IsEmpty()) { 389 return; 390 } 391 promiseHolder = std::move(mPendingPromises[0].mPromiseHolder); 392 mediaElement = mPendingPromises[0].mMediaElement; 393 } 394 for (;;) { 395 DDMediaLog* log = GetLogFor(mediaElement); 396 if (!log) { 397 // No such media element -> Reject this promise. 398 DDL_INFO("Rejecting promise for HTMLMediaElement[%p] - Cannot find log", 399 mediaElement); 400 promiseHolder.Reject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR, __func__); 401 // Pop this rejected promise, fetch next one. 402 MutexAutoLock lock(mMutex); 403 mPendingPromises.RemoveElementAt(0); 404 if (mPendingPromises.IsEmpty()) { 405 break; 406 } 407 promiseHolder = std::move(mPendingPromises[0].mPromiseHolder); 408 mediaElement = mPendingPromises[0].mMediaElement; 409 continue; 410 } 411 412 JSONStringWriteFunc<nsCString> json; 413 JSONWriter jw{json}; 414 jw.Start(); 415 jw.StartArrayProperty("messages"); 416 for (const DDLogMessage& message : log->mMessages) { 417 jw.StartObjectElement(JSONWriter::SingleLineStyle); 418 jw.IntProperty("i", message.mIndex.Value()); 419 jw.DoubleProperty("ts", ToSeconds(message.mTimeStamp)); 420 DDLifetime* lifetime = 421 mLifetimes.FindLifetime(message.mObject, message.mIndex); 422 if (lifetime) { 423 jw.IntProperty("ob", lifetime->mTag); 424 } else { 425 jw.StringProperty( 426 "ob", nsPrintfCString(R"("%s[%p]")", message.mObject.TypeName(), 427 message.mObject.Pointer())); 428 } 429 jw.StringProperty("cat", 430 MakeStringSpan(ToShortString(message.mCategory))); 431 if (message.mLabel && message.mLabel[0] != '\0') { 432 jw.StringProperty("lbl", MakeStringSpan(message.mLabel)); 433 } 434 if (!message.mValue.is<DDNoValue>()) { 435 if (message.mValue.is<DDLogObject>()) { 436 const DDLogObject& ob2 = message.mValue.as<DDLogObject>(); 437 DDLifetime* lifetime2 = mLifetimes.FindLifetime(ob2, message.mIndex); 438 if (lifetime2) { 439 jw.IntProperty("val", lifetime2->mTag); 440 } else { 441 ToJSON(message.mValue, jw, "val"); 442 } 443 } else { 444 ToJSON(message.mValue, jw, "val"); 445 } 446 } 447 jw.EndObject(); 448 } 449 jw.EndArray(); 450 jw.StartObjectProperty("objects"); 451 mLifetimes.Visit( 452 mediaElement, 453 [&](const DDLifetime& lifetime) { 454 jw.StartObjectProperty(nsPrintfCString("%" PRIi32, lifetime.mTag), 455 JSONWriter::SingleLineStyle); 456 jw.IntProperty("tag", lifetime.mTag); 457 jw.StringProperty("cls", MakeStringSpan(lifetime.mObject.TypeName())); 458 jw.StringProperty("ptr", 459 nsPrintfCString("%p", lifetime.mObject.Pointer())); 460 jw.IntProperty("con", lifetime.mConstructionIndex.Value()); 461 jw.DoubleProperty("con_ts", 462 ToSeconds(lifetime.mConstructionTimeStamp)); 463 if (lifetime.mDestructionTimeStamp) { 464 jw.IntProperty("des", lifetime.mDestructionIndex.Value()); 465 jw.DoubleProperty("des_ts", 466 ToSeconds(lifetime.mDestructionTimeStamp)); 467 } 468 if (lifetime.mDerivedObject.Pointer()) { 469 DDLifetime* derived = mLifetimes.FindLifetime( 470 lifetime.mDerivedObject, lifetime.mDerivedObjectLinkingIndex); 471 if (derived) { 472 jw.IntProperty("drvd", derived->mTag); 473 } 474 } 475 jw.EndObject(); 476 }, 477 // If there were no (new) messages, only give the main HTMLMediaElement 478 // object (used to identify this log against the correct element.) 479 log->mMessages.IsEmpty()); 480 jw.EndObject(); 481 jw.End(); 482 DDL_DEBUG("RetrieveMessages(%p) ->\n%s", mediaElement, 483 json.StringCRef().get()); 484 485 // This log exists (new messages or not) -> Resolve this promise. 486 DDL_INFO("Resolving promise for HTMLMediaElement[%p] with messages %" PRImi 487 "-%" PRImi, 488 mediaElement, 489 log->mMessages.IsEmpty() ? 0 : log->mMessages[0].mIndex.Value(), 490 log->mMessages.IsEmpty() 491 ? 0 492 : log->mMessages[log->mMessages.Length() - 1].mIndex.Value()); 493 promiseHolder.Resolve(std::move(json).StringRRef(), __func__); 494 495 // Remove exported messages. 496 log->mMessages.Clear(); 497 498 // Pop this resolved promise, fetch next one. 499 MutexAutoLock lock(mMutex); 500 mPendingPromises.RemoveElementAt(0); 501 if (mPendingPromises.IsEmpty()) { 502 break; 503 } 504 promiseHolder = std::move(mPendingPromises[0].mPromiseHolder); 505 mediaElement = mPendingPromises[0].mMediaElement; 506 } 507 } 508 509 void DDMediaLogs::CleanUpLogs() { 510 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 511 512 DDTimeStamp now = DDNow(); 513 514 // Keep up to 30s of unclassified messages (if a message doesn't get 515 // classified this quickly, it probably never will be.) 516 static const double sMaxAgeUnclassifiedMessages_s = 30.0; 517 // Keep "dead" log (video element and dependents were destroyed) for up to 518 // 2 minutes, in case the user wants to look at it after the facts. 519 static const double sMaxAgeDeadLog_s = 120.0; 520 // Keep old messages related to a live video for up to 5 minutes. 521 static const double sMaxAgeClassifiedMessages_s = 300.0; 522 523 for (size_t logIndexPlus1 = mMediaLogs.Length(); logIndexPlus1 != 0; 524 --logIndexPlus1) { 525 DDMediaLog& log = mMediaLogs[logIndexPlus1 - 1]; 526 if (log.mMediaElement) { 527 // Remove logs for which no lifetime still existed some time ago. 528 bool used = mLifetimes.VisitBreakable( 529 log.mMediaElement, [&](const DDLifetime& lifetime) { 530 // Do we still have a lifetime that existed recently enough? 531 return !lifetime.mDestructionTimeStamp || 532 (now - lifetime.mDestructionTimeStamp).ToSeconds() <= 533 sMaxAgeDeadLog_s; 534 }); 535 if (!used) { 536 DDL_INFO("Removed old log for media element %p", log.mMediaElement); 537 mLifetimes.Visit(log.mMediaElement, [&](const DDLifetime& lifetime) { 538 DestroyLifetimeLinks(lifetime); 539 }); 540 mLifetimes.RemoveLifetimesFor(log.mMediaElement); 541 mMediaLogs.RemoveElementAt(logIndexPlus1 - 1); 542 continue; 543 } 544 } 545 546 // Remove old messages. 547 size_t old = 0; 548 const size_t len = log.mMessages.Length(); 549 while (old < len && 550 (now - log.mMessages[old].mTimeStamp).ToSeconds() > 551 (log.mMediaElement ? sMaxAgeClassifiedMessages_s 552 : sMaxAgeUnclassifiedMessages_s)) { 553 ++old; 554 } 555 if (old != 0) { 556 // We are going to remove `old` messages. 557 // First, remove associated destroyed lifetimes that are not used after 558 // these old messages. (We want to keep non-destroyed lifetimes, in 559 // case they get used later on.) 560 size_t removedLifetimes = 0; 561 for (size_t i = 0; i < old; ++i) { 562 auto RemoveDestroyedUnusedLifetime = [&](DDLifetime* lifetime) { 563 if (!lifetime->mDestructionTimeStamp) { 564 // Lifetime is still alive, keep it. 565 return; 566 } 567 bool used = false; 568 for (size_t after = old; after < len; ++after) { 569 const DDLogMessage message = log.mMessages[i]; 570 if (!lifetime->IsAliveAt(message.mIndex)) { 571 // Lifetime is already dead, and not used yet -> kill it. 572 break; 573 } 574 const DDLogObject& ob = message.mObject; 575 if (lifetime->mObject == ob) { 576 used = true; 577 break; 578 } 579 if (message.mValue.is<DDLogObject>()) { 580 if (lifetime->mObject == message.mValue.as<DDLogObject>()) { 581 used = true; 582 break; 583 } 584 } 585 } 586 if (!used) { 587 DestroyLifetimeLinks(*lifetime); 588 mLifetimes.RemoveLifetime(lifetime); 589 ++removedLifetimes; 590 } 591 }; 592 593 const DDLogMessage message = log.mMessages[i]; 594 const DDLogObject& ob = message.mObject; 595 596 DDLifetime* lifetime1 = mLifetimes.FindLifetime(ob, message.mIndex); 597 if (lifetime1) { 598 RemoveDestroyedUnusedLifetime(lifetime1); 599 } 600 601 if (message.mValue.is<DDLogObject>()) { 602 DDLifetime* lifetime2 = mLifetimes.FindLifetime( 603 message.mValue.as<DDLogObject>(), message.mIndex); 604 if (lifetime2) { 605 RemoveDestroyedUnusedLifetime(lifetime2); 606 } 607 } 608 } 609 DDL_INFO("Removed %zu messages (#%" PRImi " %f - #%" PRImi 610 " %f) and %zu lifetimes from log for media element %p", 611 old, log.mMessages[0].mIndex.Value(), 612 ToSeconds(log.mMessages[0].mTimeStamp), 613 log.mMessages[old - 1].mIndex.Value(), 614 ToSeconds(log.mMessages[old - 1].mTimeStamp), removedLifetimes, 615 log.mMediaElement); 616 log.mMessages.RemoveElementsAt(0, old); 617 } 618 } 619 } 620 621 void DDMediaLogs::ProcessLog() { 622 MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread()); 623 ProcessBuffer(); 624 FulfillPromises(); 625 CleanUpLogs(); 626 DDL_INFO("ProcessLog() completed - DDMediaLog size: %zu", 627 SizeOfIncludingThis(moz_malloc_size_of)); 628 } 629 630 nsresult DDMediaLogs::DispatchProcessLog(const MutexAutoLock& aProofOfLock) { 631 if (!mThread) { 632 return NS_ERROR_SERVICE_NOT_AVAILABLE; 633 } 634 return mThread->Dispatch( 635 NS_NewRunnableFunction("ProcessLog", [this] { ProcessLog(); }), 636 NS_DISPATCH_NORMAL); 637 } 638 639 nsresult DDMediaLogs::DispatchProcessLog() { 640 DDL_INFO("DispatchProcessLog() - Yet-unprocessed message buffers: %d", 641 mMessagesQueue.LiveBuffersStats().mCount); 642 MutexAutoLock lock(mMutex); 643 return DispatchProcessLog(lock); 644 } 645 646 RefPtr<DDMediaLogs::LogMessagesPromise> DDMediaLogs::RetrieveMessages( 647 const dom::HTMLMediaElement* aMediaElement) { 648 MozPromiseHolder<LogMessagesPromise> holder; 649 RefPtr<LogMessagesPromise> promise = holder.Ensure(__func__); 650 { 651 MutexAutoLock lock(mMutex); 652 // If there were unfulfilled promises, we know processing has already 653 // been requested. 654 if (mPendingPromises.IsEmpty()) { 655 // But if we're the first one, start processing. 656 nsresult rv = DispatchProcessLog(lock); 657 if (NS_FAILED(rv)) { 658 holder.Reject(rv, __func__); 659 } 660 } 661 mPendingPromises.AppendElement( 662 PendingPromise{std::move(holder), aMediaElement}); 663 } 664 return promise; 665 } 666 667 } // namespace mozilla