Bug 1362212 - Move canPlayType telemetry to an idle service observer off main thread. r=gerald
☠☠ backed out by 903d3921e250 ☠ ☠
authorChris Pearce <cpearce@mozilla.com>
Fri, 05 May 2017 13:55:14 +1200
changeset 356975 5f263d6fb16db08d0d7efc73aaccf53240d0b461
parent 356974 a208e1be391020ad135d5dc3add7fd08184cd341
child 356976 ccb5b3503b663283ce238d10db8fc0188dfe9f98
push id31777
push usercbook@mozilla.com
push dateMon, 08 May 2017 08:04:08 +0000
treeherdermozilla-central@81977c96c6ff [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgerald
bugs1362212
milestone55.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 1362212 - Move canPlayType telemetry to an idle service observer off main thread. r=gerald Currently we call HTMLMediaElement.canPlayType() in a JS function called shortly after startup in order to collect telemetry as to how many of our users don't have functioning decoders. Unfortunately, HTMLMediaElement.canPlayType() checks whether we can play a codec by instantiating a decoder, and this requires us to load the system decoding libraries from disk. This requires disk I/O, which can cause jank. We have some BHR reports showing that canPlayType can hang for > 8 seconds to back this up. So move the collection of this telemetry to an idle service observer, so that we only collect this when the user is idle, and do it on a non-main thread so it is less likely to cause jank. MozReview-Commit-ID: HJQawmRxz
dom/media/MediaDecoder.cpp
dom/media/MediaPrefs.h
dom/media/fmp4/MP4Decoder.cpp
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -28,20 +28,25 @@
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 #include "nsPrintfCString.h"
 #include "mozilla/Telemetry.h"
 #include "Layers.h"
 #include "mozilla/layers/ShadowLayers.h"
+#include "nsIIdleService.h"
+#include "MP4Decoder.h"
 
 #ifdef MOZ_ANDROID_OMX
 #include "AndroidBridge.h"
 #endif
+#ifdef XP_WIN
+#include "Objbase.h"
+#endif
 
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 using namespace mozilla::media;
 
 namespace mozilla {
 
 // avoid redefined macro in unified build
@@ -120,20 +125,118 @@ public:
 };
 
 StaticRefPtr<MediaMemoryTracker> MediaMemoryTracker::sUniqueInstance;
 
 LazyLogModule gMediaTimerLog("MediaTimer");
 
 constexpr TimeUnit MediaDecoder::DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED;
 
+class MediaIdleListener : public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+private:
+  virtual ~MediaIdleListener() {}
+};
+
+NS_IMPL_ISUPPORTS(MediaIdleListener, nsIObserver)
+
+const unsigned long sMediaTelemetryIdleSeconds = 30u;
+
+NS_IMETHODIMP
+MediaIdleListener::Observe(nsISupports*,
+                           const char* aTopic,
+                           const char16_t* aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(XRE_IsParentProcess());
+
+  if (!NS_LITERAL_CSTRING(OBSERVER_TOPIC_IDLE).EqualsASCII(aTopic)) {
+    return NS_OK;
+  }
+
+  // Collect telemetry about whether the user's machine can decode AAC
+  // and H.264. We do this off main thread, as determining whether we
+  // can create a decoder requires us to load decoding libraries, which
+  // requires disk I/O, and we don't want to be blocking the main thread
+  // on the chrome process for I/O.
+
+  nsresult rv;
+  nsCOMPtr<nsIIdleService> idleService =
+    do_GetService("@mozilla.org/widget/idleservice;1", &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_OK;
+  }
+
+  RefPtr<MediaIdleListener> self = this;
+  rv = idleService->RemoveIdleObserver(self, sMediaTelemetryIdleSeconds);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_OK;
+  }
+
+  RefPtr<nsIThread> thread;
+  rv = NS_NewNamedThread("MediaTelemetry", getter_AddRefs(thread));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_OK;
+  }
+
+  thread->Dispatch(
+    NS_NewRunnableFunction([thread]() {
+#if XP_WIN
+      // Windows Media Foundation requires MSCOM to be inited.
+      HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
+      MOZ_ASSERT(hr == S_OK);
+#endif
+      bool aac = MP4Decoder::IsSupportedType(
+        MediaContainerType(MEDIAMIMETYPE("audio/mp4")), nullptr);
+      bool h264 = MP4Decoder::IsSupportedType(
+        MediaContainerType(MEDIAMIMETYPE("video/mp4")), nullptr);
+
+      AbstractThread::MainThread()->Dispatch(
+        NS_NewRunnableFunction([thread, aac, h264]() {
+          MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("MediaTelemetry aac=%d h264=%d", aac, h264));
+          Telemetry::Accumulate(
+            Telemetry::HistogramID::VIDEO_CAN_CREATE_AAC_DECODER, aac);
+          Telemetry::Accumulate(
+            Telemetry::HistogramID::VIDEO_CAN_CREATE_H264_DECODER, h264);
+          thread->AsyncShutdown();
+        }));
+    }),
+    NS_DISPATCH_NORMAL);
+
+  return NS_OK;
+}
+
 void
 MediaDecoder::InitStatics()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("%s", __func__));
