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