ambient_multi_screen_metrics_recorder.cc 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // Copyright 2022 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. #include "ash/ambient/metrics/ambient_multi_screen_metrics_recorder.h"
  5. #include <algorithm>
  6. #include "base/check.h"
  7. #include "base/logging.h"
  8. #include "base/metrics/histogram_functions.h"
  9. #include "base/metrics/histogram_macros.h"
  10. #include "base/strings/strcat.h"
  11. namespace ash {
  12. AmbientMultiScreenMetricsRecorder::AmbientMultiScreenMetricsRecorder(
  13. AmbientAnimationTheme theme)
  14. : theme_(theme) {}
  15. AmbientMultiScreenMetricsRecorder::~AmbientMultiScreenMetricsRecorder() {
  16. base::UmaHistogramCounts100(
  17. base::StrCat({"Ash.AmbientMode.ScreenCount.", ToString(theme_)}),
  18. num_registered_screens_);
  19. }
  20. void AmbientMultiScreenMetricsRecorder::RegisterScreen(
  21. lottie::Animation* animation) {
  22. ++num_registered_screens_;
  23. if (!animation)
  24. return;
  25. DCHECK(!animation_observations_.IsObservingSource(animation));
  26. registered_animations_.insert(animation);
  27. animation_observations_.AddObservation(animation);
  28. }
  29. void AmbientMultiScreenMetricsRecorder::AnimationFramePainted(
  30. const lottie::Animation* animation,
  31. float t) {
  32. if (registered_animations_.size() <= 1u) {
  33. DVLOG(4) << "Not computing mean timestamp offset for single screen";
  34. return;
  35. }
  36. // Out of the N animations, find the pair with the largest timestamp offset.
  37. // The below does it brute force in N^2 time because it's simplest and N
  38. // (the number of screens present) is realistically going to be very small.
  39. // It's not worth optimizing.
  40. absl::optional<base::TimeDelta> largest_timestamp_offset;
  41. for (auto animation_l = registered_animations_.begin();
  42. animation_l != registered_animations_.end(); ++animation_l) {
  43. for (auto animation_r = animation_l + 1;
  44. animation_r != registered_animations_.end(); ++animation_r) {
  45. absl::optional<base::TimeDelta> offset =
  46. GetOffsetBetweenAnimations(**animation_l, **animation_r);
  47. if (!offset) {
  48. DVLOG(4)
  49. << "One or both animations are inactive. Cannot compute offset";
  50. continue;
  51. }
  52. if (!largest_timestamp_offset || *offset > *largest_timestamp_offset) {
  53. largest_timestamp_offset = offset;
  54. }
  55. }
  56. }
  57. if (!largest_timestamp_offset) {
  58. DVLOG(4) << "At least 2 animations need to be active to compute an offset";
  59. return;
  60. }
  61. // Since this metric is recorded on every single animation frame, the UMA
  62. // histogram macros are used for performance reasons. They require a static
  63. // compile-time metric name as an argument though.
  64. #define MUTLISCREEN_OFFSET_NAME(theme) \
  65. "Ash.AmbientMode.MultiScreenOffset." theme
  66. switch (theme_) {
  67. case AmbientAnimationTheme::kFeelTheBreeze:
  68. UMA_HISTOGRAM_TIMES(MUTLISCREEN_OFFSET_NAME("FeelTheBreeze"),
  69. *largest_timestamp_offset);
  70. break;
  71. case AmbientAnimationTheme::kFloatOnBy:
  72. UMA_HISTOGRAM_TIMES(MUTLISCREEN_OFFSET_NAME("FloatOnBy"),
  73. *largest_timestamp_offset);
  74. break;
  75. case AmbientAnimationTheme::kSlideshow:
  76. LOG(DFATAL) << "Should not be recording animation metrics for slideshow";
  77. break;
  78. }
  79. #undef MUTLISCREEN_OFFSET_NAME
  80. }
  81. void AmbientMultiScreenMetricsRecorder::AnimationIsDeleting(
  82. const lottie::Animation* animation) {
  83. animation_observations_.RemoveObservation(
  84. const_cast<lottie::Animation*>(animation));
  85. // Remove from |registered_animations_| here to prevent any possibility of
  86. // use-after-free if AnimationFramePainted() happens to be called for a
  87. // remaining animation.
  88. registered_animations_.erase(animation);
  89. }
  90. absl::optional<base::TimeDelta>
  91. AmbientMultiScreenMetricsRecorder::GetOffsetBetweenAnimations(
  92. const lottie::Animation& animation_l,
  93. const lottie::Animation& animation_r) const {
  94. absl::optional<float> current_progress_l = animation_l.GetCurrentProgress();
  95. absl::optional<float> current_progress_r = animation_r.GetCurrentProgress();
  96. if (!current_progress_l || !current_progress_r) {
  97. DVLOG(4) << "Both animations must be active (playing and painted at least "
  98. "1 frame) to compute an offset";
  99. return absl::nullopt;
  100. }
  101. const lottie::Animation* animation_with_smaller_t = nullptr;
  102. const lottie::Animation* animation_with_larger_t = nullptr;
  103. base::TimeDelta smaller_timestamp;
  104. base::TimeDelta larger_timestamp;
  105. if (*current_progress_l < *current_progress_r) {
  106. animation_with_smaller_t = &animation_l;
  107. animation_with_larger_t = &animation_r;
  108. smaller_timestamp =
  109. *current_progress_l * animation_l.GetAnimationDuration();
  110. larger_timestamp = *current_progress_r * animation_r.GetAnimationDuration();
  111. } else {
  112. animation_with_smaller_t = &animation_r;
  113. animation_with_larger_t = &animation_l;
  114. smaller_timestamp =
  115. *current_progress_r * animation_r.GetAnimationDuration();
  116. larger_timestamp = *current_progress_l * animation_l.GetAnimationDuration();
  117. }
  118. // Take the smaller of incremental (a normal forward animation step) and
  119. // loopback progress. Ex:
  120. // * Incremental: .5 -> .52 = .02
  121. // * Loopback: .98 -> .02 = .04 (whereas the incremental would be .96, which
  122. // does not make sense).
  123. base::TimeDelta incremental_progress = larger_timestamp - smaller_timestamp;
  124. DCHECK(IsPlaybackConfigValid(animation_with_larger_t->GetPlaybackConfig()));
  125. DCHECK(IsPlaybackConfigValid(animation_with_smaller_t->GetPlaybackConfig()));
  126. absl::optional<lottie::Animation::CycleBoundaries> larger_t_cycle =
  127. animation_with_larger_t->GetCurrentCycleBoundaries();
  128. absl::optional<lottie::Animation::CycleBoundaries> smaller_t_cycle =
  129. animation_with_smaller_t->GetCurrentCycleBoundaries();
  130. DCHECK(larger_t_cycle);
  131. DCHECK(smaller_t_cycle);
  132. // Note the animations may not loop from [0, 1]. They may start and end their
  133. // loops at arbitrary points in the middle. For example, if the start/end
  134. // points are [.25, .75], and the 2 timestamps are 0.73 and 0.26, the
  135. // offset would be (.75 - .73) + (.26 - .25) = .03.
  136. base::TimeDelta looped_progress =
  137. (larger_t_cycle->end_offset - larger_timestamp) +
  138. (smaller_timestamp - smaller_t_cycle->start_offset);
  139. return std::min(incremental_progress, looped_progress);
  140. }
  141. // static
  142. bool AmbientMultiScreenMetricsRecorder::IsPlaybackConfigValid(
  143. const absl::optional<lottie::Animation::PlaybackConfig>& playback_config) {
  144. return playback_config &&
  145. // The logic in GetOffsetBetweenAnimations() assumes animation time
  146. // always ticks forward.
  147. playback_config->style != lottie::Animation::Style::kThrobbing;
  148. }
  149. } // namespace ash