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

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

Powered by Google App Engine
This is Rietveld 408576698