Report each surface in the ImageLib SurfaceCache individually in about:memory

RESOLVED FIXED in Firefox 40



3 years ago
3 years ago


(Reporter: seth, Assigned: seth)



Firefox Tracking Flags

(firefox40 fixed)


(Whiteboard: [MemShrink:P1])


(1 attachment, 2 obsolete attachments)



3 years ago
Right now we only report the total amount of memory used by an image in the SurfaceCache (though we separate the number into heap vs nonheap).

It'd be much better to report each surface individually. That would make debugging an issue like the one in bug 1157559 very straightforward.

Comment 1

3 years ago
Created attachment 8597568 [details] [diff] [review]
Report each surface in the ImageLib SurfaceCache individually in about:memory

Wow, this turned into a pretty big patch for what seems like a relatively small

The main idea here is that instead of recording all memory counters on an
ImageMemoryCounter, we add an intermediate type called a SurfaceMemoryCounter
that records the counters for each surface. ImageMemoryCounter then just records
totals. As with ImageMemoryCounter, SurfaceMemoryCounter also tracks a little
bit of metadata: for surfaces, we track their size, whether they're locked,
their animation time, and their decode flags. That allows us to report enough
information to diagnose memory issues involving the SurfaceCache, especially
those involving HQ scaling, downscale-during-decode, or animation.

