Bug 1268021 - Implement memory reporting for the user-font cache. r=njn
authorJonathan Kew <jkew@mozilla.com>
Tue, 03 May 2016 12:14:34 +0100
changeset 320568 a8d46c58ce5832e87506215efd86d2bd46262311
parent 320567 58ea1b949927c31b30098d05b5dee11664d38897
child 320569 988f42d740b6a94cd46ad9cc1ad6b30f6c8541e3
push id9671
push userraliiev@mozilla.com
push dateMon, 06 Jun 2016 20:27:52 +0000
treeherdermozilla-aurora@cea65ca3d0bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs1268021
milestone49.0a1
Bug 1268021 - Implement memory reporting for the user-font cache. r=njn
gfx/thebes/gfxFontEntry.cpp
gfx/thebes/gfxFontEntry.h
gfx/thebes/gfxUserFontSet.cpp
gfx/thebes/gfxUserFontSet.h
--- a/gfx/thebes/gfxFontEntry.cpp
+++ b/gfx/thebes/gfxFontEntry.cpp
@@ -94,17 +94,18 @@ gfxFontEntry::gfxFontEntry() :
     mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
     mUVSOffset(0), mUVSData(nullptr),
     mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
     mCOLR(nullptr),
     mCPAL(nullptr),
     mUnitsPerEm(0),
     mHBFace(nullptr),
     mGrFace(nullptr),
-    mGrFaceRefCnt(0)
+    mGrFaceRefCnt(0),
+    mComputedSizeOfUserFont(0)
 {
     memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
     memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
 }
 
 gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
     mName(aName), mStyle(NS_FONT_STYLE_NORMAL), mFixedPitch(false),
     mIsValid(true),
