Bug 1492930 - Part 4. Add ImageMemoryReporter to support extra shared surfaces reporting. r=tnikkel
☠☠ backed out by 597019fb23d9 ☠ ☠
authorAndrew Osmond <aosmond@mozilla.com>
Tue, 25 Sep 2018 06:18:06 -0400
changeset 438154 2959314ecf7caaf5def4a05e280c732c5365027b
parent 438153 587e01daa080af33c43fe71edf307e02c5a97eb2
child 438155 bdb1bf2d8062dc54fceaa06c9a032ea6c12489e4
push id34711
push useraciure@mozilla.com
push dateTue, 25 Sep 2018 21:49:34 +0000
treeherdermozilla-central@2e3e89c9c68c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstnikkel
bugs1492930
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1492930 - Part 4. Add ImageMemoryReporter to support extra shared surfaces reporting. r=tnikkel By delegating responsibility for shared surfaces reporting to imagelib, we can cross reference the GPU shared surfaces cache with the local surface cache in a content process (or the main process). This will allow us to identify entries that are in the GPU cache but not in the content/main process cache, and aid in debugging memory leaks. This functionality is pref'd off by default behind image.mem.debug-reporting. Additionally, we want to report every entry that was mapped into the compositor process, in the compositor process memory report. This will give us a sense of how much of our resident memory is consumed by mapped images in absence of the more detailed cross referencing above.
gfx/thebes/gfxPrefs.h
image/ImageMemoryReporter.cpp
image/ImageMemoryReporter.h
image/moz.build
modules/libpref/init/all.js
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -552,16 +552,17 @@ private:
   DECL_GFX_PREF(Live, "image.decode-immediately.enabled",      ImageDecodeImmediatelyEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, true);
   DECL_GFX_PREF(Live, "image.infer-src-animation.threshold-ms", ImageInferSrcAnimationThresholdMS, uint32_t, 2000);
   DECL_GFX_PREF(Live, "image.layout_network_priority",         ImageLayoutNetworkPriority, bool, true);
   DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
   DECL_GFX_PREF(Live, "image.mem.discardable",                 ImageMemDiscardable, bool, false);
   DECL_GFX_PREF(Once, "image.mem.animated.discardable",        ImageMemAnimatedDiscardable, bool, false);
   DECL_GFX_PREF(Live, "image.mem.animated.use_heap",           ImageMemAnimatedUseHeap, bool, false);
+  DECL_GFX_PREF(Live, "image.mem.debug-reporting",             ImageMemDebugReporting, bool, false);
   DECL_GFX_PREF(Live, "image.mem.shared",                      ImageMemShared, bool, true);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb",    ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
   DECL_GFX_PREF(Live, "image.mem.volatile.min_threshold_kb",   ImageMemVolatileMinThresholdKB, int32_t, -1);
   DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
   DECL_GFX_PREF(Once, "image.multithreaded_decoding.idle_timeout", ImageMTDecodingIdleTimeout, int32_t, -1);
