summaryrefslogtreecommitdiff
path: root/chromium/cc/metrics/jank_metrics.cc
blob: 9201516a7ab1781ac2903a77a3cbbac6db419f1f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/metrics/jank_metrics.h"

#include <algorithm>
#include <memory>
#include <string>
#include <utility>

#include "base/metrics/histogram.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/trace_event/trace_event.h"
#include "cc/metrics/frame_sequence_tracker.h"

namespace cc {

namespace {

constexpr int kBuiltinJankSequenceNum =
    static_cast<int>(FrameSequenceTrackerType::kMaxType) + 1;
constexpr int kMaximumJankHistogramIndex = 2 * kBuiltinJankSequenceNum;
constexpr int kMaximumStaleHistogramIndex = kBuiltinJankSequenceNum;

constexpr base::TimeDelta kStaleHistogramMin =
    base::TimeDelta::FromMicroseconds(1);
constexpr base::TimeDelta kStaleHistogramMax =
    base::TimeDelta::FromMilliseconds(1000);
constexpr int kStaleHistogramBucketCount = 200;

constexpr bool IsValidJankThreadType(FrameSequenceMetrics::ThreadType type) {
  return type == FrameSequenceMetrics::ThreadType::kCompositor ||
         type == FrameSequenceMetrics::ThreadType::kMain;
}

const char* GetJankThreadTypeName(FrameSequenceMetrics::ThreadType type) {
  DCHECK(IsValidJankThreadType(type));

  switch (type) {
    case FrameSequenceMetrics::ThreadType::kCompositor:
      return "Compositor";
    case FrameSequenceMetrics::ThreadType::kMain:
      return "Main";
    default:
      NOTREACHED();
      return "";
  }
}

int GetIndexForJankMetric(FrameSequenceMetrics::ThreadType thread_type,
                          FrameSequenceTrackerType type) {
  DCHECK(IsValidJankThreadType(thread_type));
  if (thread_type == FrameSequenceMetrics::ThreadType::kMain)
    return static_cast<int>(type);

  DCHECK_EQ(thread_type, FrameSequenceMetrics::ThreadType::kCompositor);
  return static_cast<int>(type) + kBuiltinJankSequenceNum;
}

int GetIndexForStaleMetric(FrameSequenceTrackerType type) {
  return static_cast<int>(type);
}

std::string GetJankHistogramName(FrameSequenceTrackerType type,
                                 const char* thread_name) {
  return base::StrCat(
      {"Graphics.Smoothness.Jank.", thread_name, ".",
       FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
}

std::string GetStaleHistogramName(FrameSequenceTrackerType type) {
  return base::StrCat(
      {"Graphics.Smoothness.Stale.",
       FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
}

std::string GetMaxStaleHistogramName(FrameSequenceTrackerType type) {
  return base::StrCat(
      {"Graphics.Smoothness.MaxStale.",
       FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
}

}  // namespace

JankMetrics::JankMetrics(FrameSequenceTrackerType tracker_type,
                         FrameSequenceMetrics::ThreadType effective_thread)
    : tracker_type_(tracker_type), effective_thread_(effective_thread) {
  DCHECK(IsValidJankThreadType(effective_thread));
}
JankMetrics::~JankMetrics() = default;

void JankMetrics::AddSubmitFrame(uint32_t frame_token,
                                 uint32_t sequence_number) {
  // When a frame is submitted, record its |frame_token| and its associated
  // |sequence_number|. This pushed item will be removed when this frame is
  // presented.
  queue_frame_token_and_id_.push({frame_token, sequence_number});
}

void JankMetrics::AddFrameWithNoUpdate(uint32_t sequence_number,
                                       base::TimeDelta frame_interval) {
  // If a frame does not cause an increase in expected frames, it will be
  // recorded here and later subtracted from the presentation interval that
  // includes this frame.
  queue_frame_id_and_interval_.push({sequence_number, frame_interval});
}

void JankMetrics::AddPresentedFrame(
    uint32_t presented_frame_token,
    base::TimeTicks current_presentation_timestamp,
    base::TimeDelta frame_interval) {
  uint32_t presented_frame_id = 0;

  // Find the main_sequence_number of the presented_frame_token
  while (!queue_frame_token_and_id_.empty()) {
    auto token_and_id = queue_frame_token_and_id_.front();

    if (token_and_id.first > presented_frame_token) {
      // The submitting of this presented frame was not recorded (e.g. the
      // submitting might have occurred before JankMetrics starts recording).
      // In that case, do not use this frame presentation for jank detection.
      return;
    }
    queue_frame_token_and_id_.pop();

    if (token_and_id.first == presented_frame_token) {
      // Found information about the submit of this presented frame;
      // retrieve the frame's sequence number.
      presented_frame_id = token_and_id.second;
      break;
    }
  }
  // If for any reason the sequence number associated with the
  // presented_frame_token cannot be identified, then ignore this frame
  // presentation.
  if (presented_frame_id == 0)
    return;

  base::TimeDelta no_update_time;  // The frame time spanned by the frames that
                                   // have no updates

  // Compute the presentation delay contributed by no-update frames that began
  // BEFORE (i.e. have smaller sequence number than) the current presented
  // frame.
  while (!queue_frame_id_and_interval_.empty() &&
         queue_frame_id_and_interval_.front().first < presented_frame_id) {
    auto id_and_interval = queue_frame_id_and_interval_.front();
    if (id_and_interval.first >= last_presentation_frame_id_) {
      // Only count no-update frames that began SINCE (i.e. have a greater [or
      // equal] sequence number than) the beginning of previous presented frame.
      // If, in rare cases, there are still no-update frames that began BEFORE
      // the beginning of previous presented frame left in the queue, those
      // frames will simply be discarded and not counted into |no_update_time|.
      no_update_time += id_and_interval.second;
    }
    queue_frame_id_and_interval_.pop();
  }

  // Exclude the presentation delay introduced by no-update frames. If this
  // exclusion results in negative frame delta, treat the frame delta as 0.
  base::TimeDelta current_frame_delta = current_presentation_timestamp -
                                        last_presentation_timestamp_ -
                                        no_update_time;
  const base::TimeDelta zero_delta = base::TimeDelta::FromMilliseconds(0);

  // Guard against the situation when the physical presentation interval is
  // shorter than |no_update_time|. For example, consider two BeginFrames A and
  // B separated by 5 vsync cycles of no-updates (i.e. |no_update_time| = 5
  // vsync cycles); the Presentation of A occurs 2 vsync cycles after BeginFrame
  // A, whereas Presentation B occurs in the same vsync cycle as BeginFrame B.
  // In this situation, the physical presentation interval is shorter than 5
  // vsync cycles and will result in a negative |current_frame_delta|.
  if (current_frame_delta < zero_delta)
    current_frame_delta = zero_delta;

  // Only start tracking jank if this function has already been
  // called at least once (so that |last_presentation_timestamp_|
  // and |prev_frame_delta_| have been set).
  //
  // The presentation interval is typically a multiple of VSync
  // intervals (i.e. 16.67ms, 33.33ms, 50ms ... on a 60Hz display)
  // with small fluctuations. The 0.5 * |frame_interval| criterion
  // is chosen so that the jank detection is robust to those
  // fluctuations.
  if (!last_presentation_timestamp_.is_null()) {
    base::TimeDelta staleness = current_frame_delta - frame_interval;
    if (staleness < zero_delta)
      staleness = zero_delta;

    if (tracker_type_ != FrameSequenceTrackerType::kCustom) {
      STATIC_HISTOGRAM_POINTER_GROUP(
          GetStaleHistogramName(tracker_type_),
          GetIndexForStaleMetric(tracker_type_), kMaximumStaleHistogramIndex,
          AddTimeMillisecondsGranularity(staleness),
          base::Histogram::FactoryTimeGet(
              GetStaleHistogramName(tracker_type_), kStaleHistogramMin,
              kStaleHistogramMax, kStaleHistogramBucketCount,
              base::HistogramBase::kUmaTargetedHistogramFlag));
      if (staleness > max_staleness_)
        max_staleness_ = staleness;
    }

    if (!prev_frame_delta_.is_zero() &&
        current_frame_delta > prev_frame_delta_ + 0.5 * frame_interval) {
      jank_count_++;

      TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1(
          "cc,benchmark", "Jank", TRACE_ID_LOCAL(this),
          last_presentation_timestamp_, "thread-type",
          GetJankThreadTypeName(effective_thread_));
      TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP1(
          "cc,benchmark", "Jank", TRACE_ID_LOCAL(this),
          current_presentation_timestamp, "tracker-type",
          FrameSequenceTracker::GetFrameSequenceTrackerTypeName(tracker_type_));
    }
  }
  last_presentation_timestamp_ = current_presentation_timestamp;
  last_presentation_frame_id_ = presented_frame_id;
  prev_frame_delta_ = current_frame_delta;
}

void JankMetrics::ReportJankMetrics(int frames_expected) {
  if (tracker_type_ == FrameSequenceTrackerType::kCustom)
    return;

  int jank_percent = static_cast<int>(100 * jank_count_ / frames_expected);

  const char* jank_thread_name = GetJankThreadTypeName(effective_thread_);

  STATIC_HISTOGRAM_POINTER_GROUP(
      GetJankHistogramName(tracker_type_, jank_thread_name),
      GetIndexForJankMetric(effective_thread_, tracker_type_),
      kMaximumJankHistogramIndex, Add(jank_percent),
      base::LinearHistogram::FactoryGet(
          GetJankHistogramName(tracker_type_, jank_thread_name), 1, 100, 101,
          base::HistogramBase::kUmaTargetedHistogramFlag));

  // Report the max staleness metrics
  STATIC_HISTOGRAM_POINTER_GROUP(
      GetMaxStaleHistogramName(tracker_type_),
      GetIndexForStaleMetric(tracker_type_), kMaximumStaleHistogramIndex,
      AddTimeMillisecondsGranularity(max_staleness_),
      base::Histogram::FactoryTimeGet(
          GetMaxStaleHistogramName(tracker_type_), kStaleHistogramMin,
          kStaleHistogramMax, kStaleHistogramBucketCount,
          base::HistogramBase::kUmaTargetedHistogramFlag));

  // Reset counts to avoid duplicated reporting.
  Reset();
}

void JankMetrics::Reset() {
  jank_count_ = 0;
  max_staleness_ = {};
}

void JankMetrics::Merge(std::unique_ptr<JankMetrics> jank_metrics) {
  if (jank_metrics) {
    jank_count_ += jank_metrics->jank_count_;
    max_staleness_ = std::max(max_staleness_, jank_metrics->max_staleness_);
  }
}

}  // namespace cc