@@ -133,17 +134,18 @@ gfxFontEntry::gfxFontEntry(const nsAStri
     mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
     mUVSOffset(0), mUVSData(nullptr),
     mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
     mCOLR(nullptr),
     mCPAL(nullptr),
     mUnitsPerEm(0),
     mHBFace(nullptr),
     mGrFace(nullptr),
-    mGrFaceRefCnt(0)
+    mGrFaceRefCnt(0),
+    mComputedSizeOfUserFont(0)
 {
     memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
     memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
 }
 
 gfxFontEntry::~gfxFontEntry()
 {
     if (mCOLR) {
@@ -1141,16 +1143,42 @@ gfxFontEntry::AddSizeOfExcludingThis(Mal
 void
 gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
                                      FontListSizes* aSizes) const
 {
     aSizes->mFontListSize += aMallocSizeOf(this);
     AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
 }
 
+// This is used to report the size of an individual downloaded font in the
+// user font cache. (Fonts that are part of the platform font list accumulate
+// their sizes to the font list's reporter using the AddSizeOf... methods
+// above.)
+size_t
+gfxFontEntry::ComputedSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+    FontListSizes s = { 0 };
+    AddSizeOfExcludingThis(aMallocSizeOf, &s);
+
+    // When reporting memory used for the main platform font list,
+    // where we're typically summing the totals for a few hundred font faces,
+    // we report the fields of FontListSizes separately.
+    // But for downloaded user fonts, the actual resource data (added below)
+    // will dominate, and the minor overhead of these pieces isn't worth
+    // splitting out for an individual font.
+    size_t result = s.mFontListSize + s.mFontTableCacheSize + s.mCharMapsSize;
+
+    if (mIsDataUserFont) {
+        MOZ_ASSERT(mComputedSizeOfUserFont > 0, "user font with no data?");
+        result += mComputedSizeOfUserFont;
+    }
+
+    return result;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 //
 // class gfxFontFamily
 //
 //////////////////////////////////////////////////////////////////////////////
 
 // we consider faces with mStandardFace == true to be "less than" those with false,
 // because during style matching, earlier entries are tried first
--- a/gfx/thebes/gfxFontEntry.h
+++ b/gfx/thebes/gfxFontEntry.h
@@ -370,22 +370,27 @@ public:
 
     // Release any SVG-glyphs document this font may have loaded.
     void DisconnectSVG();
 
     // Called to notify that aFont is being destroyed. Needed when we're tracking
     // the fonts belonging to this font entry.
     void NotifyFontDestroyed(gfxFont* aFont);
 
-    // For memory reporting
+    // For memory reporting of the platform font list.
     virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontListSizes* aSizes) const;
     virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontListSizes* aSizes) const;
 
+    // Used for reporting on individual font entries in the user font cache,
+    // which are not present in the platform font list.
+    size_t
+    ComputedSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
     // Used when checking for complex script support, to mask off cmap ranges
     struct ScriptRange {
         uint32_t         rangeStart;
         uint32_t         rangeEnd;
         hb_tag_t         tags[3]; // one or two OpenType script tags to check,
                                   // plus a NULL terminator
     };
 
@@ -448,16 +453,17 @@ public:
     hb_blob_t*       mCPAL;
 
 protected:
     friend class gfxPlatformFontList;
     friend class gfxMacPlatformFontList;
     friend class gfxUserFcFontEntry;
     friend class gfxFontFamily;
     friend class gfxSingleFaceMacFontFamily;
+    friend class gfxUserFontEntry;
 
     gfxFontEntry();
 
     // Protected destructor, to discourage deletion outside of Release():
     virtual ~gfxFontEntry();
 
     virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) {
         NS_NOTREACHED("oops, somebody didn't override CreateFontInstance");
@@ -526,16 +532,22 @@ protected:
     nsrefcnt mGrFaceRefCnt;
 
     static const void* GrGetTable(const void *aAppFaceHandle,
                                   unsigned int aName,
                                   size_t *aLen);
     static void GrReleaseTable(const void *aAppFaceHandle,
                                const void *aTableBuffer);
 
+    // For memory reporting: size of user-font data belonging to this entry.
+    // We record this in the font entry because the actual data block may be
+    // handed over to platform APIs, so that it would become difficult (and
+    // platform-specific) to measure it directly at report-gathering time.
+    uint32_t mComputedSizeOfUserFont;
+
 private:
     /**
      * Font table hashtable, to support GetFontTable for harfbuzz.
      *
      * The harfbuzz shaper (and potentially other clients) needs access to raw
      * font table data. This needs to be cached so that it can be used
      * repeatedly (each time we construct a text run; in some cases, for
      * each character/glyph within the run) without re-fetching large tables
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -597,16 +597,18 @@ gfxUserFontEntry::LoadNextSrc()
 }
 
 void
 gfxUserFontEntry::SetLoadState(UserFontLoadState aLoadState)
 {
     mUserFontLoadState = aLoadState;
 }
 
+MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(UserFontMallocSizeOfOnAlloc)
+
 bool
 gfxUserFontEntry::LoadPlatformFont(const uint8_t* aFontData, uint32_t& aLength)
 {
     NS_ASSERTION((mUserFontLoadState == STATUS_NOT_LOADED ||
                   mUserFontLoadState == STATUS_LOADING) &&
                  mFontDataLoadingState < LOADING_FAILED,
                  "attempting to load a font that has either completed or failed");
 
@@ -623,16 +625,17 @@ gfxUserFontEntry::LoadPlatformFont(const
     // in the font with a synthetic one, we save the original name so that
     // it can be reported via the nsIDOMFontFace API.
     nsAutoString originalFullName;
 
     // Call the OTS sanitizer; this will also decode WOFF to sfnt
     // if necessary. The original data in aFontData is left unchanged.
     uint32_t saneLen;
     uint32_t fontCompressionRatio = 0;
+    size_t computedSize = 0;
     const uint8_t* saneData =
         SanitizeOpenTypeData(aFontData, aLength, saneLen, fontType);
     if (!saneData) {
         mFontSet->LogMessage(this, "rejected by sanitizer");
     }
     if (saneData) {
         if (saneLen) {
             fontCompressionRatio = uint32_t(100.0 * aLength / saneLen + 0.5);
@@ -646,30 +649,41 @@ gfxUserFontEntry::LoadPlatformFont(const
         }
 
         // The sanitizer ensures that we have a valid sfnt and a usable
         // name table, so this should never fail unless we're out of
         // memory, and GetFullNameFromSFNT is not directly exposed to
         // arbitrary/malicious data from the web.
         gfxFontUtils::GetFullNameFromSFNT(saneData, saneLen,
                                           originalFullName);
+
+        // Record size for memory reporting purposes. We measure this now
+        // because by the time we potentially want to collect reports, this
+        // data block may have been handed off to opaque OS font APIs that
+        // don't allow us to retrieve or measure it directly.
+        // The *OnAlloc function will also tell DMD about this block, as the
+        // OS font code may hold on to it for an extended period.
+        computedSize = UserFontMallocSizeOfOnAlloc(saneData);
+
         // Here ownership of saneData is passed to the platform,
         // which will delete it when no longer required
         fe = gfxPlatform::GetPlatform()->MakePlatformFont(mName,
                                                           mWeight,
                                                           mStretch,
                                                           mStyle,
                                                           saneData,
                                                           saneLen);
         if (!fe) {
             mFontSet->LogMessage(this, "not usable by platform");
         }
     }
 
     if (fe) {
+        fe->mComputedSizeOfUserFont = computedSize;
+
         // Save a copy of the metadata block (if present) for nsIDOMFontFace
         // to use if required. Ownership of the metadata block will be passed
         // to the gfxUserFontData record below.
         FallibleTArray<uint8_t> metadata;
         uint32_t metaOrigLen = 0;
         uint8_t compression = gfxUserFontData::kUnknownCompression;
         if (fontType == GFX_USERFONT_WOFF) {
             CopyWOFFMetadata<WOFFHeader>(aFontData, aLength,
@@ -1146,16 +1160,23 @@ gfxUserFontSet::UserFontCache::CacheFont
             mozilla::services::GetObserverService();
         if (obs) {
             Flusher* flusher = new Flusher;
             obs->AddObserver(flusher, "cacheservice:empty-cache",
                              false);
             obs->AddObserver(flusher, "last-pb-context-exited", false);
             obs->AddObserver(flusher, "xpcom-shutdown", false);
         }
+
+        // Create and register a memory reporter for sUserFonts.
+        // This reporter is never unregistered, but that's OK because
+        // the reporter checks whether sUserFonts is null, so it would
+        // be safe to call even after UserFontCache::Shutdown has deleted
+        // the cache.
+        RegisterStrongMemoryReporter(new MemoryReporter());
     }
 
     if (data->mLength) {
         MOZ_ASSERT(aPersistence == kPersistent);
         MOZ_ASSERT(!data->mPrivate);
         sUserFonts->PutEntry(Key(data->mCRC32, data->mLength, aFontEntry,
                                  data->mPrivate, aPersistence));
     } else {
@@ -1272,16 +1293,99 @@ void
 gfxUserFontSet::UserFontCache::Shutdown()
 {
     if (sUserFonts) {
         delete sUserFonts;
         sUserFonts = nullptr;
     }
 }
 
+MOZ_DEFINE_MALLOC_SIZE_OF(UserFontsMallocSizeOf)
+
+nsresult
+gfxUserFontSet::UserFontCache::Entry::ReportMemory(nsIMemoryReporterCallback* aCb,
+                                                   nsISupports* aClosure,
+                                                   bool aAnonymize)
+{
+    nsAutoCString path("explicit/gfx/user-fonts/font(");
+
+    if (aAnonymize) {
+        path.AppendPrintf("<anonymized-%p>", this);
+    } else {
+        if (mFontEntry) { // this should always be present
+            NS_ConvertUTF16toUTF8 familyName(mFontEntry->mFamilyName);
+            path.AppendPrintf("family=%s", familyName.get());
+        }
+        if (mURI) {
+            nsCString spec;
+            mURI->GetSpec(spec);
+            spec.ReplaceChar('/', '\\');
+            // Some fonts are loaded using horrendously-long data: URIs;
+            // truncate those before reporting them.
+            bool isData;
+            if (NS_SUCCEEDED(mURI->SchemeIs("data", &isData)) && isData &&
+                spec.Length() > 255) {
+                spec.Truncate(252);
+                spec.Append("...");
+            }
+            path.AppendPrintf(", url=%s", spec.get());
+        }
+        if (mPrincipal) {
+            nsCOMPtr<nsIURI> uri;
+            mPrincipal->GetURI(getter_AddRefs(uri));
+            nsCString spec;
+            uri->GetSpec(spec);
+            if (!spec.IsEmpty()) {
+                // Include a clue as to who loaded this resource. (Note that
+                // because of font entry sharing, other pages may now be using
+                // this resource, and the original page may not even be loaded
+                // any longer.)
+                spec.ReplaceChar('/', '\\');
+                path.AppendPrintf(", principal=%s", spec.get());
+            }
+        }
+    }
+    path.Append(')');
+
+    return aCb->
+        Callback(EmptyCString(), path,
+                 nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
+                 mFontEntry->ComputedSizeOfExcludingThis(UserFontsMallocSizeOf),
+                 NS_LITERAL_CSTRING("Memory used by @font-face resource."),
+                 aClosure);
+}
+
+NS_IMPL_ISUPPORTS(gfxUserFontSet::UserFontCache::MemoryReporter,
+                  nsIMemoryReporter)
+
+NS_IMETHODIMP
+gfxUserFontSet::UserFontCache::MemoryReporter::CollectReports(
+    nsIMemoryReporterCallback* aCb, nsISupports* aClosure, bool aAnonymize)
+{
+    if (!sUserFonts) {
+        return NS_OK;
+    }
+
+    for (auto it = sUserFonts->Iter(); !it.Done(); it.Next()) {
+        nsresult rv = it.Get()->ReportMemory(aCb, aClosure, aAnonymize);
+        if (NS_FAILED(rv)) {
+            return rv;
+        }
+    }
+
+    return aCb->
+        Callback(EmptyCString(),
+                 NS_LITERAL_CSTRING("explicit/gfx/user-fonts/cache-overhead"),
+                 nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
+                 sUserFonts->ShallowSizeOfIncludingThis(UserFontsMallocSizeOf),
+                 NS_LITERAL_CSTRING("Memory used by the @font-face cache, "
+                                    "not counting the actual font resources."),
+                 aClosure);
+}
+
 #ifdef DEBUG_USERFONT_CACHE
 
 void
 gfxUserFontSet::UserFontCache::Entry::Dump()
 {
     nsresult rv;
 
     nsAutoCString principalURISpec("(null)");
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -317,16 +317,27 @@ public:
         static gfxFontEntry* GetFont(nsIURI* aSrcURI,
                                      nsIPrincipal* aPrincipal,
                                      gfxUserFontEntry* aUserFontEntry,
                                      bool              aPrivate);
 
         // Clear everything so that we don't leak URIs and Principals.
         static void Shutdown();
 
+        // Memory-reporting support.
+        class MemoryReporter final : public nsIMemoryReporter
+        {
+        private:
+            ~MemoryReporter() { }
+
+        public:
+            NS_DECL_ISUPPORTS
+            NS_DECL_NSIMEMORYREPORTER
+        };
+
 #ifdef DEBUG_USERFONT_CACHE
         // dump contents
         static void Dump();
 #endif
 
     private:
         // Helper that we use to observe the empty-cache notification
         // from nsICacheService.
@@ -432,16 +443,20 @@ public:
 
             enum { ALLOW_MEMMOVE = false };
 
             gfxFontEntry* GetFontEntry() const { return mFontEntry; }
 
             bool IsPersistent() const { return mPersistence == kPersistent; }
             bool IsPrivate() const { return mPrivate; }
 
+            nsresult ReportMemory(nsIMemoryReporterCallback* aCb,
+                                  nsISupports* aClosure,
+                                  bool aAnonymize);
+
 #ifdef DEBUG_USERFONT_CACHE
             void Dump();
 #endif
 
         private:
             static uint32_t
             HashFeatures(const nsTArray<gfxFontFeature>& aFeatures) {
                 return mozilla::HashBytes(aFeatures.Elements(),