Bug 1293145 - Simulate video-decode-suspend for telemetry purposes - r=kamidphish draft
authorGerald Squelart <gsquelart@mozilla.com>
Mon, 08 Aug 2016 10:14:39 +0800
changeset 398477 151e1f1383ab5c445eb8c957be8363340cdc4ab1
parent 398395 274b11f935ec53ce31d8b6cf9b256f11cb9ba702
child 527667 338c638d9d0bc76fe9aa14e1edabb5aaead54c81
push id25537
push usergsquelart@mozilla.com
push dateTue, 09 Aug 2016 07:06:27 +0000
reviewerskamidphish
bugs1293145
milestone51.0a1
Bug 1293145 - Simulate video-decode-suspend for telemetry purposes - r=kamidphish After a video has been playing while hidden for a certain time, count the time until it is not hidden anymore (or it has finished playing), to test-drive how much decoding time would have been saved by the video-decode-suspend feature. Note that this is done inside HTMLMediaElement by simulating what should happen in the MDSM, because instrumenting the MDSM itself and friends would have been harder and more intrusive. MozReview-Commit-ID: LdxhPtmoXeA
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -43,19 +43,20 @@
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 #include "jsapi.h"
 
 #include "nsITimer.h"
 
 #include "MediaError.h"
 #include "MediaDecoder.h"
-#include "nsICategoryManager.h"
+#include "MediaPrefs.h"
 #include "MediaResource.h"
 
+#include "nsICategoryManager.h"
 #include "nsIContentPolicy.h"
 #include "nsContentPolicyUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsICachingChannel.h"
 #include "nsLayoutUtils.h"
 #include "nsVideoFrame.h"
 #include "Layers.h"
 #include <limits>
@@ -2557,16 +2558,20 @@ HTMLMediaElement::~HTMLMediaElement()
   }
   UnregisterActivityObserver();
   if (mDecoder) {
     ShutdownDecoder();
   }
   if (mProgressTimer) {
     StopProgress();
   }
+  if (mVideoDecodeSuspendTimer) {
+    mVideoDecodeSuspendTimer->Cancel();
+    mVideoDecodeSuspendTimer = nullptr;
+  }
   if (mSrcStream) {
     EndSrcMediaStreamPlayback();
   }
 
   if (mCaptureStreamPort) {
     mCaptureStreamPort->Destroy();
     mCaptureStreamPort = nullptr;
   }
@@ -3031,16 +3036,52 @@ nsresult HTMLMediaElement::BindToTree(ns
     // When the MediaElement is binding to tree, the dormant status is
     // aligned to document's hidden status.
     mDecoder->NotifyOwnerActivityChanged(!IsHidden());
   }
 
   return rv;
 }
 
