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 |