Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(377)

Unified Diff: third_party/WebKit/Source/core/html/shadow/MediaControlTimelineMetrics.cpp

Issue 2779273003: [Media Controls] Add UMA for timeline scrubber (Closed)
Patch Set: Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/core/html/shadow/MediaControlTimelineMetrics.cpp
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControlTimelineMetrics.cpp b/third_party/WebKit/Source/core/html/shadow/MediaControlTimelineMetrics.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2259a18cf0e5c48910b5c8a8168514d864d6aac3
--- /dev/null
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControlTimelineMetrics.cpp
@@ -0,0 +1,340 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/html/shadow/MediaControlTimelineMetrics.h"
+
+#include <stdint.h>
+#include <cmath>
+#include <limits>
+#include "platform/KeyboardCodes.h"
+#include "platform/wtf/StdLibExtras.h"
+
+namespace blink {
+
+namespace {
+
+// Correponds to UMA MediaTimelineSeekType enum. Enum values can be added, but
+// must never be renumbered or deleted and reused.
+enum class SeekType {
+ kClick = 0,
+ kDragFromCurrentPosition = 1,
+ kDragFromElsewhere = 2,
+ kKeyboardArrowKey = 3,
+ kKeyboardPageUpDownKey = 4,
+ kKeyboardHomeEndKey = 5,
+ // Update kLast when adding new values.
+ kLast = kKeyboardHomeEndKey
+};
+
+// Inclusive upper bounds for the positive buckets of UMA MediaTimelinePercent
+// enum, which are reflected to form the negative buckets. Values must not be
+// added, modified, or removed.
+constexpr double kPercentIntervals[] = {
+ 0, 0.1, 0.2, 0.3, 0.5, 0.7, 1.0, 1.5, 2.0,
+ 3.0, 5.0, 7.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0,
+ 40.0, 45.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0};
+constexpr int32_t kPercentBucketCount = 51;
+static_assert(arraysize(kPercentIntervals) * 2 == 1 + kPercentBucketCount,
+ "Intervals must match UMA MediaTimelinePercent enum");
+
+// Corresponds two UMA enums! Values are the inclusive upper bounds for buckets
+// of UMA MediaTimelineAbsTimeDelta enum with the same index, and also for the
+// positive buckets of UMA MediaTimelineTimeDelta enum, which are reflected to
+// form the negative buckets. Values must not be added, modified, or removed.
+constexpr double kTimeDeltaMSIntervals[] = {
+ 0, // 0
+ 16, // 16ms
+ 32, // 32ms
+ 64, // 64ms
+ 128, // 128ms
+ 256, // 256ms
+ 512, // 512ms
+ 1000, // 1s
+ 2000, // 2s
+ 4000, // 4s
+ 8000, // 8s
+ 15000, // 15s
+ 30000, // 30s
+ 60000, // 1m
+ 120000, // 2m
+ 240000, // 4m
+ 480000, // 8m
+ 900000, // 15m
+ 1800000, // 30m
+ 3600000, // 1h
+ 7200000, // 2h
+ 14400000, // 4h
+ 28800000, // 8h
+ 57600000, // 16h
+ std::numeric_limits<double>::infinity()};
+constexpr int32_t kAbsTimeDeltaBucketCount = 25;
+constexpr int32_t kTimeDeltaBucketCount = 49;
mlamouri (slow - plz ping) 2017/03/31 11:39:40 (kAbsTimeDeltaBucketCount * 2) - 1
johnme 2017/03/31 13:47:23 I'd rather not compute this, because it's potentia
+static_assert(arraysize(kTimeDeltaMSIntervals) == kAbsTimeDeltaBucketCount,
+ "Intervals must match UMA MediaTimelineAbsTimeDelta enum");
+static_assert(arraysize(kTimeDeltaMSIntervals) * 2 == 1 + kTimeDeltaBucketCount,
+ "Intervals must match UMA MediaTimelineTimeDelta enum");
mlamouri (slow - plz ping) 2017/03/31 11:39:40 If you do the change above, it would make this sta
johnme 2017/03/31 13:47:23 Ack. See above though.
+
+int32_t toPercentSample(double percent) {
+ constexpr int32_t negativeBucketCount = arraysize(kPercentIntervals) - 1;
+ bool negative = percent < 0;
+ double absPercent = std::abs(percent);
+ for (int32_t i = 0; i < static_cast<int>(arraysize(kPercentIntervals)); i++) {
+ if (absPercent <= kPercentIntervals[i])
+ return negativeBucketCount + (negative ? -i : +i);
+ }
+ // No NOTREACHED since percent may exit -100..100 due to floating point error.
+ return negative ? 0 : kPercentBucketCount - 1;
+}
+
+int32_t toAbsTimeDeltaSample(double sumAbsDeltaSeconds) {
+ double sumAbsDeltaMS = 1000 * sumAbsDeltaSeconds;
+ for (int32_t i = 0; i < kAbsTimeDeltaBucketCount; i++) {
+ if (sumAbsDeltaMS <= kTimeDeltaMSIntervals[i])
+ return i;
+ }
+ NOTREACHED();
+ return kAbsTimeDeltaBucketCount - 1;
+}
+
+int32_t toTimeDeltaSample(double deltaSeconds) {
+ constexpr int32_t negativeBucketCount = arraysize(kTimeDeltaMSIntervals) - 1;
+ bool negative = deltaSeconds < 0;
+ double absDeltaMS = 1000 * std::abs(deltaSeconds);
+ for (int32_t i = 0; i < static_cast<int>(arraysize(kTimeDeltaMSIntervals));
+ i++) {
+ if (absDeltaMS <= kTimeDeltaMSIntervals[i])
+ return negativeBucketCount + (negative ? -i : +i);
+ }
+ NOTREACHED();
+ return negative ? 0 : kTimeDeltaBucketCount - 1;
+}
+
+#define ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, minWidth, maxWidth, metric, \
+ sample, HistogramType, ...) \
+ else if (width >= minWidth) { \
+ DEFINE_STATIC_LOCAL( \
+ HistogramType, metric##minWidth##_##maxWidth##Histogram, \
+ ("Media.Timeline." #metric "." #minWidth "_" #maxWidth, \
+ ##__VA_ARGS__)); \
+ metric##minWidth##_##maxWidth##Histogram.count(sample); \
+ }
+
+#define RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, metric, sample, \
+ HistogramType, ...) \
+ do { \
+ int width = timelineWidth; /* Avoid multiple evaluation. */ \
+ if (false) { \
+ } \
mlamouri (slow - plz ping) 2017/03/31 11:39:40 Can you add a comment explaining that the if (fals
johnme 2017/03/31 13:47:23 Done.
+ ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 512, inf, metric, sample, \
+ HistogramType, ##__VA_ARGS__) \
+ ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 256, 511, metric, sample, \
+ HistogramType, ##__VA_ARGS__) \
+ ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 128, 255, metric, sample, \
+ HistogramType, ##__VA_ARGS__) \
+ ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 80, 127, metric, sample, \
+ HistogramType, ##__VA_ARGS__) \
+ ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 48, 79, metric, sample, \
+ HistogramType, ##__VA_ARGS__) \
+ ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 32, 47, metric, sample, \
+ HistogramType, ##__VA_ARGS__) \
+ else { \
+ /* Skip logging if timeline is narrower than minimum suffix bucket. */ \
+ /* If this happens a lot, it'll show up in Media.Timeline.Width. */ \
+ } \
+ } while (false)
+
+void recordDragGestureDurationByWidth(int timelineWidth, TimeDelta duration) {
+ int32_t sample = static_cast<int32_t>(duration.InMilliseconds());
mlamouri (slow - plz ping) 2017/03/31 11:39:40 Here and below, why is it int32_t?
johnme 2017/03/31 13:47:23 For samples, base::HistogramBase::Sample is typede
+ RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, DragGestureDuration, sample,
+ CustomCountHistogram, 1 /* 1 ms */,
+ 60000 /* 1 minute */, 50);
+}
+void recordDragPercentByWidth(int timelineWidth, double percent) {
+ int32_t sample = toPercentSample(percent);
+ RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, DragPercent, sample,
+ EnumerationHistogram, kPercentBucketCount);
+}
+void recordDragSumAbsTimeDeltaByWidth(int timelineWidth,
+ double sumAbsDeltaSeconds) {
+ int32_t sample = toAbsTimeDeltaSample(sumAbsDeltaSeconds);
+ RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, DragSumAbsTimeDelta, sample,
+ EnumerationHistogram, kAbsTimeDeltaBucketCount);
+}
+void recordDragTimeDeltaByWidth(int timelineWidth, double deltaSeconds) {
+ int32_t sample = toTimeDeltaSample(deltaSeconds);
+ RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, DragTimeDelta, sample,
+ EnumerationHistogram, kTimeDeltaBucketCount);
+}
+void recordSeekTypeByWidth(int timelineWidth, SeekType type) {
+ int32_t sample = static_cast<int>(type);
+ constexpr int bucketCount = static_cast<int>(SeekType::kLast) + 1;
+ RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, SeekType, sample,
+ EnumerationHistogram, bucketCount);
+}
+
+#undef RECORD_TIMELINE_UMA_BY_WIDTH
+#undef ELSEIF_WIDTH_RECORD_TIMELINE_UMA
+
+} // namespace
+
+void MediaControlTimelineMetrics::startGesture(bool fromThumb) {
+ // Initialize gesture tracking.
+ m_state = fromThumb ? State::kGestureFromThumb : State::kGestureFromElsewhere;
+ m_dragStartTimeTicks = TimeTicks::Now();
+ m_dragDeltaMediaSeconds = 0;
+ m_dragSumAbsDeltaMediaSeconds = 0;
+}
+
+void MediaControlTimelineMetrics::onInput(double fromSeconds,
+ double toSeconds) {
+ switch (m_state) {
+ case State::kInactive:
+ // Unexpected input.
+ m_state = State::kInactive;
+ break;
+ case State::kGestureFromThumb:
+ // Drag confirmed now input has been received.
+ m_state = State::kDragFromThumb;
+ break;
+ case State::kGestureFromElsewhere:
+ // Click/drag confirmed now input has been received. Assume it's a click
+ // until further input is received.
+ m_state = State::kClick;
+ break;
+ case State::kClick:
+ // Drag confirmed now further input has been received.
+ m_state = State::kDragFromElsewhere;
+ break;
+ case State::kDragFromThumb:
+ case State::kDragFromElsewhere:
+ // Continue tracking drag.
+ break;
+ case State::kKeyDown:
+ // Continue tracking key.
+ break;
+ }
+
+ // The following tracking is only for drags. Note that we exclude kClick here,
+ // as even if it progresses to a kDragFromElsewhere, the first input event
+ // corresponds to the position jump from the pointer down on the track.
+ if (m_state != State::kDragFromThumb && m_state != State::kDragFromElsewhere)
+ return;
+
+ float deltaMediaSeconds = static_cast<float>(toSeconds - fromSeconds);
+ m_dragDeltaMediaSeconds += deltaMediaSeconds;
+ m_dragSumAbsDeltaMediaSeconds += std::abs(deltaMediaSeconds);
+}
+
+void MediaControlTimelineMetrics::recordEndGesture(
+ int timelineWidth,
+ double mediaDurationSeconds) {
+ State endState = m_state;
+ m_state = State::kInactive; // Reset tracking.
+
+ SeekType seekType;
+ switch (endState) {
+ case State::kInactive:
+ case State::kKeyDown:
+ return; // Pointer and keys were interleaved. Skip UMA in this edge case.
+ case State::kGestureFromThumb:
+ case State::kGestureFromElsewhere:
+ return; // Empty gesture with no calls to gestureInput.
+ case State::kDragFromThumb:
+ seekType = SeekType::kDragFromCurrentPosition;
+ break;
+ case State::kClick:
+ seekType = SeekType::kClick;
+ break;
+ case State::kDragFromElsewhere:
+ seekType = SeekType::kDragFromElsewhere;
+ break;
+ }
+
+ recordSeekTypeByWidth(timelineWidth, seekType);
+
+ if (seekType == SeekType::kClick)
+ return; // Metrics below are only for drags.
+
+ recordDragGestureDurationByWidth(timelineWidth,
+ TimeTicks::Now() - m_dragStartTimeTicks);
+ if (std::isfinite(mediaDurationSeconds)) {
+ recordDragPercentByWidth(
+ timelineWidth, 100.0 * m_dragDeltaMediaSeconds / mediaDurationSeconds);
+ }
+ recordDragSumAbsTimeDeltaByWidth(timelineWidth,
+ m_dragSumAbsDeltaMediaSeconds);
+ recordDragTimeDeltaByWidth(timelineWidth, m_dragDeltaMediaSeconds);
+}
+
+void MediaControlTimelineMetrics::startKey() {
+ m_state = State::kKeyDown;
+}
+
+void MediaControlTimelineMetrics::recordEndKey(int timelineWidth, int keyCode) {
+ State endState = m_state;
+ m_state = State::kInactive; // Reset tracking.
+ if (endState != State::kKeyDown)
+ return; // Pointer and keys were interleaved. Skip UMA in this edge case.
+
+ SeekType type;
+ switch (keyCode) {
+ case VKEY_UP:
+ case VKEY_DOWN:
+ case VKEY_LEFT:
+ case VKEY_RIGHT:
+ type = SeekType::kKeyboardArrowKey;
+ break;
+ case VKEY_PRIOR: // PageUp
+ case VKEY_NEXT: // PageDown
+ type = SeekType::kKeyboardPageUpDownKey;
+ break;
+ case VKEY_HOME:
+ case VKEY_END:
+ type = SeekType::kKeyboardHomeEndKey;
+ break;
+ default:
+ return; // Other keys don't seek (at time of writing).
+ }
+ recordSeekTypeByWidth(timelineWidth, type);
+}
+
+void MediaControlTimelineMetrics::recordWidthOnFirstPlay(bool isFullscreen,
+ bool isPortrait,
+ int timelineWidth) {
+ constexpr int min = 1;
+ constexpr int max = 7680; // Timeline unlikely to be wider than 8K monitors.
mlamouri (slow - plz ping) 2017/03/31 11:39:40 +1 with the comment, especially in CSS pixels.
johnme 2017/03/31 13:47:23 Changed to "Equivalent to an 80inch wide 8K monito
+ constexpr int bucketCount = 50;
+ // Record merged histogram for all configurations.
+ DEFINE_STATIC_LOCAL(CustomCountHistogram, allConfigurationsWidthHistogram,
+ ("Media.Timeline.Width", min, max, bucketCount));
+ allConfigurationsWidthHistogram.count(timelineWidth);
+ // Record configuration-specific histogram.
+ if (!isFullscreen) {
+ if (isPortrait) {
+ DEFINE_STATIC_LOCAL(
+ CustomCountHistogram, widthHistogram,
+ ("Media.Timeline.Width.InlinePortrait", min, max, bucketCount));
+ widthHistogram.count(timelineWidth);
+ } else {
+ DEFINE_STATIC_LOCAL(
+ CustomCountHistogram, widthHistogram,
+ ("Media.Timeline.Width.InlineLandscape", min, max, bucketCount));
+ widthHistogram.count(timelineWidth);
+ }
+ } else {
+ if (isPortrait) {
+ DEFINE_STATIC_LOCAL(
+ CustomCountHistogram, widthHistogram,
+ ("Media.Timeline.Width.FullscreenPortrait", min, max, bucketCount));
+ widthHistogram.count(timelineWidth);
+ } else {
+ DEFINE_STATIC_LOCAL(
+ CustomCountHistogram, widthHistogram,
+ ("Media.Timeline.Width.FullscreenLandscape", min, max, bucketCount));
+ widthHistogram.count(timelineWidth);
+ }
+ }
+}
+
+} // namespace blink

Powered by Google App Engine
This is Rietveld 408576698