+/* static */
+void HTMLMediaElement::VideoDecodeSuspendTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  auto element = static_cast<HTMLMediaElement*>(aClosure);
+  element->mVideoDecodeSuspendTime.Start();
+  element->mVideoDecodeSuspendTimer = nullptr;
+}
+
+void HTMLMediaElement::HiddenVideoStart()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mHiddenPlayTime.Start();
+  if (mVideoDecodeSuspendTimer) {
+    // Already started, just keep it running.
+    return;
+  }
+  mVideoDecodeSuspendTimer = do_CreateInstance("@mozilla.org/timer;1");
+  mVideoDecodeSuspendTimer->InitWithNamedFuncCallback(
+    VideoDecodeSuspendTimerCallback, this,
+    MediaPrefs::MDSMSuspendBackgroundVideoDelay(), nsITimer::TYPE_ONE_SHOT,
+    "HTMLMediaElement::VideoDecodeSuspendTimerCallback");
+}
+
+void HTMLMediaElement::HiddenVideoStop()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mHiddenPlayTime.Pause();
+  mVideoDecodeSuspendTime.Pause();
+  if (!mVideoDecodeSuspendTimer) {
+    return;
+  }
+  mVideoDecodeSuspendTimer->Cancel();
+  mVideoDecodeSuspendTimer = nullptr;
+}
+
 #ifdef MOZ_EME
 void
 HTMLMediaElement::ReportEMETelemetry()
 {
   // Report telemetry for EME videos when a page is unloaded.
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mIsEncrypted && Preferences::GetBool("media.eme.enabled")) {
     Telemetry::Accumulate(Telemetry::VIDEO_EME_PLAY_SUCCESS, mLoadedDataFired);
@@ -3117,26 +3158,26 @@ HTMLMediaElement::ReportTelemetry()
     }
   }
 
   if (mMediaInfo.HasVideo() &&
       mMediaInfo.mVideo.mImage.height > 0) {
     // We have a valid video.
     double playTime = mPlayTime.Total();
     double hiddenPlayTime = mHiddenPlayTime.Total();
+    double videoDecodeSuspendTime = mVideoDecodeSuspendTime.Total();
 
     Telemetry::Accumulate(Telemetry::VIDEO_PLAY_TIME_MS, SECONDS_TO_MS(playTime));
     LOG(LogLevel::Debug, ("%p VIDEO_PLAY_TIME_MS = %f", this, playTime));
 
     Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_MS, SECONDS_TO_MS(hiddenPlayTime));
     LOG(LogLevel::Debug, ("%p VIDEO_HIDDEN_PLAY_TIME_MS = %f", this, hiddenPlayTime));
 
     if (playTime > 0.0) {
-      // We have actually played something -> Report hidden/total ratio.
-      uint32_t hiddenPercentage = uint32_t(hiddenPlayTime / playTime * 100.0 + 0.5);
+      // We have actually played something -> Report some valid-video telemetry.
 
       // Keyed by audio+video or video alone, and by a resolution range.
       nsCString key(mMediaInfo.HasAudio() ? "AV," : "V,");
       static const struct { int32_t mH; const char* mRes; } sResolutions[] = {
         {  240, "0<h<=240" },
         {  480, "240<h<=480" },
         {  576, "480<h<=576" },
         {  720, "576<h<=720" },
@@ -3148,26 +3189,38 @@ HTMLMediaElement::ReportTelemetry()
       for (const auto& res : sResolutions) {
         if (height <= res.mH) {
           resolution = res.mRes;
           break;
         }
       }
       key.AppendASCII(resolution);
 
+      uint32_t hiddenPercentage = uint32_t(hiddenPlayTime / playTime * 100.0 + 0.5);
       Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE,
                             key,
                             hiddenPercentage);
       // Also accumulate all percentages in an "All" key.
       Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE,
                             NS_LITERAL_CSTRING("All"),
                             hiddenPercentage);
       LOG(LogLevel::Debug, ("%p VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE = %u, keys: '%s' and 'All'",
                             this, hiddenPercentage, key.get()));
 
