LCOV - code coverage report
Current view: top level - dom/media - MemoryBlockCache.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 2 133 1.5 %
Date: 2017-07-14 16:53:18 Functions: 2 18 11.1 %
Legend: Lines: hit not hit

          Line data    Source code
       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 file,
       5             :  * You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "MemoryBlockCache.h"
       8             : 
       9             : #include "MediaPrefs.h"
      10             : #include "mozilla/Atomics.h"
      11             : #include "mozilla/ClearOnShutdown.h"
      12             : #include "mozilla/Logging.h"
      13             : #include "mozilla/Telemetry.h"
      14             : #include "mozilla/Services.h"
      15             : #include "nsIObserver.h"
      16             : #include "nsIObserverService.h"
      17             : #include "nsWeakReference.h"
      18             : #include "prsystem.h"
      19             : 
      20             : namespace mozilla {
      21             : 
      22             : #undef LOG
      23             : LazyLogModule gMemoryBlockCacheLog("MemoryBlockCache");
      24             : #define LOG(x, ...)                                                            \
      25             :   MOZ_LOG(gMemoryBlockCacheLog, LogLevel::Debug, ("%p " x, this, ##__VA_ARGS__))
      26             : 
      27             : // Combined sizes of all MemoryBlockCache buffers.
      28             : // Initialized to 0 by non-local static initialization.
      29             : // Increases when a buffer grows (during initialization or unexpected OOB
      30             : // writes), decreases when a MemoryBlockCache (with its buffer) is destroyed.
      31             : static Atomic<size_t> gCombinedSizes;
      32             : 
      33             : class MemoryBlockCacheTelemetry final
      34             :   : public nsIObserver
      35             :   , public nsSupportsWeakReference
      36             : {
      37             : public:
      38             :   NS_DECL_ISUPPORTS
      39             :   NS_DECL_NSIOBSERVER
      40             : 
      41             :   // To be called when the combined size has grown, so that the watermark may
      42             :   // be updated if needed.
      43             :   // Ensures MemoryBlockCache telemetry will be reported at shutdown.
      44             :   // Returns current watermark.
      45             :   static size_t NotifyCombinedSizeGrown(size_t aNewSize);
      46             : 
      47             : private:
      48           0 :   MemoryBlockCacheTelemetry() {}
      49           0 :   ~MemoryBlockCacheTelemetry() {}
      50             : 
      51             :   // Singleton instance created when a first MediaCache is registered, and
      52             :   // released when the last MediaCache is unregistered.
      53             :   // The observer service will keep a weak reference to it, for notifications.
      54             :   static StaticRefPtr<MemoryBlockCacheTelemetry> gMemoryBlockCacheTelemetry;
      55             : 
      56             :   // Watermark for the combined sizes; can only increase when a buffer grows.
      57             :   static Atomic<size_t> gCombinedSizesWatermark;
      58             : };
      59             : 
      60             : // Initialized to nullptr by non-local static initialization.
      61             : /* static */ StaticRefPtr<MemoryBlockCacheTelemetry>
      62           3 :   MemoryBlockCacheTelemetry::gMemoryBlockCacheTelemetry;
      63             : 
      64             : // Initialized to 0 by non-local static initialization.
      65             : /* static */ Atomic<size_t> MemoryBlockCacheTelemetry::gCombinedSizesWatermark;
      66             : 
      67           0 : NS_IMPL_ISUPPORTS(MemoryBlockCacheTelemetry,
      68             :                   nsIObserver,
      69             :                   nsISupportsWeakReference)
      70             : 
      71             : /* static */ size_t
      72           0 : MemoryBlockCacheTelemetry::NotifyCombinedSizeGrown(size_t aNewSize)
      73             : {
      74           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
      75             : 
      76             :   // Ensure gMemoryBlockCacheTelemetry exists.
      77           0 :   if (!gMemoryBlockCacheTelemetry) {
      78           0 :     gMemoryBlockCacheTelemetry = new MemoryBlockCacheTelemetry();
      79             : 
      80             :     nsCOMPtr<nsIObserverService> observerService =
      81           0 :       mozilla::services::GetObserverService();
      82           0 :     if (observerService) {
      83           0 :       observerService->AddObserver(
      84           0 :         gMemoryBlockCacheTelemetry, "profile-change-teardown", true);
      85             :     }
      86             : 
      87             :     // Clearing gMemoryBlockCacheTelemetry when handling
      88             :     // "profile-change-teardown" could run the risk of re-creating it (and then
      89             :     // leaking it) if some MediaCache work happened after that notification.
      90             :     // So instead we just request it to be cleared on final shutdown.
      91           0 :     ClearOnShutdown(&gMemoryBlockCacheTelemetry);
      92             :   }
      93             : 
      94             :   // Update watermark if needed, report current watermark.
      95             :   for (;;) {
      96           0 :     size_t oldSize = gMemoryBlockCacheTelemetry->gCombinedSizesWatermark;
      97           0 :     if (aNewSize < oldSize) {
      98           0 :       return oldSize;
      99             :     }
     100           0 :     if (gMemoryBlockCacheTelemetry->gCombinedSizesWatermark.compareExchange(
     101             :           oldSize, aNewSize)) {
     102           0 :       return aNewSize;
     103             :     }
     104           0 :   }
     105             : }
     106             : 
     107             : NS_IMETHODIMP
     108           0 : MemoryBlockCacheTelemetry::Observe(nsISupports* aSubject,
     109             :                                    char const* aTopic,
     110             :                                    char16_t const* aData)
     111             : {
     112           0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     113             : 
     114           0 :   if (strcmp(aTopic, "profile-change-teardown") == 0) {
     115           0 :     uint32_t watermark = static_cast<uint32_t>(gCombinedSizesWatermark);
     116           0 :     LOG("MemoryBlockCacheTelemetry::~Observe() "
     117             :         "MEDIACACHE_MEMORY_WATERMARK=%" PRIu32,
     118             :         watermark);
     119             :     Telemetry::Accumulate(Telemetry::HistogramID::MEDIACACHE_MEMORY_WATERMARK,
     120           0 :                           watermark);
     121           0 :     return NS_OK;
     122             :   }
     123           0 :   return NS_OK;
     124             : }
     125             : 
     126             : enum MemoryBlockCacheTelemetryErrors
     127             : {
     128             :   // Don't change order/numbers! Add new values at the end and update
     129             :   // MEMORYBLOCKCACHE_ERRORS description in Histograms.json.
     130             :   InitUnderuse = 0,
     131             :   InitAllocation = 1,
     132             :   ReadOverrun = 2,
     133             :   WriteBlockOverflow = 3,
     134             :   WriteBlockCannotGrow = 4,
     135             :   MoveBlockSourceOverrun = 5,
     136             :   MoveBlockDestOverflow = 6,
     137             :   MoveBlockCannotGrow = 7,
     138             : };
     139             : 
     140             : static int32_t
     141           0 : CalculateMaxBlocks(int64_t aContentLength)
     142             : {
     143             :   // Note: It doesn't matter if calculations overflow, Init() would later fail.
     144             :   // We want at least enough blocks to contain the original content length.
     145             :   const int32_t requiredBlocks =
     146           0 :     int32_t((aContentLength - 1) / MediaBlockCacheBase::BLOCK_SIZE + 1);
     147             :   // Allow at least 1s of ultra HD (25Mbps).
     148             :   const int32_t workableBlocks =
     149           0 :     25 * 1024 * 1024 / 8 / MediaBlockCacheBase::BLOCK_SIZE;
     150           0 :   return std::max(requiredBlocks, workableBlocks);
     151             : }
     152             : 
     153           0 : MemoryBlockCache::MemoryBlockCache(int64_t aContentLength)
     154             :   // Buffer whole blocks.
     155           0 :   : mInitialContentLength((aContentLength >= 0) ? size_t(aContentLength) : 0)
     156           0 :   , mMaxBlocks(CalculateMaxBlocks(aContentLength))
     157             :   , mMutex("MemoryBlockCache")
     158           0 :   , mHasGrown(false)
     159             : {
     160           0 :   if (aContentLength <= 0) {
     161           0 :     LOG("MemoryBlockCache() MEMORYBLOCKCACHE_ERRORS='InitUnderuse'");
     162             :     Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
     163           0 :                           InitUnderuse);
     164             :   }
     165           0 : }
     166             : 
     167           0 : MemoryBlockCache::~MemoryBlockCache()
     168             : {
     169           0 :   size_t sizes = static_cast<size_t>(gCombinedSizes -= mBuffer.Length());
     170           0 :   LOG("~MemoryBlockCache() - destroying buffer of size %zu; combined sizes now "
     171             :       "%zu",
     172             :       mBuffer.Length(),
     173             :       sizes);
     174           0 : }
     175             : 
     176             : bool
     177           0 : MemoryBlockCache::EnsureBufferCanContain(size_t aContentLength)
     178             : {
     179           0 :   mMutex.AssertCurrentThreadOwns();
     180           0 :   if (aContentLength == 0) {
     181           0 :     return true;
     182             :   }
     183           0 :   const size_t initialLength = mBuffer.Length();
     184             :   const size_t desiredLength =
     185           0 :     ((aContentLength - 1) / BLOCK_SIZE + 1) * BLOCK_SIZE;
     186           0 :   if (initialLength >= desiredLength) {
     187             :     // Already large enough.
     188           0 :     return true;
     189             :   }
     190             :   // Need larger buffer. If we are allowed more memory, attempt to re-allocate.
     191           0 :   const size_t extra = desiredLength - initialLength;
     192             :   // Only check the very first allocation against the combined MemoryBlockCache
     193             :   // limit. Further growths will always be allowed, assuming MediaCache won't
     194             :   // go over GetMaxBlocks() by too much.
     195           0 :   if (initialLength == 0) {
     196             :     // Note: There is a small race between testing `atomic + extra > limit` and
     197             :     // committing to it with `atomic += extra` below; but this is acceptable, as
     198             :     // in the worst case it may allow a small number of buffers to go past the
     199             :     // limit.
     200             :     // The alternative would have been to reserve the space first with
     201             :     // `atomic += extra` and then undo it with `atomic -= extra` in case of
     202             :     // failure; but this would have meant potentially preventing other (small
     203             :     // but successful) allocations.
     204             :     static const size_t sysmem =
     205           0 :       std::max<size_t>(PR_GetPhysicalMemorySize(), 32 * 1024 * 1024);
     206             :     const size_t limit = std::min(
     207           0 :       size_t(MediaPrefs::MediaMemoryCachesCombinedLimitKb()) * 1024,
     208           0 :       sysmem * MediaPrefs::MediaMemoryCachesCombinedLimitPcSysmem() / 100);
     209           0 :     const size_t currentSizes = static_cast<size_t>(gCombinedSizes);
     210           0 :     if (currentSizes + extra > limit) {
     211           0 :       LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu;"
     212             :           " combined sizes %zu + %zu > limit %zu",
     213             :           aContentLength,
     214             :           initialLength,
     215             :           extra,
     216             :           desiredLength,
     217             :           currentSizes,
     218             :           extra,
     219             :           limit);
     220           0 :       return false;
     221             :     }
     222             :   }
     223           0 :   if (!mBuffer.SetLength(desiredLength, mozilla::fallible)) {
     224           0 :     LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu, "
     225             :         "allocation failed",
     226             :         aContentLength,
     227             :         initialLength,
     228             :         extra,
     229             :         desiredLength);
     230           0 :     return false;
     231             :   }
     232           0 :   MOZ_ASSERT(mBuffer.Length() == desiredLength);
     233           0 :   const size_t capacity = mBuffer.Capacity();
     234           0 :   const size_t extraCapacity = capacity - desiredLength;
     235           0 :   if (extraCapacity != 0) {
     236             :     // Our buffer was given a larger capacity than the requested length, we may
     237             :     // as well claim that extra capacity, both for our accounting, and to
     238             :     // possibly bypass some future growths that would fit in this new capacity.
     239           0 :     mBuffer.SetLength(capacity);
     240             :   }
     241             :   size_t newSizes =
     242           0 :     static_cast<size_t>(gCombinedSizes += (extra + extraCapacity));
     243             :   size_t watermark =
     244           0 :     MemoryBlockCacheTelemetry::NotifyCombinedSizeGrown(newSizes);
     245           0 :   LOG("EnsureBufferCanContain(%zu) - buffer size %zu + requested %zu + bonus "
     246             :       "%zu = %zu; combined "
     247             :       "sizes %zu, watermark %zu",
     248             :       aContentLength,
     249             :       initialLength,
     250             :       extra,
     251             :       extraCapacity,
     252             :       capacity,
     253             :       newSizes,
     254             :       watermark);
     255           0 :   mHasGrown = true;
     256           0 :   return true;
     257             : }
     258             : 
     259             : nsresult
     260           0 : MemoryBlockCache::Init()
     261             : {
     262           0 :   MutexAutoLock lock(mMutex);
     263           0 :   if (mBuffer.IsEmpty()) {
     264           0 :     LOG("Init()");
     265             :     // Attempt to pre-allocate buffer for expected content length.
     266           0 :     if (!EnsureBufferCanContain(mInitialContentLength)) {
     267           0 :       LOG("Init() MEMORYBLOCKCACHE_ERRORS='InitAllocation'");
     268             :       Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
     269           0 :                             InitAllocation);
     270           0 :       return NS_ERROR_FAILURE;
     271             :     }
     272             :   } else {
     273           0 :     LOG("Init() again");
     274             :     // Re-initialization - Just erase data.
     275           0 :     MOZ_ASSERT(mBuffer.Length() >= mInitialContentLength);
     276           0 :     memset(mBuffer.Elements(), 0, mBuffer.Length());
     277             :   }
     278             :   // Ignore initial growth.
     279           0 :   mHasGrown = false;
     280           0 :   return NS_OK;
     281             : }
     282             : 
     283             : nsresult
     284           0 : MemoryBlockCache::WriteBlock(uint32_t aBlockIndex,
     285             :                              Span<const uint8_t> aData1,
     286             :                              Span<const uint8_t> aData2)
     287             : {
     288           0 :   MutexAutoLock lock(mMutex);
     289             : 
     290           0 :   size_t offset = BlockIndexToOffset(aBlockIndex);
     291           0 :   if (offset + aData1.Length() + aData2.Length() > mBuffer.Length() &&
     292           0 :       !mHasGrown) {
     293           0 :     LOG("WriteBlock() MEMORYBLOCKCACHE_ERRORS='WriteBlockOverflow'");
     294             :     Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
     295           0 :                           WriteBlockOverflow);
     296             :   }
     297           0 :   if (!EnsureBufferCanContain(offset + aData1.Length() + aData2.Length())) {
     298           0 :     LOG("WriteBlock() MEMORYBLOCKCACHE_ERRORS='WriteBlockCannotGrow'");
     299             :     Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
     300           0 :                           WriteBlockCannotGrow);
     301           0 :     return NS_ERROR_FAILURE;
     302             :   }
     303             : 
     304           0 :   memcpy(mBuffer.Elements() + offset, aData1.Elements(), aData1.Length());
     305           0 :   if (aData2.Length() > 0) {
     306           0 :     memcpy(mBuffer.Elements() + offset + aData1.Length(),
     307           0 :            aData2.Elements(),
     308           0 :            aData2.Length());
     309             :   }
     310             : 
     311           0 :   return NS_OK;
     312             : }
     313             : 
     314             : nsresult
     315           0 : MemoryBlockCache::Read(int64_t aOffset,
     316             :                        uint8_t* aData,
     317             :                        int32_t aLength,
     318             :                        int32_t* aBytes)
     319             : {
     320           0 :   MutexAutoLock lock(mMutex);
     321             : 
     322           0 :   MOZ_ASSERT(aOffset >= 0);
     323           0 :   if (aOffset + aLength > int64_t(mBuffer.Length())) {
     324           0 :     LOG("Read() MEMORYBLOCKCACHE_ERRORS='ReadOverrun'");
     325             :     Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
     326           0 :                           ReadOverrun);
     327           0 :     return NS_ERROR_FAILURE;
     328             :   }
     329             : 
     330           0 :   memcpy(aData, mBuffer.Elements() + aOffset, aLength);
     331           0 :   *aBytes = aLength;
     332             : 
     333           0 :   return NS_OK;
     334             : }
     335             : 
     336             : nsresult
     337           0 : MemoryBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex)
     338             : {
     339           0 :   MutexAutoLock lock(mMutex);
     340             : 
     341           0 :   size_t sourceOffset = BlockIndexToOffset(aSourceBlockIndex);
     342           0 :   size_t destOffset = BlockIndexToOffset(aDestBlockIndex);
     343           0 :   if (sourceOffset + BLOCK_SIZE > mBuffer.Length()) {
     344           0 :     LOG("MoveBlock() MEMORYBLOCKCACHE_ERRORS='MoveBlockSourceOverrun'");
     345             :     Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
     346           0 :                           MoveBlockSourceOverrun);
     347           0 :     return NS_ERROR_FAILURE;
     348             :   }
     349           0 :   if (destOffset + BLOCK_SIZE > mBuffer.Length() && !mHasGrown) {
     350           0 :     LOG("MoveBlock() MEMORYBLOCKCACHE_ERRORS='MoveBlockDestOverflow'");
     351             :     Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
     352           0 :                           MoveBlockDestOverflow);
     353             :   }
     354           0 :   if (!EnsureBufferCanContain(destOffset + BLOCK_SIZE)) {
     355           0 :     LOG("MoveBlock() MEMORYBLOCKCACHE_ERRORS='MoveBlockCannotGrow'");
     356             :     Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
     357           0 :                           MoveBlockCannotGrow);
     358           0 :     return NS_ERROR_FAILURE;
     359             :   }
     360             : 
     361           0 :   memcpy(mBuffer.Elements() + destOffset,
     362           0 :          mBuffer.Elements() + sourceOffset,
     363           0 :          BLOCK_SIZE);
     364             : 
     365           0 :   return NS_OK;
     366             : }
     367             : 
     368           9 : } // End namespace mozilla.
     369             : 
     370             : // avoid redefined macro in unified build
     371             : #undef LOG

Generated by: LCOV version 1.13