new file mode 100644
--- /dev/null
+++ b/image/ImageMemoryReporter.cpp
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ImageMemoryReporter.h"
+#include "Image.h"
+#include "mozilla/layers/SharedSurfacesParent.h"
+#include "nsIMemoryReporter.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace image {
+
+ImageMemoryReporter::WebRenderReporter* ImageMemoryReporter::sWrReporter;
+ 
+class ImageMemoryReporter::WebRenderReporter final : public nsIMemoryReporter
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  WebRenderReporter()
+  { }
+
+  NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+                            nsISupports* aData, bool aAnonymize) override
+  {
+    layers::SharedSurfacesMemoryReport report;
+    layers::SharedSurfacesParent::AccumulateMemoryReport(report);
+    ReportSharedSurfaces(aHandleReport, aData, /* aIsForCompositor */ true, report);
+    return NS_OK;
+  }
+
+private:
+  virtual ~WebRenderReporter()
+  { }
+};
+
+NS_IMPL_ISUPPORTS(ImageMemoryReporter::WebRenderReporter, nsIMemoryReporter)
+
+/* static */ void
+ImageMemoryReporter::InitForWebRender()
+{
+  MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsGPUProcess());
+  if (!sWrReporter) {
+    sWrReporter = new WebRenderReporter();
+    RegisterStrongMemoryReporter(sWrReporter);
+  }
+}
+
+/* static */ void
+ImageMemoryReporter::ShutdownForWebRender()
+{
+  MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsGPUProcess());
+  if (sWrReporter) {
+    UnregisterStrongMemoryReporter(sWrReporter);
+    sWrReporter = nullptr;
+  }
+}
+
+/* static */ void
+ImageMemoryReporter::ReportSharedSurfaces(nsIHandleReportCallback* aHandleReport,
+                                          nsISupports* aData,
+                                          const layers::SharedSurfacesMemoryReport& aSharedSurfaces)
+{
+  ReportSharedSurfaces(aHandleReport, aData,
+                       /* aIsForCompositor */ false,
+                       aSharedSurfaces);
+}
+
+/* static */ void
+ImageMemoryReporter::ReportSharedSurfaces(nsIHandleReportCallback* aHandleReport,
+                                          nsISupports* aData,
+                                          bool aIsForCompositor,
+                                          const layers::SharedSurfacesMemoryReport& aSharedSurfaces)
+{
+  MOZ_ASSERT_IF(aIsForCompositor, XRE_IsParentProcess() || XRE_IsGPUProcess());
+  MOZ_ASSERT_IF(!aIsForCompositor,
+                XRE_IsParentProcess() || XRE_IsContentProcess());
+
+  for (auto i = aSharedSurfaces.mSurfaces.begin();
+    i != aSharedSurfaces.mSurfaces.end(); ++i) {
+    ReportSharedSurface(aHandleReport, aData, aIsForCompositor, i->first, i->second);
+  }
+}
+
+/* static */ void
+ImageMemoryReporter::ReportSharedSurface(nsIHandleReportCallback* aHandleReport,
+                                         nsISupports* aData,
+                                         bool aIsForCompositor,
+                                         uint64_t aExternalId,
+                                         const layers::SharedSurfacesMemoryReport::SurfaceEntry& aEntry)
+{
+  nsAutoCString path;
+  if (aIsForCompositor) {
+    path.AppendLiteral("gfx/webrender/images/mapped_from_owner/");
+  } else {
+    path.AppendLiteral("gfx/webrender/images/owner_cache_missing/");
+  }
+
+  if (aIsForCompositor) {
+    path.AppendLiteral("pid=");
+    path.AppendInt(aEntry.mCreatorPid);
+    path.AppendLiteral("/");
+  }
+
+  if (gfxPrefs::ImageMemDebugReporting()) {
+    path.AppendPrintf("%016lx/", aExternalId);
+  }
+
+  path.AppendLiteral("image(");
+  path.AppendInt(aEntry.mSize.width);
+  path.AppendLiteral("x");
+  path.AppendInt(aEntry.mSize.height);
+  path.AppendLiteral(", compositor_ref:");
+  path.AppendInt(aEntry.mConsumers);
+  path.AppendLiteral(", creator_ref:");
+  path.AppendInt(aEntry.mCreatorRef);
+  path.AppendLiteral(")/decoded-nonheap");
+
+  size_t surfaceSize =
+    mozilla::ipc::SharedMemory::PageAlignedSize(aEntry.mSize.height *
+                                                aEntry.mStride);
+
+  // If this memory has already been reported elsewhere (e.g. as part of our
+  // explicit section in the surface cache), we don't want report it again as
+  // KIND_NONHEAP and have it counted again.
+  bool sameProcess = aEntry.mCreatorPid == base::GetCurrentProcId();
+  int32_t kind = aIsForCompositor && !sameProcess
+               ? nsIMemoryReporter::KIND_NONHEAP
+               : nsIMemoryReporter::KIND_OTHER;
+
+  NS_NAMED_LITERAL_CSTRING(desc, "Decoded image data stored in shared memory.");
+  aHandleReport->Callback(EmptyCString(), path, kind,
+                          nsIMemoryReporter::UNITS_BYTES,
+                          surfaceSize, desc, aData);
+}
+
+/* static */ void
+ImageMemoryReporter::AppendSharedSurfacePrefix(nsACString& aPathPrefix,
+                                               const SurfaceMemoryCounter& aCounter,
+                                               layers::SharedSurfacesMemoryReport& aSharedSurfaces)
+{
+  uint64_t extId = aCounter.Values().ExternalId();
+  if (extId) {
+    auto gpuEntry = aSharedSurfaces.mSurfaces.find(extId);
+
+    if (gfxPrefs::ImageMemDebugReporting()) {
+      aPathPrefix.AppendPrintf(", external_id:%016lx", extId);
+      if (gpuEntry != aSharedSurfaces.mSurfaces.end()) {
+        aPathPrefix.AppendLiteral(", compositor_ref:");
+        aPathPrefix.AppendInt(gpuEntry->second.mConsumers);
+      } else {
+        aPathPrefix.AppendLiteral(", compositor_ref:missing");
+      }
+    }
+
+    if (gpuEntry != aSharedSurfaces.mSurfaces.end()) {
+      MOZ_ASSERT(gpuEntry->second.mCreatorRef);
+      aSharedSurfaces.mSurfaces.erase(gpuEntry);
+    }
+  }
+}
+
+/* static */ void
+ImageMemoryReporter::TrimSharedSurfaces(const ImageMemoryCounter& aCounter,
+                                        layers::SharedSurfacesMemoryReport& aSharedSurfaces)
+{
+  if (aSharedSurfaces.mSurfaces.empty()) {
+    return;
+  }
+
+  for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) {
+    uint64_t extId = counter.Values().ExternalId();
+    if (extId) {
+      auto gpuEntry = aSharedSurfaces.mSurfaces.find(extId);
+      if (gpuEntry != aSharedSurfaces.mSurfaces.end()) {
+        MOZ_ASSERT(gpuEntry->second.mCreatorRef);
+        aSharedSurfaces.mSurfaces.erase(gpuEntry);
+      }
+    }
+  }
+}
+
+} // image
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/image/ImageMemoryReporter.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_image_ImageMemoryReporter_h
+#define mozilla_image_ImageMemoryReporter_h
+
+#include <cstdint>
+#include "nsString.h"
+#include "mozilla/layers/SharedSurfacesMemoryReport.h"
+
+class nsISupports;
+class nsIHandleReportCallback;
+
+namespace mozilla {
+namespace image {
+struct ImageMemoryCounter;
+struct SurfaceMemoryCounter;
+
+class ImageMemoryReporter final
+{
+public:
+  /**
+   * Initializes image related memory reporting in the compositor process when
+   * using WebRender.
+   */
+  static void InitForWebRender();
+
+  /**
+   * Tears down image related memory reporting in the compositor process when
+   * using WebRender.
+   */
+  static void ShutdownForWebRender();
+
+  /**
+   * Report all remaining entries in the shared surface's memory report. This
+   * should be used by the content or main process to allow reporting any
+   * entries that is was unable to cross reference with the local surface cache.
+   * These are candidates for having been leaked. This should be used in
+   * conjunction with AppendSharedSurfacePrefix and/or TrimSharedSurfaces to
+   * produce the expected result.
+   */
+  static void ReportSharedSurfaces(nsIHandleReportCallback* aHandleReport,
+                                   nsISupports* aData,
+                                   const layers::SharedSurfacesMemoryReport& aSharedSurfaces);
+
+  /**
+   * Adjust the path prefix for a surface to include any additional metadata for
+   * the shared surface, if any. It will also remove any corresponding entries
+   * in the given memory report.
+   */
+  static void AppendSharedSurfacePrefix(nsACString& aPathPrefix,
+                                        const SurfaceMemoryCounter& aCounter,
+                                        layers::SharedSurfacesMemoryReport& aSharedSurfaces);
+
+  /**
+   * Remove all entries in the memory report for the given set of surfaces for
+   * an image. This is useful when we aren't reporting on a particular image
+   * because it isn't notable.
+   */
+  static void TrimSharedSurfaces(const ImageMemoryCounter& aCounter,
+                                 layers::SharedSurfacesMemoryReport& aSharedSurfaces);
+
+private:
+  /**
+   * Report all remaining entries in the shared surface's memory report.
+   *
+   * aIsForCompositor controls how to intepret what remains in the report. If
+   * true, this should mirror exactly what is currently in
+   * SharedSurfacesParent's cache. This will report entries that are currently
+   * mapped into the compositor process. If false, then we are in a content or
+   * main process, and it should have removed entries that also exist in its
+   * local surface cache -- thus any remaining entries are those that are
+   * candidates for leaks.
+   */
+  static void ReportSharedSurfaces(nsIHandleReportCallback* aHandleReport,
+                                   nsISupports* aData,
+                                   bool aIsForCompositor,
+                                   const layers::SharedSurfacesMemoryReport& aSharedSurfaces);
+
+  static void ReportSharedSurface(nsIHandleReportCallback* aHandleReport,
+                                  nsISupports* aData,
+                                  bool aIsForCompositor,
+                                  uint64_t aExternalId,
+                                  const layers::SharedSurfacesMemoryReport::SurfaceEntry& aEntry);
+
+  class WebRenderReporter;
+  static WebRenderReporter* sWrReporter;
+};
+
+} // image
+} // mozilla
+
+#endif // mozilla_image_ImageMemoryReporter_h
--- a/image/moz.build
+++ b/image/moz.build
@@ -48,31 +48,36 @@ EXPORTS += [
     'imgLoader.h',
     'imgRequest.h',
     'imgRequestProxy.h',
     'IProgressObserver.h',
     'Orientation.h',
     'SurfaceCacheUtils.h',
 ]
 