We also track one more piece of information: the surface type. This only exists
to let us flag the compositing surfaces we use in FrameAnimator as something
special. (Those are also the only surfaces that we don't store in the

The about:memory output places "locked/" and "unlocked/" sections under each
image. Under each of those sections is a list of surfaces, identified by the
metadata mentioned above. The surfaces are then broken down individually into
"decoded-heap", "decoded-nonheap", and the rest as usual.

The common case is that images only have one surface, so this doesn't bloat the
output much. When images *do* have many surfaces, we of course end up reporting
a lot more information after this patch, but that's the point: this information
will help tracking down memory issues that currently require custom logging to
accurately diagnose. This should make life easier for me, and I think also for
MemShrink folks.
Attachment #8597568 - Flags: review?(dholbert)
Attachment #8597568 - Flags: feedback?(erahm)
Comment on attachment 8597568 [details] [diff] [review]
Report each surface in the ImageLib SurfaceCache individually in about:memory

Review of attachment 8597568 [details] [diff] [review]:

As discussed in IRC, I think we want to keep 'SizeOf' somewhere in the function names of things that use |MallocSizeOf|.

Seth, is it possible for you to paste a snippet of the new about:memory output as well?

::: image/src/Image.h
@@ +53,5 @@
> +  MemoryCounter& operator-=(const MemoryCounter& aOther)
> +  {
> +    mSource += aOther.mSource;
> +    mDecodedHeap += aOther.mDecodedHeap;
> +    mDecodedNonHeap += aOther.mDecodedNonHeap;

Looks like a copy and paste failure here.

::: image/src/imgLoader.cpp
@@ +230,5 @@
> +                                 const ImageMemoryCounter& aCounter)
> +  {
> +    nsresult rv;
> +
> +    for (const SurfaceMemoryCounter counter : aCounter.Surfaces()) {

const SurfaceMemoryCounter& ?
Attachment #8597568 - Flags: feedback?(erahm) → feedback+
Comment on attachment 8597568 [details] [diff] [review]
Report each surface in the ImageLib SurfaceCache individually in about:memory

Review of attachment 8597568 [details] [diff] [review]:

::: image/src/DynamicImage.cpp
@@ +48,2 @@
>  {
> +  // We can't do anything useful because gfxDrawable doesn't expose this

Maybe s/do/report/?
(i.e. "We can't report anything useful because...")

::: image/src/FrameAnimator.cpp
@@ +345,5 @@
> +}
> +
> +void
> +FrameAnimator::
> +  ReportCompositingSurfaceMemory(nsTArray<SurfaceMemoryCounter>& aCounters,

Stylistic nit: this particular break-and-deindent strategy (which I have myself used in the past) is actually an antipattern, because it breaks searchability.  If you're trying to find this funtion-impl in MXR, you'd rightfully expect to be able to search for "FrameAnimator::ReportCompositingSurfaceMemory", but that won't work if you break after the ::.

So, better to break-and-deindent after the open-paren, like so:

  nsTArray<SurfaceMemoryCounter>& aCounters,
  MallocSizeOf aMallocSizeOf) const

(Mats pointed this out to me during a review a little while back, and I agree with him.)

::: image/src/Image.h
@@ +91,5 @@
> +private:
> +  const SurfaceKey mKey;
> +  MemoryCounter mValues;
> +  SurfaceMemoryCounterType mType;
> +  bool mIsLocked;

Looks like mType & mIsLocked could be declared as const. (Their values are assigned in the init list, and they're not modified after that.)

(It doesn't matter too much, since this is just a simple struct; I'm only mentioning it because you've got one thing already labeled as const here, which sort of implies that the rest of the variables are modified -- but really only one of them is.)

@@ +129,5 @@
> +  nsCString mURI;
> +  nsTArray<SurfaceMemoryCounter> mSurfaces;
> +  uint16_t mType;
> +  MemoryCounter mValues;
> +  bool mIsUsed : 1;

Nit: seems likely that this will pack better if you move "uint16_t mType" down by one line, to make it adjacent to the bitfield bools.

::: image/src/SurfaceCache.cpp
@@ +164,5 @@
>    Lifetime GetLifetime() const { return mLifetime; }
>    bool IsDecoded() const { return mSurface->IsImageComplete(); }
> +  // A helper type used by SurfaceCacheImpl::ReportSurfaceMemory.
> +  struct SurfaceMemoryReport

Add MOZ_STACK_CLASS after "struct" here.

(This is only ever used on the stack, as a helper-object for use during hashtable enumeration.  And this is important to enforce, because this object retains a (C++) reference to a nsTArray whose lifetime it doesn't control.  So, this object had *better* be stack-only, or it might accidentally outlive that nsTArray and trigger a use-after-free bug.)

::: image/src/imgLoader.cpp
@@ +228,5 @@
> +                                 nsISupports* aData,
> +                                 const nsACString& aPathPrefix,
> +                                 const ImageMemoryCounter& aCounter)
> +  {
> +    nsresult rv;

You only use this variable inside the 'for' loop. Just declare it at the line where it's used.

@@ +390,5 @@
>      nsAutoCString spec;
>      imageURL->GetSpec(spec);
>      ImageMemoryCounter counter(image->GetType(), spec, aIsUsed);
> +    counter.Init(image, ImagesMallocSizeOf);

(I'd suggest collapsing "Init()" into the constructor.  Generally, I think Init() methods make sense when there's a potential for something to fail, or when there's some processing we need to do between construction & initialization.  But neither of those things are true here, so I don't see any reason for the division.  Note that combining these would let us get rid of the mInitialized bool, which is a small complexity win.

This is a minor point; I'm OK keeping it as-is, too, if you like having them split up for some reason.)
Attachment #8597568 - Flags: review?(dholbert) → review+

Comment 4

3 years ago
Thanks for the feedback and review comments, everyone! I'll post an updated version of the patch shortly.

In addition to addressing the comments here, it'll address a few other issues I noticed, and will tweak the output format a bit.

(In reply to Eric Rahm [:erahm] from comment #2)
> Seth, is it possible for you to paste a snippet of the new about:memory
> output as well?

Sure. Here's a snippet of the new output format for

├────9.11 MB (04.61%) -- images
│    ├──8.03 MB (04.06%) -- content/raster/used
│    │  ├──0.55 MB (00.28%) ++ image(480x300, moz-page-thumb://thumbnail?
│    │  ├──0.25 MB (00.13%) ++ image(728x90,
│    │  ├──0.17 MB (00.09%) ++ image(300x150,
│    │  ├──0.12 MB (00.06%) ++ image(300x100,

(It's not present in this example, but images *can* have both locked and unlocked surfaces. Downscale-during-decode makes that much more common.)

Here's another example, for some animated GIF I found on reddit, because some aspects of the output are only relevant for animated images:

│   │  │  └──10.97 MB (04.06%) -- image(400x300,
│   │  │     ├───0.46 MB (00.17%) ++ surface(400x300, compositing frame)
│   │  │     ├───0.46 MB (00.17%) ++ surface(400x300@0)
│   │  │     ├───0.12 MB (00.04%) ── surface(400x300@1)/decoded-heap
│   │  │     ├───0.12 MB (00.04%) ── surface(400x300@149)/decoded-heap
│   │  │     ├───0.09 MB (00.03%) ── surface(299x298 subframe of 400x300@69)/decoded-heap
│   │  │     ├───0.09 MB (00.03%) ── surface(299x299 subframe of 400x300@132)/decoded-heap

In "400x300@132", the "132" is a time value. (Meaning, for raster images, a frame number.)

Comment 5

3 years ago
Hmm. It didn't occur to me that bugzilla would linkify those URIs. I apologize to the people running those sites for any additional traffic. =\

Comment 6

3 years ago
Created attachment 8598360 [details] [diff] [review]
Report each surface in the ImageLib SurfaceCache individualy in about:memory

Here's the updated version of the patch. It should address all of the comments
so far.

This version of the patch also includes a few changes to formatting and other
similar small nits.

Most importantly, this version changes the output format to the one shown in
comment 4. It should convey all of the same information as the original patch,
while being a bit easier to read and a bit less verbose in the common case. It
also reports one new piece of information: when animated images frames do not
cover the complete area of the image, it reports the subframe size. I included
this because it's important for understanding memory usage.

Comment 7

3 years ago
Oh, I just realized that I also added reporting of each image's intrinsic size in the new version of the patch. Again, you can see an example in comment 4.
Comment on attachment 8598360 [details] [diff] [review]
Report each surface in the ImageLib SurfaceCache individualy in about:memory

One nit on the new patch:

>+++ b/image/src/imgLoader.cpp
>@@ -408,26 +401,19 @@ private:
>     if (!image) {
>       return;
>     }
>     nsRefPtr<ImageURL> imageURL(image->GetURI());
>     nsAutoCString spec;
>     imageURL->GetSpec(spec);
>-    ImageMemoryCounter counter(image->GetType(), spec, aIsUsed);
>+    ImageMemoryCounter counter(image, ImagesMallocSizeOf, aIsUsed);
>+    aArray->AppendElement(Move(counter));
>   }

Looks like the variables "imageURL" and "spec" here are unused now, & should be removed. (You look them up inside of the ImageMemoryCounter constructor now, so they're not needed here anymore.)


3 years ago
Attachment #8597568 - Attachment is obsolete: true


3 years ago
Attachment #8598360 - Attachment is obsolete: true

Comment 9

3 years ago
Created attachment 8598371 [details] [diff] [review]
Report each surface in the ImageLib SurfaceCache individually in about:memory

Thanks for taking another look, Daniel! I've removed those lines. Here's the
final version of the patch.
Last Resolved: 3 years ago
status-firefox40: --- → fixed
Resolution: --- → FIXED
Target Milestone: --- → mozilla40


3 years ago
Whiteboard: [MemShrink:P1]
You need to log in before you can comment on or make changes to this bug.