+      uint32_t videoDecodeSuspendPercentage =
+        uint32_t(videoDecodeSuspendTime / playTime * 100.0 + 0.5);
+      Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE,
+                            key,
+                            videoDecodeSuspendPercentage);
+      Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE,
+                            NS_LITERAL_CSTRING("All"),
+                            videoDecodeSuspendPercentage);
+      LOG(LogLevel::Debug, ("%p VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE = %u, keys: '%s' and 'All'",
+                            this, videoDecodeSuspendPercentage, key.get()));
+
       if (data.mInterKeyframeCount != 0) {
         uint32_t average_ms =
           uint32_t(std::min<uint64_t>(double(data.mInterKeyframeSum_us)
                                       / double(data.mInterKeyframeCount)
                                       / 1000.0
                                       + 0.5,
                                       UINT32_MAX));
         Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS,
@@ -4706,24 +4759,24 @@ nsresult HTMLMediaElement::DispatchAsync
   }
 
   nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this);
   NS_DispatchToMainThread(event);
 
   if ((aName.EqualsLiteral("play") || aName.EqualsLiteral("playing"))) {
     mPlayTime.Start();
     if (IsHidden()) {
-      mHiddenPlayTime.Start();
+      HiddenVideoStart();
     }
   } else if (aName.EqualsLiteral("waiting")) {
     mPlayTime.Pause();
-    mHiddenPlayTime.Pause();
+    HiddenVideoStop();
   } else if (aName.EqualsLiteral("pause")) {
     mPlayTime.Pause();
-    mHiddenPlayTime.Pause();
+    HiddenVideoStop();
   }
 
   return NS_OK;
 }
 
 nsresult HTMLMediaElement::DispatchPendingMediaEvents()
 {
   NS_ASSERTION(!mEventDeliveryPaused,
@@ -4911,20 +4964,20 @@ void HTMLMediaElement::NotifyOwnerDocume
 }
 
 bool
 HTMLMediaElement::NotifyOwnerDocumentActivityChangedInternal()
 {
   bool visible = !IsHidden();
   if (visible) {
     // Visible -> Just pause hidden play time (no-op if already paused).
-    mHiddenPlayTime.Pause();
+    HiddenVideoStop();
   } else if (mPlayTime.IsStarted()) {
     // Not visible, play time is running -> Start hidden play time if needed.
-    mHiddenPlayTime.Start();
+    HiddenVideoStart();
   }
 
   if (mDecoder && !IsBeingDestroyed()) {
     mDecoder->NotifyOwnerActivityChanged(visible);
   }
 
   bool pauseElement = !IsActive();
   SuspendOrResumeElement(pauseElement, !IsActive());
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1120,16 +1120,33 @@ protected:
   // Return true if decoding should be paused
   virtual bool GetPaused() final override
   {
     bool isPaused = false;
     GetPaused(&isPaused);
     return isPaused;
   }
 
+  /**
+   * Video has been playing while hidden and, if feature was enabled, would
+   * trigger suspending decoder.
+   * Used to track hidden-video-decode-suspend telemetry.
+   */
+  static void VideoDecodeSuspendTimerCallback(nsITimer* aTimer, void* aClosure);
+  /**
+   * Video is now both: playing and hidden.
+   * Used to track hidden-video telemetry.
+   */
+  void HiddenVideoStart();
+  /**
+   * Video is not playing anymore and/or has become visible.
+   * Used to track hidden-video telemetry.
+   */
+  void HiddenVideoStop();
+
 #ifdef MOZ_EME
   void ReportEMETelemetry();
 #endif
   void ReportTelemetry();
 
   // Check the permissions for audiochannel.
   bool CheckAudioChannelPermissions(const nsAString& aType);
 
@@ -1370,19 +1387,22 @@ protected:
 
   // Reference to the source element last returned by GetNextSource().
   // This is the child source element which we're trying to load from.
   nsCOMPtr<nsIContent> mSourceLoadCandidate;
 
   // Range of time played.
   RefPtr<TimeRanges> mPlayed;
 
-  // Timer used for updating progress events
+  // Timer used for updating progress events.
   nsCOMPtr<nsITimer> mProgressTimer;
 
+  // Timer used to simulate video-suspend.
+  nsCOMPtr<nsITimer> mVideoDecodeSuspendTimer;
+
 #ifdef MOZ_EME
   // Encrypted Media Extension media keys.
   RefPtr<MediaKeys> mMediaKeys;
 #endif
 
   // Stores the time at the start of the current 'played' range.
   double mCurrentPlayRangeStart;
 
@@ -1631,16 +1651,19 @@ public:
   };
 private:
   // Total time a video has spent playing.
   TimeDurationAccumulator mPlayTime;
 
   // Total time a video has spent playing while hidden.
   TimeDurationAccumulator mHiddenPlayTime;
 
+  // Total time a video has (or would have) spent in video-decode-suspend mode.
+  TimeDurationAccumulator mVideoDecodeSuspendTime;
+
   // Indicates if user has interacted with the element.
   // Used to block autoplay when disabled.
   bool mHasUserInteraction;
 
   // True if the first frame has been successfully loaded.
   bool mFirstFrameLoaded;
 
   // Media elements also have a default playback start position, which must