Chromium Code Reviews| 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 // 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 | |
| OLD | NEW |