+
+  if (!XRE_IsParentProcess()) {
+    return;
+  }
+
+  // Add an idle listener so that we can record some telemetry when the
+  // user is idle.
+  nsresult rv;
+  nsCOMPtr<nsIIdleService> idleService =
+    do_GetService("@mozilla.org/widget/idleservice;1", &rv);
+  if (NS_WARN_IF(NS_FAILED((rv)))) {
+    return;
+  }
+
+  RefPtr<MediaIdleListener> listener = new MediaIdleListener();
+  rv = idleService->AddIdleObserver(listener, sMediaTelemetryIdleSeconds);
+  if (NS_WARN_IF(NS_FAILED((rv)))) {
+    return;
+  }
 }
 
 NS_IMPL_ISUPPORTS(MediaMemoryTracker, nsIMemoryReporter)
 
 NS_IMPL_ISUPPORTS0(MediaDecoder)
 
 MediaDecoder::ResourceCallback::ResourceCallback(AbstractThread* aMainThread)
   : mAbstractMainThread(aMainThread)
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -176,16 +176,18 @@ private:
 #endif
 
 #if defined(MOZ_WIDGET_GTK)
   DECL_MEDIA_PREF("media.rust.mp4parser",                     EnableRustMP4Parser, bool, true);
 #else
   DECL_MEDIA_PREF("media.rust.mp4parser",                     EnableRustMP4Parser, bool, false);
 #endif
 
+  DECL_MEDIA_PREF("media.mp4.enabled",                        MP4Enabled, bool, false);
+
   // Error/warning handling, Decoder Doctor
   DECL_MEDIA_PREF("media.playback.warnings-as-errors",        MediaWarningsAsErrors, bool, false);
   DECL_MEDIA_PREF("media.playback.warnings-as-errors.stagefright-vs-rust",
                                                               MediaWarningsAsErrorsStageFrightVsRust, bool, false);
 
 public:
   // Manage the singleton:
   static MediaPrefs& GetSingleton();
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -176,17 +176,17 @@ MP4Decoder::IsAAC(const nsACString& aMim
 {
   return aMimeType.EqualsLiteral("audio/mp4a-latm");
 }
 
 /* static */
 bool
 MP4Decoder::IsEnabled()
 {
-  return Preferences::GetBool("media.mp4.enabled", true);
+  return MediaPrefs::MP4Enabled();
 }
 
 // sTestH264ExtraData represents the content of the avcC atom found in
 // an AVC1 h264 video. It contains the H264 SPS and PPS NAL.
 // the structure of the avcC atom is as follow:
 // write(0x1);  // version, always 1
 // write(sps[0].data[1]); // profile
 // write(sps[0].data[2]); // compatibility