| 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 kNonNegativeBucketCount = arraysize(kPercentIntervals); | |
| 92 constexpr int32_t kNegativeBucketCount = arraysize(kPercentIntervals) - 1; | |
| 93 bool negative = percent < 0; | |
| 94 double abs_percent = std::abs(percent); | |
| 95 if (abs_percent == 0) | |
| 96 return kNegativeBucketCount; // Dedicated zero bucket. | |
| 97 for (int32_t i = 0; i < kNonNegativeBucketCount; i++) { | |
| 98 if (abs_percent < kPercentIntervals[i]) | |
| 99 return kNegativeBucketCount + (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 sum_abs_delta_seconds) { | |
| 109 double sum_abs_delta_ms = 1000 * sum_abs_delta_seconds; | |
| 110 if (sum_abs_delta_ms == 0) | |
| 111 return 0; // Dedicated zero bucket. | |
| 112 for (int32_t i = 0; i < kAbsTimeDeltaBucketCount; i++) { | |
| 113 if (sum_abs_delta_ms < 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 delta_seconds) { | |
| 123 constexpr int32_t kNonNegativeBucketCount = arraysize(kTimeDeltaMSIntervals); | |
| 124 constexpr int32_t kNegativeBucketCount = arraysize(kTimeDeltaMSIntervals) - 1; | |
| 125 bool negative = delta_seconds < 0; | |
| 126 double abs_delta_ms = 1000 * std::abs(delta_seconds); | |
| 127 if (abs_delta_ms == 0) | |
| 128 return kNegativeBucketCount; // Dedicated zero bucket. | |
| 129 for (int32_t i = 0; i < kNonNegativeBucketCount; i++) { | |
| 130 if (abs_delta_ms < kTimeDeltaMSIntervals[i]) | |
| 131 return kNegativeBucketCount + (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 timeline_width, TimeDelta duration) { | |
| 175 int32_t sample = static_cast<int32_t>(duration.InMilliseconds()); | |
| 176 RECORD_TIMELINE_UMA_BY_WIDTH(timeline_width, DragGestureDuration, sample, | |
| 177 CustomCountHistogram, 1 /* 1 ms */, | |
| 178 60000 /* 1 minute */, 50); | |
| 179 } | |
| 180 void RecordDragPercentByWidth(int timeline_width, double percent) { | |
| 181 int32_t sample = ToPercentSample(percent); | |
| 182 RECORD_TIMELINE_UMA_BY_WIDTH(timeline_width, DragPercent, sample, | |
| 183 EnumerationHistogram, kPercentBucketCount); | |
| 184 } | |
| 185 void RecordDragSumAbsTimeDeltaByWidth(int timeline_width, | |
| 186 double sum_abs_delta_seconds) { | |
| 187 int32_t sample = ToAbsTimeDeltaSample(sum_abs_delta_seconds); | |
| 188 RECORD_TIMELINE_UMA_BY_WIDTH(timeline_width, DragSumAbsTimeDelta, sample, | |
| 189 EnumerationHistogram, kAbsTimeDeltaBucketCount); | |
| 190 } | |
| 191 void RecordDragTimeDeltaByWidth(int timeline_width, double delta_seconds) { | |
| 192 int32_t sample = ToTimeDeltaSample(delta_seconds); | |
| 193 RECORD_TIMELINE_UMA_BY_WIDTH(timeline_width, DragTimeDelta, sample, | |
| 194 EnumerationHistogram, kTimeDeltaBucketCount); | |
| 195 } | |
| 196 void RecordSeekTypeByWidth(int timeline_width, SeekType type) { | |
| 197 int32_t sample = static_cast<int32_t>(type); | |
| 198 constexpr int32_t kBucketCount = static_cast<int32_t>(SeekType::kLast) + 1; | |
| 199 RECORD_TIMELINE_UMA_BY_WIDTH(timeline_width, SeekType, sample, | |
| 200 EnumerationHistogram, kBucketCount); | |
| 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 from_thumb) { | |
| 209 // Initialize gesture tracking. | |
| 210 state_ = from_thumb ? State::kGestureFromThumb : State::kGestureFromElsewhere; | |
| 211 drag_start_time_ticks_ = TimeTicks::Now(); | |
| 212 drag_delta_media_seconds_ = 0; | |
| 213 drag_sum_abs_delta_media_seconds_ = 0; | |
| 214 } | |
| 215 | |
| 216 void MediaControlTimelineMetrics::RecordEndGesture( | |
| 217 int timeline_width, | |
| 218 double media_duration_seconds) { | |
| 219 State end_state = state_; | |
| 220 state_ = State::kInactive; // Reset tracking. | |
| 221 | |
| 222 SeekType seek_type = | |
| 223 SeekType::kLast; // Arbitrary inital val to appease MSVC. | |
| 224 switch (end_state) { | |
| 225 case State::kInactive: | |
| 226 case State::kKeyDown: | |
| 227 return; // Pointer and keys were interleaved. Skip UMA in this edge case. | |
| 228 case State::kGestureFromThumb: | |
| 229 case State::kGestureFromElsewhere: | |
| 230 return; // Empty gesture with no calls to gestureInput. | |
| 231 case State::kDragFromThumb: | |
| 232 seek_type = SeekType::kDragFromCurrentPosition; | |
| 233 break; | |
| 234 case State::kClick: | |
| 235 seek_type = SeekType::kClick; | |
| 236 break; | |
| 237 case State::kDragFromElsewhere: | |
| 238 seek_type = SeekType::kDragFromElsewhere; | |
| 239 break; | |
| 240 } | |
| 241 | |
| 242 RecordSeekTypeByWidth(timeline_width, seek_type); | |
| 243 | |
| 244 if (seek_type == SeekType::kClick) | |
| 245 return; // Metrics below are only for drags. | |
| 246 | |
| 247 RecordDragGestureDurationByWidth(timeline_width, | |
| 248 TimeTicks::Now() - drag_start_time_ticks_); | |
| 249 if (std::isfinite(media_duration_seconds)) { | |
| 250 RecordDragPercentByWidth(timeline_width, 100.0 * drag_delta_media_seconds_ / | |
| 251 media_duration_seconds); | |
| 252 } | |
| 253 RecordDragSumAbsTimeDeltaByWidth(timeline_width, | |
| 254 drag_sum_abs_delta_media_seconds_); | |
| 255 RecordDragTimeDeltaByWidth(timeline_width, drag_delta_media_seconds_); | |
| 256 } | |
| 257 | |
| 258 void MediaControlTimelineMetrics::StartKey() { | |
| 259 state_ = State::kKeyDown; | |
| 260 } | |
| 261 | |
| 262 void MediaControlTimelineMetrics::RecordEndKey(int timeline_width, | |
| 263 int key_code) { | |
| 264 State end_state = state_; | |
| 265 state_ = State::kInactive; // Reset tracking. | |
| 266 if (end_state != State::kKeyDown) | |
| 267 return; // Pointer and keys were interleaved. Skip UMA in this edge case. | |
| 268 | |
| 269 SeekType type; | |
| 270 switch (key_code) { | |
| 271 case VKEY_UP: | |
| 272 case VKEY_DOWN: | |
| 273 case VKEY_LEFT: | |
| 274 case VKEY_RIGHT: | |
| 275 type = SeekType::kKeyboardArrowKey; | |
| 276 break; | |
| 277 case VKEY_PRIOR: // PageUp | |
| 278 case VKEY_NEXT: // PageDown | |
| 279 type = SeekType::kKeyboardPageUpDownKey; | |
| 280 break; | |
| 281 case VKEY_HOME: | |
| 282 case VKEY_END: | |
| 283 type = SeekType::kKeyboardHomeEndKey; | |
| 284 break; | |
| 285 default: | |
| 286 return; // Other keys don't seek (at time of writing). | |
| 287 } | |
| 288 RecordSeekTypeByWidth(timeline_width, type); | |
| 289 } | |
| 290 | |
| 291 void MediaControlTimelineMetrics::OnInput(double from_seconds, | |
| 292 double to_seconds) { | |
| 293 switch (state_) { | |
| 294 case State::kInactive: | |
| 295 // Unexpected input. | |
| 296 state_ = State::kInactive; | |
| 297 break; | |
| 298 case State::kGestureFromThumb: | |
| 299 // Drag confirmed now input has been received. | |
| 300 state_ = State::kDragFromThumb; | |
| 301 break; | |
| 302 case State::kGestureFromElsewhere: | |
| 303 // Click/drag confirmed now input has been received. Assume it's a click | |
| 304 // until further input is received. | |
| 305 state_ = State::kClick; | |
| 306 break; | |
| 307 case State::kClick: | |
| 308 // Drag confirmed now further input has been received. | |
| 309 state_ = State::kDragFromElsewhere; | |
| 310 break; | |
| 311 case State::kDragFromThumb: | |
| 312 case State::kDragFromElsewhere: | |
| 313 // Continue tracking drag. | |
| 314 break; | |
| 315 case State::kKeyDown: | |
| 316 // Continue tracking key. | |
| 317 break; | |
| 318 } | |
| 319 | |
| 320 // The following tracking is only for drags. Note that we exclude kClick here, | |
| 321 // as even if it progresses to a kDragFromElsewhere, the first input event | |
| 322 // corresponds to the position jump from the pointer down on the track. | |
| 323 if (state_ != State::kDragFromThumb && state_ != State::kDragFromElsewhere) | |
| 324 return; | |
| 325 | |
| 326 float delta_media_seconds = static_cast<float>(to_seconds - from_seconds); | |
| 327 drag_delta_media_seconds_ += delta_media_seconds; | |
| 328 drag_sum_abs_delta_media_seconds_ += std::abs(delta_media_seconds); | |
| 329 } | |
| 330 | |
| 331 void MediaControlTimelineMetrics::RecordPlaying( | |
| 332 WebScreenOrientationType orientation, | |
| 333 bool is_fullscreen, | |
| 334 int timeline_width) { | |
| 335 bool is_portrait = false; // Arbitrary initial value to appease MSVC. | |
| 336 switch (orientation) { | |
| 337 case kWebScreenOrientationPortraitPrimary: | |
| 338 case kWebScreenOrientationPortraitSecondary: | |
| 339 is_portrait = true; | |
| 340 break; | |
| 341 case kWebScreenOrientationLandscapePrimary: | |
| 342 case kWebScreenOrientationLandscapeSecondary: | |
| 343 is_portrait = false; | |
| 344 break; | |
| 345 case kWebScreenOrientationUndefined: | |
| 346 return; // Skip UMA in the unlikely event we fail to detect orientation. | |
| 347 } | |
| 348 | |
| 349 // Only record the first time each media element enters the playing state. | |
| 350 if (!has_never_been_playing_) | |
| 351 return; | |
| 352 has_never_been_playing_ = false; | |
| 353 | |
| 354 constexpr int32_t kMin = 1; | |
| 355 constexpr int32_t kMax = 7680; // Equivalent to an 80inch wide 8K monitor. | |
| 356 constexpr int32_t kBucketCount = 50; | |
| 357 // Record merged histogram for all configurations. | |
| 358 DEFINE_STATIC_LOCAL(CustomCountHistogram, all_configurations_width_histogram, | |
| 359 ("Media.Timeline.Width", kMin, kMax, kBucketCount)); | |
| 360 all_configurations_width_histogram.Count(timeline_width); | |
| 361 // Record configuration-specific histogram. | |
| 362 if (!is_fullscreen) { | |
| 363 if (is_portrait) { | |
| 364 DEFINE_STATIC_LOCAL( | |
| 365 CustomCountHistogram, width_histogram, | |
| 366 ("Media.Timeline.Width.InlinePortrait", kMin, kMax, kBucketCount)); | |
| 367 width_histogram.Count(timeline_width); | |
| 368 } else { | |
| 369 DEFINE_STATIC_LOCAL( | |
| 370 CustomCountHistogram, width_histogram, | |
| 371 ("Media.Timeline.Width.InlineLandscape", kMin, kMax, kBucketCount)); | |
| 372 width_histogram.Count(timeline_width); | |
| 373 } | |
| 374 } else { | |
| 375 if (is_portrait) { | |
| 376 DEFINE_STATIC_LOCAL(CustomCountHistogram, width_histogram, | |
| 377 ("Media.Timeline.Width.FullscreenPortrait", kMin, | |
| 378 kMax, kBucketCount)); | |
| 379 width_histogram.Count(timeline_width); | |
| 380 } else { | |
| 381 DEFINE_STATIC_LOCAL(CustomCountHistogram, width_histogram, | |
| 382 ("Media.Timeline.Width.FullscreenLandscape", kMin, | |
| 383 kMax, kBucketCount)); | |
| 384 width_histogram.Count(timeline_width); | |
| 385 } | |
| 386 } | |
| 387 } | |
| 388 | |
| 389 } // namespace blink | |
| OLD | NEW |