Bug 1685399 - part7 : implement a helper class to accumulate and report the telemetry probe. r=padenot,bryce

In this patch, we move the responsibility of accumulating time and report the telemetry to `TelemetryProbesReporter`.

There are some differences between new telemetry report and the old one.
1. more accuracy on knowing if element is visible
2. more accuracy on determining when it should start accumulating visible & invisible play time
3. being able to report the correct result when element encounts an error or changes to a new resource
4. report result whenever MediaDecoder stops working

Here is the explanation [1] describing why our previous method was not able to achieve those advantages.

[1] https://bugzilla.mozilla.org/show_bug.cgi?id=1685399#c13

Differential Revision: https://phabricator.services.mozilla.com/D101112
This commit is contained in:
alwu
2021-01-19 17:34:47 +00:00
parent 2462eb4c64
commit c8b54c29bb
12 changed files with 517 additions and 345 deletions

View File

@@ -3008,7 +3008,7 @@ MediaResult HTMLMediaElement::LoadResource() {
if (mMediaSource) {
MediaDecoderInit decoderInit(
this, mMuted ? 0.0 : mVolume, mPreservesPitch,
this, this, mMuted ? 0.0 : mVolume, mPreservesPitch,
ClampPlaybackRate(mPlaybackRate),
mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint,
HasAttr(kNameSpaceID_None, nsGkAtoms::loop),
@@ -4235,10 +4235,6 @@ HTMLMediaElement::~HTMLMediaElement() {
if (mProgressTimer) {
StopProgress();
}
if (mVideoDecodeSuspendTimer) {
mVideoDecodeSuspendTimer->Cancel();
mVideoDecodeSuspendTimer = nullptr;
}
if (mSrcStream) {
EndSrcMediaStreamPlayback();
}
@@ -4743,187 +4739,6 @@ nsresult HTMLMediaElement::BindToTree(BindContext& aContext, nsINode& aParent) {
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;
}
NS_NewTimerWithFuncCallback(
getter_AddRefs(mVideoDecodeSuspendTimer), VideoDecodeSuspendTimerCallback,
this, StaticPrefs::media_suspend_bkgnd_video_delay_ms(),
nsITimer::TYPE_ONE_SHOT,
"HTMLMediaElement::VideoDecodeSuspendTimerCallback",
mMainThreadEventTarget);
}
void HTMLMediaElement::HiddenVideoStop() {
MOZ_ASSERT(NS_IsMainThread());
mHiddenPlayTime.Pause();
mVideoDecodeSuspendTime.Pause();
if (!mVideoDecodeSuspendTimer) {
return;
}
mVideoDecodeSuspendTimer->Cancel();
mVideoDecodeSuspendTimer = nullptr;
}
void HTMLMediaElement::ReportTelemetry() {
FrameStatisticsData data;
if (HTMLVideoElement* vid = HTMLVideoElement::FromNodeOrNull(this)) {
FrameStatistics* stats = vid->GetFrameStatistics();
if (stats) {
data = stats->GetFrameStatisticsData();
uint64_t parsedFrames = stats->GetParsedFrames();
if (parsedFrames) {
uint64_t droppedFrames = stats->GetDroppedFrames();
MOZ_ASSERT(droppedFrames <= parsedFrames);
// Dropped frames <= total frames, so 'percentage' cannot be higher than
// 100 and therefore can fit in a uint32_t (that Telemetry takes).
uint32_t percentage = 100 * droppedFrames / parsedFrames;
LOG(LogLevel::Debug,
("Reporting telemetry DROPPED_FRAMES_IN_VIDEO_PLAYBACK"));
Telemetry::Accumulate(Telemetry::VIDEO_DROPPED_FRAMES_PROPORTION,
percentage);
}
}
}
if (mMediaInfo.HasVideo() && mMediaInfo.mVideo.mImage.height > 0 &&
mMediaInfo.mVideo.mImage.width > 0) {
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 (this->IsEncrypted()) {
Telemetry::Accumulate(Telemetry::VIDEO_ENCRYPTED_PLAY_TIME_MS,
SECONDS_TO_MS(playTime));
LOG(LogLevel::Debug,
("%p VIDEO_ENCRYPTED_PLAY_TIME_MS = %f", this, playTime));
}
if (mMediaKeys) {
nsAutoString keySystem;
mMediaKeys->GetKeySystem(keySystem);
if (IsClearkeyKeySystem(keySystem)) {
Telemetry::Accumulate(Telemetry::VIDEO_CLEARKEY_PLAY_TIME_MS,
SECONDS_TO_MS(playTime));
LOG(LogLevel::Debug,
("%p VIDEO_CLEARKEY_PLAY_TIME_MS = %f", this, playTime));
} else if (IsWidevineKeySystem(keySystem)) {
Telemetry::Accumulate(Telemetry::VIDEO_WIDEVINE_PLAY_TIME_MS,
SECONDS_TO_MS(playTime));
LOG(LogLevel::Debug,
("%p VIDEO_WIDEVINE_PLAY_TIME_MS = %f", this, playTime));
}
}
if (playTime > 0.0) {
// 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"},
{1080, "720<h<=1080"}, {2160, "1080<h<=2160"}};
const char* resolution = "h>2160";
int32_t height = mMediaInfo.mVideo.mImage.height;
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,
"All"_ns, 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,
"All"_ns, 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, key,
average_ms);
Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS,
"All"_ns, average_ms);
LOG(LogLevel::Debug,
("%p VIDEO_INTER_KEYFRAME_AVERAGE_MS = %u, keys: '%s' and 'All'",
this, average_ms, key.get()));
uint32_t max_ms = uint32_t(std::min<uint64_t>(
(data.mInterKeyFrameMax_us + 500) / 1000, UINT32_MAX));
Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS, key,
max_ms);
Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS, "All"_ns,
max_ms);
LOG(LogLevel::Debug,
("%p VIDEO_INTER_KEYFRAME_MAX_MS = %u, keys: '%s' and 'All'", this,
max_ms, key.get()));
} else {
// Here, we have played *some* of the video, but didn't get more than 1
// keyframe. Report '0' if we have played for longer than the video-
// decode-suspend delay (showing recovery would be difficult).
uint32_t suspendDelay_ms =
StaticPrefs::media_suspend_bkgnd_video_delay_ms();
if (uint32_t(playTime * 1000.0) > suspendDelay_ms) {
Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS, key, 0);
Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS,
"All"_ns, 0);
LOG(LogLevel::Debug,
("%p VIDEO_INTER_KEYFRAME_MAX_MS = 0 (only 1 keyframe), keys: "
"'%s' and 'All'",
this, key.get()));
}
}
}
}
}
void HTMLMediaElement::UnbindFromTree(bool aNullParent) {
mVisibilityState = Visibility::Untracked;
@@ -5031,7 +4846,7 @@ nsresult HTMLMediaElement::InitializeDecoderAsClone(
AssertReadyStateIsNothing();
MediaDecoderInit decoderInit(
this, mMuted ? 0.0 : mVolume, mPreservesPitch,
this, this, mMuted ? 0.0 : mVolume, mPreservesPitch,
ClampPlaybackRate(mPlaybackRate),
mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint,
HasAttr(kNameSpaceID_None, nsGkAtoms::loop), aOriginal->ContainerType());
@@ -5108,7 +4923,7 @@ nsresult HTMLMediaElement::InitializeDecoderForChannel(
}
MediaDecoderInit decoderInit(
this, mMuted ? 0.0 : mVolume, mPreservesPitch,
this, this, mMuted ? 0.0 : mVolume, mPreservesPitch,
ClampPlaybackRate(mPlaybackRate),
mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint,
HasAttr(kNameSpaceID_None, nsGkAtoms::loop), *containerType);
@@ -6354,19 +6169,6 @@ void HTMLMediaElement::DispatchAsyncEvent(const nsAString& aName) {
}
mMainThreadEventTarget->Dispatch(event.forget());
if ((aName.EqualsLiteral("play") || aName.EqualsLiteral("playing"))) {
mPlayTime.Start();
if (IsActuallyInvisible()) {
HiddenVideoStart();
}
} else if (aName.EqualsLiteral("waiting")) {
mPlayTime.Pause();
HiddenVideoStop();
} else if (aName.EqualsLiteral("pause")) {
mPlayTime.Pause();
HiddenVideoStop();
}
}
nsresult HTMLMediaElement::DispatchPendingMediaEvents() {
@@ -6503,8 +6305,6 @@ void HTMLMediaElement::SuspendOrResumeElement(bool aSuspendElement) {
UpdateAudioChannelPlayingState();
if (aSuspendElement) {
ReportTelemetry();
if (mDecoder) {
mDecoder->Pause();
mDecoder->Suspend();
@@ -6559,15 +6359,6 @@ bool HTMLMediaElement::ShouldBeSuspendedByInactiveDocShell() const {
}
void HTMLMediaElement::NotifyOwnerDocumentActivityChanged() {
bool visible = !IsActuallyInvisible();
if (visible) {
// Visible -> Just pause hidden play time (no-op if already paused).
HiddenVideoStop();
} else if (mPlayTime.IsStarted()) {
// Not visible, play time is running -> Start hidden play time if needed.
HiddenVideoStart();
}
if (mDecoder && !IsBeingDestroyed()) {
NotifyDecoderActivityChanges();
}
@@ -6927,27 +6718,6 @@ void HTMLMediaElement::OnVisibilityChange(Visibility aNewVisibility) {
if (!mDecoder) {
return;
}
switch (aNewVisibility) {
case Visibility::Untracked: {
MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
return;
}
case Visibility::ApproximatelyNonVisible: {
if (mPlayTime.IsStarted()) {
// Not visible, play time is running -> Start hidden play time if
// needed.
HiddenVideoStart();
}
break;
}
case Visibility::ApproximatelyVisible: {
// Visible -> Just pause hidden play time (no-op if already paused).
HiddenVideoStop();
break;
}
}
NotifyDecoderActivityChanges();
}
@@ -7349,6 +7119,12 @@ void HTMLMediaElement::SetMediaInfo(const MediaInfo& aInfo) {
UpdateWakeLock();
}
MediaInfo HTMLMediaElement::GetMediaInfo() const { return mMediaInfo; }
FrameStatistics* HTMLMediaElement::GetFrameStatistics() const {
return mDecoder ? &(mDecoder->GetFrameStatistics()) : nullptr;
}
void HTMLMediaElement::AudioCaptureTrackChange(bool aCapture) {
// No need to capture a silent media element.
if (!HasAudio()) {
@@ -7524,6 +7300,15 @@ bool HTMLMediaElement::IsAudible() const {
return mIsAudioTrackAudible;
}
Maybe<nsAutoString> HTMLMediaElement::GetKeySystem() const {
if (!mMediaKeys) {
return Nothing();
}
nsAutoString keySystem;
mMediaKeys->GetKeySystem(keySystem);
return Some(keySystem);
}
void HTMLMediaElement::ConstructMediaTracks(const MediaInfo* aInfo) {
if (!aInfo) {
return;