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

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

Issue 2779273003: [Media Controls] Add UMA for timeline scrubber (Closed)
Patch Set: Appease MSVC Created 3 years, 8 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "core/html/shadow/MediaControlTimelineMetrics.h"
6
7 #include <stdint.h>
8 #include <cmath>
9 #include <limits>
10 #include "platform/KeyboardCodes.h"
11 #include "platform/wtf/StdLibExtras.h"
12
13 namespace blink {
14
15 namespace {
16
17 // Correponds to UMA MediaTimelineSeekType enum. Enum values can be added, but
18 // must never be renumbered or deleted and reused.
19 enum class SeekType {
20 kClick = 0,
21 kDragFromCurrentPosition = 1,
22 kDragFromElsewhere = 2,
23 kKeyboardArrowKey = 3,
24 kKeyboardPageUpDownKey = 4,
25 kKeyboardHomeEndKey = 5,
26 // Update kLast when adding new values.
27 kLast = kKeyboardHomeEndKey
28 };
29
30 // Exclusive upper bounds for the positive buckets of UMA MediaTimelinePercent
31 // enum, which are reflected to form the negative buckets. The custom enum is
32 // because UMA count histograms don't support negative values. Values must not
33 // be added/modified/removed due to the way the negative buckets are formed.
34 constexpr double kPercentIntervals[] = {
35 0, // Dedicated zero bucket so upper bound is inclusive, unlike the others.
36 0.1, 0.2, 0.3, 0.5, 0.7, 1.0, 1.5, 2.0, 3.0, 5.0, 7.0, 10.0,
37 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0, 50.0, 60.0, 70.0, 80.0, 90.0,
38 100.0 // 100% upper bound is inclusive, unlike the others.
39 };
40 // Must match length of UMA MediaTimelinePercent enum.
41 constexpr int32_t kPercentBucketCount = 51;
42 static_assert(arraysize(kPercentIntervals) * 2 - 1 == kPercentBucketCount,
43 "Intervals must match UMA MediaTimelinePercent enum");
44
45 // Corresponds to two UMA enums of different sizes! Values are the exclusive
46 // upper bounds for buckets of UMA MediaTimelineAbsTimeDelta enum with the same
47 // index, and also for the positive buckets of UMA MediaTimelineTimeDelta enum,
48 // which are reflected to form the negative buckets. MediaTimelineTimeDelta
49 // needed a custom enum because UMA count histograms don't support negative
50 // values, and MediaTimelineAbsTimeDelta uses the same mechanism so the values
51 // can be compared easily. Values must not be added/modified/removed due to the
52 // way the negative buckets are formed.
53 constexpr double kTimeDeltaMSIntervals[] = {
54 1, // 1ms
55 16, // 16ms
56 32, // 32ms
57 64, // 64ms
58 128, // 128ms
59 256, // 256ms
60 512, // 512ms
61 1000, // 1s
62 2000, // 2s
63 4000, // 4s
64 8000, // 8s
65 15000, // 15s
66 30000, // 30s
67 60000, // 1m
68 120000, // 2m
69 240000, // 4m
70 480000, // 8m
71 900000, // 15m
72 1800000, // 30m
73 3600000, // 1h
74 7200000, // 2h
75 14400000, // 4h
76 28800000, // 8h
77 57600000, // 16h
78 std::numeric_limits<double>::infinity()};
79 // Must match length of UMA MediaTimelineAbsTimeDelta enum.
80 constexpr int32_t kAbsTimeDeltaBucketCount = 25;
81 // Must match length of UMA MediaTimelineTimeDelta enum.
82 constexpr int32_t kTimeDeltaBucketCount = 49;
83 static_assert(arraysize(kTimeDeltaMSIntervals) == kAbsTimeDeltaBucketCount,
84 "Intervals must match UMA MediaTimelineAbsTimeDelta enum");
85 static_assert(arraysize(kTimeDeltaMSIntervals) * 2 - 1 == kTimeDeltaBucketCount,
86 "Intervals must match UMA MediaTimelineTimeDelta enum");
87
88 // Calculates index of UMA MediaTimelinePercent enum corresponding to |percent|.
89 // Negative values use kPercentIntervals in reverse.
90 int32_t toPercentSample(double percent) {
91 constexpr int32_t nonNegativeBucketCount = arraysize(kPercentIntervals);
92 constexpr int32_t negativeBucketCount = arraysize(kPercentIntervals) - 1;
93 bool negative = percent < 0;
94 double absPercent = std::abs(percent);
95 if (absPercent == 0)
96 return negativeBucketCount; // Dedicated zero bucket.
97 for (int32_t i = 0; i < nonNegativeBucketCount; i++) {
98 if (absPercent < kPercentIntervals[i])
99 return negativeBucketCount + (negative ? -i : +i);
100 }
101 // No NOTREACHED since the +/-100 bounds are inclusive (even if they are
102 // slightly exceeded due to floating point inaccuracies).
103 return negative ? 0 : kPercentBucketCount - 1;
104 }
105
106 // Calculates index of UMA MediaTimelineAbsTimeDelta enum corresponding to
107 // |sumAbsDeltaSeconds|.
108 int32_t toAbsTimeDeltaSample(double sumAbsDeltaSeconds) {
109 double sumAbsDeltaMS = 1000 * sumAbsDeltaSeconds;
110 if (sumAbsDeltaMS == 0)
111 return 0; // Dedicated zero bucket.
112 for (int32_t i = 0; i < kAbsTimeDeltaBucketCount; i++) {
113 if (sumAbsDeltaMS < kTimeDeltaMSIntervals[i])
114 return i;
115 }
116 NOTREACHED() << "sumAbsDeltaSeconds shouldn't be infinite";
117 return kAbsTimeDeltaBucketCount - 1;
118 }
119
120 // Calculates index of UMA MediaTimelineTimeDelta enum corresponding to
121 // |deltaSeconds|. Negative values use kTimeDeltaMSIntervals in reverse.
122 int32_t toTimeDeltaSample(double deltaSeconds) {
123 constexpr int32_t nonNegativeBucketCount = arraysize(kTimeDeltaMSIntervals);
124 constexpr int32_t negativeBucketCount = arraysize(kTimeDeltaMSIntervals) - 1;
125 bool negative = deltaSeconds < 0;
126 double absDeltaMS = 1000 * std::abs(deltaSeconds);
127 if (absDeltaMS == 0)
128 return negativeBucketCount; // Dedicated zero bucket.
129 for (int32_t i = 0; i < nonNegativeBucketCount; i++) {
130 if (absDeltaMS < kTimeDeltaMSIntervals[i])
131 return negativeBucketCount + (negative ? -i : +i);
132 }
133 NOTREACHED() << "deltaSeconds shouldn't be infinite";
134 return negative ? 0 : kTimeDeltaBucketCount - 1;
135 }
136
137 // Helper for RECORD_TIMELINE_UMA_BY_WIDTH.
138 #define ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, minWidth, maxWidth, metric, \
139 sample, HistogramType, ...) \
140 else if (width >= minWidth) { \
141 DEFINE_STATIC_LOCAL( \
142 HistogramType, metric##minWidth##_##maxWidth##Histogram, \
143 ("Media.Timeline." #metric "." #minWidth "_" #maxWidth, \
144 ##__VA_ARGS__)); \
145 metric##minWidth##_##maxWidth##Histogram.count(sample); \
146 }
147
148 // Records UMA with a histogram suffix based on timelineWidth.
149 #define RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, metric, sample, \
150 HistogramType, ...) \
151 do { \
152 int width = timelineWidth; /* Avoid multiple evaluation. */ \
153 if (false) { \
154 /* This if(false) allows all the conditions below to start with else. */ \
155 } \
156 ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 512, inf, metric, sample, \
157 HistogramType, ##__VA_ARGS__) \
158 ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 256, 511, metric, sample, \
159 HistogramType, ##__VA_ARGS__) \
160 ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 128, 255, metric, sample, \
161 HistogramType, ##__VA_ARGS__) \
162 ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 80, 127, metric, sample, \
163 HistogramType, ##__VA_ARGS__) \
164 ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 48, 79, metric, sample, \
165 HistogramType, ##__VA_ARGS__) \
166 ELSEIF_WIDTH_RECORD_TIMELINE_UMA(width, 32, 47, metric, sample, \
167 HistogramType, ##__VA_ARGS__) \
168 else { \
169 /* Skip logging if timeline is narrower than minimum suffix bucket. */ \
170 /* If this happens a lot, it'll show up in Media.Timeline.Width. */ \
171 } \
172 } while (false)
173
174 void recordDragGestureDurationByWidth(int timelineWidth, TimeDelta duration) {
175 int32_t sample = static_cast<int32_t>(duration.InMilliseconds());
176 RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, DragGestureDuration, sample,
177 CustomCountHistogram, 1 /* 1 ms */,
178 60000 /* 1 minute */, 50);
179 }
180 void recordDragPercentByWidth(int timelineWidth, double percent) {
181 int32_t sample = toPercentSample(percent);
182 RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, DragPercent, sample,
183 EnumerationHistogram, kPercentBucketCount);
184 }
185 void recordDragSumAbsTimeDeltaByWidth(int timelineWidth,
186 double sumAbsDeltaSeconds) {
187 int32_t sample = toAbsTimeDeltaSample(sumAbsDeltaSeconds);
188 RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, DragSumAbsTimeDelta, sample,
189 EnumerationHistogram, kAbsTimeDeltaBucketCount);
190 }
191 void recordDragTimeDeltaByWidth(int timelineWidth, double deltaSeconds) {
192 int32_t sample = toTimeDeltaSample(deltaSeconds);
193 RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, DragTimeDelta, sample,
194 EnumerationHistogram, kTimeDeltaBucketCount);
195 }
196 void recordSeekTypeByWidth(int timelineWidth, SeekType type) {
197 int32_t sample = static_cast<int32_t>(type);
198 constexpr int32_t bucketCount = static_cast<int32_t>(SeekType::kLast) + 1;
199 RECORD_TIMELINE_UMA_BY_WIDTH(timelineWidth, SeekType, sample,
200 EnumerationHistogram, bucketCount);
201 }
202
203 #undef RECORD_TIMELINE_UMA_BY_WIDTH
204 #undef ELSEIF_WIDTH_RECORD_TIMELINE_UMA
205
206 } // namespace
207
208 void MediaControlTimelineMetrics::startGesture(bool fromThumb) {
209 // Initialize gesture tracking.
210 m_state = fromThumb ? State::kGestureFromThumb : State::kGestureFromElsewhere;
211 m_dragStartTimeTicks = TimeTicks::Now();
212 m_dragDeltaMediaSeconds = 0;
213 m_dragSumAbsDeltaMediaSeconds = 0;
214 }
215
216 void MediaControlTimelineMetrics::recordEndGesture(
217 int timelineWidth,
218 double mediaDurationSeconds) {
219 State endState = m_state;
220 m_state = State::kInactive; // Reset tracking.
221
222 SeekType seekType = SeekType::kLast; // Arbitrary inital val to appease MSVC.
223 switch (endState) {
224 case State::kInactive:
225 case State::kKeyDown:
226 return; // Pointer and keys were interleaved. Skip UMA in this edge case.
227 case State::kGestureFromThumb:
228 case State::kGestureFromElsewhere:
229 return; // Empty gesture with no calls to gestureInput.
230 case State::kDragFromThumb:
231 seekType = SeekType::kDragFromCurrentPosition;
232 break;
233 case State::kClick:
234 seekType = SeekType::kClick;
235 break;
236 case State::kDragFromElsewhere:
237 seekType = SeekType::kDragFromElsewhere;
238 break;
239 }
240
241 recordSeekTypeByWidth(timelineWidth, seekType);
242
243 if (seekType == SeekType::kClick)
244 return; // Metrics below are only for drags.
245
246 recordDragGestureDurationByWidth(timelineWidth,
247 TimeTicks::Now() - m_dragStartTimeTicks);
248 if (std::isfinite(mediaDurationSeconds)) {
249 recordDragPercentByWidth(
250 timelineWidth, 100.0 * m_dragDeltaMediaSeconds / mediaDurationSeconds);
251 }
252 recordDragSumAbsTimeDeltaByWidth(timelineWidth,
253 m_dragSumAbsDeltaMediaSeconds);
254 recordDragTimeDeltaByWidth(timelineWidth, m_dragDeltaMediaSeconds);
255 }
256
257 void MediaControlTimelineMetrics::startKey() {
258 m_state = State::kKeyDown;
259 }
260
261 void MediaControlTimelineMetrics::recordEndKey(int timelineWidth, int keyCode) {
262 State endState = m_state;
263 m_state = State::kInactive; // Reset tracking.
264 if (endState != State::kKeyDown)
265 return; // Pointer and keys were interleaved. Skip UMA in this edge case.
266
267 SeekType type;
268 switch (keyCode) {
269 case VKEY_UP:
270 case VKEY_DOWN:
271 case VKEY_LEFT:
272 case VKEY_RIGHT:
273 type = SeekType::kKeyboardArrowKey;
274 break;
275 case VKEY_PRIOR: // PageUp
276 case VKEY_NEXT: // PageDown
277 type = SeekType::kKeyboardPageUpDownKey;
278 break;
279 case VKEY_HOME:
280 case VKEY_END:
281 type = SeekType::kKeyboardHomeEndKey;
282 break;
283 default:
284 return; // Other keys don't seek (at time of writing).
285 }
286 recordSeekTypeByWidth(timelineWidth, type);
287 }
288
289 void MediaControlTimelineMetrics::onInput(double fromSeconds,
290 double toSeconds) {
291 switch (m_state) {
292 case State::kInactive:
293 // Unexpected input.
294 m_state = State::kInactive;
295 break;
296 case State::kGestureFromThumb:
297 // Drag confirmed now input has been received.
298 m_state = State::kDragFromThumb;
299 break;
300 case State::kGestureFromElsewhere:
301 // Click/drag confirmed now input has been received. Assume it's a click
302 // until further input is received.
303 m_state = State::kClick;
304 break;
305 case State::kClick:
306 // Drag confirmed now further input has been received.
307 m_state = State::kDragFromElsewhere;
308 break;
309 case State::kDragFromThumb:
310 case State::kDragFromElsewhere:
311 // Continue tracking drag.
312 break;
313 case State::kKeyDown:
314 // Continue tracking key.
315 break;
316 }
317
318 // The following tracking is only for drags. Note that we exclude kClick here,
319 // as even if it progresses to a kDragFromElsewhere, the first input event
320 // corresponds to the position jump from the pointer down on the track.
321 if (m_state != State::kDragFromThumb && m_state != State::kDragFromElsewhere)
322 return;
323
324 float deltaMediaSeconds = static_cast<float>(toSeconds - fromSeconds);
325 m_dragDeltaMediaSeconds += deltaMediaSeconds;
326 m_dragSumAbsDeltaMediaSeconds += std::abs(deltaMediaSeconds);
327 }
328
329 void MediaControlTimelineMetrics::recordPlaying(
330 WebScreenOrientationType orientation,
331 bool isFullscreen,
332 int timelineWidth) {
333 bool isPortrait = false; // Arbitrary initial value to appease MSVC.
334 switch (orientation) {
335 case WebScreenOrientationPortraitPrimary:
336 case WebScreenOrientationPortraitSecondary:
337 isPortrait = true;
338 break;
339 case WebScreenOrientationLandscapePrimary:
340 case WebScreenOrientationLandscapeSecondary:
341 isPortrait = false;
342 break;
343 case WebScreenOrientationUndefined:
344 return; // Skip UMA in the unlikely event we fail to detect orientation.
345 }
346
347 // Only record the first time each media element enters the playing state.
348 if (!m_hasNeverBeenPlaying)
349 return;
350 m_hasNeverBeenPlaying = false;
351
352 constexpr int32_t min = 1;
353 constexpr int32_t max = 7680; // Equivalent to an 80inch wide 8K monitor.
354 constexpr int32_t bucketCount = 50;
355 // Record merged histogram for all configurations.
356 DEFINE_STATIC_LOCAL(CustomCountHistogram, allConfigurationsWidthHistogram,
357 ("Media.Timeline.Width", min, max, bucketCount));
358 allConfigurationsWidthHistogram.count(timelineWidth);
359 // Record configuration-specific histogram.
360 if (!isFullscreen) {
361 if (isPortrait) {
362 DEFINE_STATIC_LOCAL(
363 CustomCountHistogram, widthHistogram,
364 ("Media.Timeline.Width.InlinePortrait", min, max, bucketCount));
365 widthHistogram.count(timelineWidth);
366 } else {
367 DEFINE_STATIC_LOCAL(
368 CustomCountHistogram, widthHistogram,
369 ("Media.Timeline.Width.InlineLandscape", min, max, bucketCount));
370 widthHistogram.count(timelineWidth);
371 }
372 } else {
373 if (isPortrait) {
374 DEFINE_STATIC_LOCAL(
375 CustomCountHistogram, widthHistogram,
376 ("Media.Timeline.Width.FullscreenPortrait", min, max, bucketCount));
377 widthHistogram.count(timelineWidth);
378 } else {
379 DEFINE_STATIC_LOCAL(
380 CustomCountHistogram, widthHistogram,
381 ("Media.Timeline.Width.FullscreenLandscape", min, max, bucketCount));
382 widthHistogram.count(timelineWidth);
383 }
384 }
385 }
386
387 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698