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

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: Address isherman's review comments 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 0, // Dedicated zero bucket so upper bound is inclusive, unlike the others.
55 16, // 16ms
56 32, // 32ms
57 64, // 64ms
58 128, // 128ms
59 256, // 256ms
60 512, // 512ms
61 1 * 1000, // 1s
62 2 * 1000, // 2s
63 4 * 1000, // 4s
64 8 * 1000, // 8s
65 15 * 1000, // 15s
66 30 * 1000, // 30s
67 60 * 1000, // 1m
68 120 * 1000, // 2m
Ilya Sherman 2017/04/03 21:39:26 nit: If you're going to write these as products, t
johnme 2017/04/06 16:23:26 Ah; in that case, I'll go back to writing them as
69 240 * 1000, // 4m
70 480 * 1000, // 8m
71 900 * 1000, // 15m
72 1800 * 1000, // 30m
73 3600 * 1000, // 1h
74 7200 * 1000, // 2h
75 14400 * 1000, // 4h
76 28800 * 1000, // 8h
Ilya Sherman 2017/04/03 21:39:26 nit: Likewise, "8 * 60 * 60 * 1000" is more clearl
johnme 2017/04/06 16:23:26 Ditto I'll go back to writing these as raw values
77 57600 * 1000, // 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::onInput(double fromSeconds,
217 double toSeconds) {
218 switch (m_state) {
219 case State::kInactive:
220 // Unexpected input.
221 m_state = State::kInactive;
222 break;
223 case State::kGestureFromThumb:
224 // Drag confirmed now input has been received.
225 m_state = State::kDragFromThumb;
226 break;
227 case State::kGestureFromElsewhere:
228 // Click/drag confirmed now input has been received. Assume it's a click
229 // until further input is received.
230 m_state = State::kClick;
231 break;
232 case State::kClick:
233 // Drag confirmed now further input has been received.
234 m_state = State::kDragFromElsewhere;
235 break;
236 case State::kDragFromThumb:
237 case State::kDragFromElsewhere:
238 // Continue tracking drag.
239 break;
240 case State::kKeyDown:
241 // Continue tracking key.
242 break;
243 }
244
245 // The following tracking is only for drags. Note that we exclude kClick here,
246 // as even if it progresses to a kDragFromElsewhere, the first input event
247 // corresponds to the position jump from the pointer down on the track.
248 if (m_state != State::kDragFromThumb && m_state != State::kDragFromElsewhere)
249 return;
250
251 float deltaMediaSeconds = static_cast<float>(toSeconds - fromSeconds);
252 m_dragDeltaMediaSeconds += deltaMediaSeconds;
253 m_dragSumAbsDeltaMediaSeconds += std::abs(deltaMediaSeconds);
254 }
255
256 void MediaControlTimelineMetrics::recordEndGesture(
257 int timelineWidth,
258 double mediaDurationSeconds) {
259 State endState = m_state;
260 m_state = State::kInactive; // Reset tracking.
261
262 SeekType seekType;
263 switch (endState) {
264 case State::kInactive:
265 case State::kKeyDown:
266 return; // Pointer and keys were interleaved. Skip UMA in this edge case.
267 case State::kGestureFromThumb:
268 case State::kGestureFromElsewhere:
269 return; // Empty gesture with no calls to gestureInput.
270 case State::kDragFromThumb:
271 seekType = SeekType::kDragFromCurrentPosition;
272 break;
273 case State::kClick:
274 seekType = SeekType::kClick;
275 break;
276 case State::kDragFromElsewhere:
277 seekType = SeekType::kDragFromElsewhere;
278 break;
279 }
280
281 recordSeekTypeByWidth(timelineWidth, seekType);
282
283 if (seekType == SeekType::kClick)
284 return; // Metrics below are only for drags.
285
286 recordDragGestureDurationByWidth(timelineWidth,
287 TimeTicks::Now() - m_dragStartTimeTicks);
288 if (std::isfinite(mediaDurationSeconds)) {
289 recordDragPercentByWidth(
290 timelineWidth, 100.0 * m_dragDeltaMediaSeconds / mediaDurationSeconds);
291 }
292 recordDragSumAbsTimeDeltaByWidth(timelineWidth,
293 m_dragSumAbsDeltaMediaSeconds);
294 recordDragTimeDeltaByWidth(timelineWidth, m_dragDeltaMediaSeconds);
295 }
296
297 void MediaControlTimelineMetrics::startKey() {
298 m_state = State::kKeyDown;
299 }
300
301 void MediaControlTimelineMetrics::recordEndKey(int timelineWidth, int keyCode) {
302 State endState = m_state;
303 m_state = State::kInactive; // Reset tracking.
304 if (endState != State::kKeyDown)
305 return; // Pointer and keys were interleaved. Skip UMA in this edge case.
306
307 SeekType type;
308 switch (keyCode) {
309 case VKEY_UP:
310 case VKEY_DOWN:
311 case VKEY_LEFT:
312 case VKEY_RIGHT:
313 type = SeekType::kKeyboardArrowKey;
314 break;
315 case VKEY_PRIOR: // PageUp
316 case VKEY_NEXT: // PageDown
317 type = SeekType::kKeyboardPageUpDownKey;
318 break;
319 case VKEY_HOME:
320 case VKEY_END:
321 type = SeekType::kKeyboardHomeEndKey;
322 break;
323 default:
324 return; // Other keys don't seek (at time of writing).
325 }
326 recordSeekTypeByWidth(timelineWidth, type);
327 }
328
329 void MediaControlTimelineMetrics::recordPlaying(
330 WebScreenOrientationType orientation,
331 bool isFullscreen,
332 int timelineWidth) {
333 bool isPortrait;
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