+EXPORTS.mozilla.image += [
+    'ImageMemoryReporter.h',
+]
+
 UNIFIED_SOURCES += [
     'AnimationFrameBuffer.cpp',
     'AnimationSurfaceProvider.cpp',
     'ClippedImage.cpp',
     'DecodedSurfaceProvider.cpp',
     'DecodePool.cpp',
     'Decoder.cpp',
     'DecoderFactory.cpp',
     'DynamicImage.cpp',
     'FrameAnimator.cpp',
     'FrozenImage.cpp',
     'IDecodingTask.cpp',
     'Image.cpp',
     'ImageCacheKey.cpp',
     'ImageFactory.cpp',
+    'ImageMemoryReporter.cpp',
     'ImageOps.cpp',
     'ImageWrapper.cpp',
     'imgFrame.cpp',
     'imgLoader.cpp',
     'imgRequest.cpp',
     'imgRequestProxy.cpp',
     'imgTools.cpp',
     'MultipartImage.cpp',
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4670,16 +4670,19 @@ pref("image.mem.animated.discardable", t
 // Whether the heap should be used for frames from animated images. On Android,
 // volatile memory keeps file handles open for each buffer.
 #if defined(ANDROID)
 pref("image.mem.animated.use_heap", true);
 #else
 pref("image.mem.animated.use_heap", false);
 #endif
 
+// Enable extra information for debugging in the image memory reports.
+pref("image.mem.debug-reporting", false);
+
 // Decodes images into shared memory to allow direct use in separate
 // rendering processes. Only applicable with WebRender.
 pref("image.mem.shared", true);
 
 // Allows image locking of decoded image data in content processes.
 pref("image.mem.allow_locking_in_content_processes", true);
 
 // Chunk size for calls to the image decoders