OLD | NEW |
---|---|
(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 | |
OLD | NEW |