1// Copyright 2013 The Flutter 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 "flutter/flow/instrumentation.h"
6
7#include <algorithm>
8#include <limits>
9
10#include "third_party/skia/include/core/SkPath.h"
11#include "third_party/skia/include/core/SkSurface.h"
12
13namespace flutter {
14
15static const size_t kMaxSamples = 120;
16static const size_t kMaxFrameMarkers = 8;
17
18Stopwatch::Stopwatch(fml::Milliseconds frame_budget)
19 : start_(fml::TimePoint::Now()), current_sample_(0) {
20 const fml::TimeDelta delta = fml::TimeDelta::Zero();
21 laps_.resize(kMaxSamples, delta);
22 cache_dirty_ = true;
23 prev_drawn_sample_index_ = 0;
24 frame_budget_ = frame_budget;
25}
26
27Stopwatch::~Stopwatch() = default;
28
29void Stopwatch::Start() {
30 start_ = fml::TimePoint::Now();
31 current_sample_ = (current_sample_ + 1) % kMaxSamples;
32}
33
34void Stopwatch::Stop() {
35 laps_[current_sample_] = fml::TimePoint::Now() - start_;
36}
37
38void Stopwatch::SetLapTime(const fml::TimeDelta& delta) {
39 current_sample_ = (current_sample_ + 1) % kMaxSamples;
40 laps_[current_sample_] = delta;
41}
42
43const fml::TimeDelta& Stopwatch::LastLap() const {
44 return laps_[(current_sample_ - 1) % kMaxSamples];
45}
46
47double Stopwatch::UnitFrameInterval(double raster_time_ms) const {
48 return raster_time_ms / frame_budget_.count();
49}
50
51double Stopwatch::UnitHeight(double raster_time_ms,
52 double max_unit_interval) const {
53 double unitHeight = UnitFrameInterval(raster_time_ms) / max_unit_interval;
54 if (unitHeight > 1.0) {
55 unitHeight = 1.0;
56 }
57 return unitHeight;
58}
59
60fml::TimeDelta Stopwatch::MaxDelta() const {
61 fml::TimeDelta max_delta;
62 for (size_t i = 0; i < kMaxSamples; i++) {
63 if (laps_[i] > max_delta) {
64 max_delta = laps_[i];
65 }
66 }
67 return max_delta;
68}
69
70fml::TimeDelta Stopwatch::AverageDelta() const {
71 fml::TimeDelta sum; // default to 0
72 for (size_t i = 0; i < kMaxSamples; i++) {
73 sum = sum + laps_[i];
74 }
75 return sum / kMaxSamples;
76}
77
78// Initialize the SkSurface for drawing into. Draws the base background and any
79// timing data from before the initial Visualize() call.
80void Stopwatch::InitVisualizeSurface(const SkRect& rect) const {
81 if (!cache_dirty_) {
82 return;
83 }
84 cache_dirty_ = false;
85
86 // TODO(garyq): Use a GPU surface instead of a CPU surface.
87 visualize_cache_surface_ =
88 SkSurface::MakeRasterN32Premul(rect.width(), rect.height());
89
90 SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas();
91
92 // Establish the graph position.
93 const SkScalar x = 0;
94 const SkScalar y = 0;
95 const SkScalar width = rect.width();
96 const SkScalar height = rect.height();
97
98 SkPaint paint;
99 paint.setColor(0x99FFFFFF);
100 cache_canvas->drawRect(SkRect::MakeXYWH(x, y, width, height), paint);
101
102 // Scale the graph to show frame times up to those that are 3 times the frame
103 // time.
104 const double one_frame_ms = frame_budget_.count();
105 const double max_interval = one_frame_ms * 3.0;
106 const double max_unit_interval = UnitFrameInterval(max_interval);
107
108 // Draw the old data to initially populate the graph.
109 // Prepare a path for the data. We start at the height of the last point, so
110 // it looks like we wrap around
111 SkPath path;
112 path.setIsVolatile(true);
113 path.moveTo(x, height);
114 path.lineTo(x, y + height * (1.0 - UnitHeight(laps_[0].ToMillisecondsF(),
115 max_unit_interval)));
116 double unit_x;
117 double unit_next_x = 0.0;
118 for (size_t i = 0; i < kMaxSamples; i += 1) {
119 unit_x = unit_next_x;
120 unit_next_x = (static_cast<double>(i + 1) / kMaxSamples);
121 const double sample_y =
122 y + height * (1.0 - UnitHeight(laps_[i].ToMillisecondsF(),
123 max_unit_interval));
124 path.lineTo(x + width * unit_x, sample_y);
125 path.lineTo(x + width * unit_next_x, sample_y);
126 }
127 path.lineTo(
128 width,
129 y + height * (1.0 - UnitHeight(laps_[kMaxSamples - 1].ToMillisecondsF(),
130 max_unit_interval)));
131 path.lineTo(width, height);
132 path.close();
133
134 // Draw the graph.
135 paint.setColor(0xAA0000FF);
136 cache_canvas->drawPath(path, paint);
137}
138
139void Stopwatch::Visualize(SkCanvas* canvas, const SkRect& rect) const {
140 // Initialize visualize cache if it has not yet been initialized.
141 InitVisualizeSurface(rect);
142
143 SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas();
144 SkPaint paint;
145
146 // Establish the graph position.
147 const SkScalar x = 0;
148 const SkScalar y = 0;
149 const SkScalar width = rect.width();
150 const SkScalar height = rect.height();
151
152 // Scale the graph to show frame times up to those that are 3 times the frame
153 // time.
154 const double one_frame_ms = frame_budget_.count();
155 const double max_interval = one_frame_ms * 3.0;
156 const double max_unit_interval = UnitFrameInterval(max_interval);
157
158 const double sample_unit_width = (1.0 / kMaxSamples);
159
160 // Draw vertical replacement bar to erase old/stale pixels.
161 paint.setColor(0x99FFFFFF);
162 paint.setStyle(SkPaint::Style::kFill_Style);
163 paint.setBlendMode(SkBlendMode::kSrc);
164 double sample_x =
165 x + width * (static_cast<double>(prev_drawn_sample_index_) / kMaxSamples);
166 const auto eraser_rect = SkRect::MakeLTRB(
167 sample_x, y, sample_x + width * sample_unit_width, height);
168 cache_canvas->drawRect(eraser_rect, paint);
169
170 // Draws blue timing bar for new data.
171 paint.setColor(0xAA0000FF);
172 paint.setBlendMode(SkBlendMode::kSrcOver);
173 const auto bar_rect = SkRect::MakeLTRB(
174 sample_x,
175 y + height * (1.0 -
176 UnitHeight(laps_[current_sample_ == 0 ? kMaxSamples - 1
177 : current_sample_ - 1]
178 .ToMillisecondsF(),
179 max_unit_interval)),
180 sample_x + width * sample_unit_width, height);
181 cache_canvas->drawRect(bar_rect, paint);
182
183 // Draw horizontal frame markers.
184 paint.setStrokeWidth(0); // hairline
185 paint.setStyle(SkPaint::Style::kStroke_Style);
186 paint.setColor(0xCC000000);
187
188 if (max_interval > one_frame_ms) {
189 // Paint the horizontal markers
190 size_t frame_marker_count =
191 static_cast<size_t>(max_interval / one_frame_ms);
192
193 // Limit the number of markers displayed. After a certain point, the graph
194 // becomes crowded
195 if (frame_marker_count > kMaxFrameMarkers) {
196 frame_marker_count = 1;
197 }
198
199 for (size_t frame_index = 0; frame_index < frame_marker_count;
200 frame_index++) {
201 const double frame_height =
202 height * (1.0 - (UnitFrameInterval((frame_index + 1) * one_frame_ms) /
203 max_unit_interval));
204 cache_canvas->drawLine(x, y + frame_height, width, y + frame_height,
205 paint);
206 }
207 }
208
209 // Paint the vertical marker for the current frame.
210 // We paint it over the current frame, not after it, because when we
211 // paint this we don't yet have all the times for the current frame.
212 paint.setStyle(SkPaint::Style::kFill_Style);
213 paint.setBlendMode(SkBlendMode::kSrcOver);
214 if (UnitFrameInterval(LastLap().ToMillisecondsF()) > 1.0) {
215 // budget exceeded
216 paint.setColor(SK_ColorRED);
217 } else {
218 // within budget
219 paint.setColor(SK_ColorGREEN);
220 }
221 sample_x = x + width * (static_cast<double>(current_sample_) / kMaxSamples);
222 const auto marker_rect = SkRect::MakeLTRB(
223 sample_x, y, sample_x + width * sample_unit_width, height);
224 cache_canvas->drawRect(marker_rect, paint);
225 prev_drawn_sample_index_ = current_sample_;
226
227 // Draw the cached surface onto the output canvas.
228 paint.reset();
229 visualize_cache_surface_->draw(canvas, rect.x(), rect.y(), &paint);
230}
231
232CounterValues::CounterValues() : current_sample_(kMaxSamples - 1) {
233 values_.resize(kMaxSamples, 0);
234}
235
236CounterValues::~CounterValues() = default;
237
238void CounterValues::Add(int64_t value) {
239 current_sample_ = (current_sample_ + 1) % kMaxSamples;
240 values_[current_sample_] = value;
241}
242
243void CounterValues::Visualize(SkCanvas* canvas, const SkRect& rect) const {
244 size_t max_bytes = GetMaxValue();
245
246 if (max_bytes == 0) {
247 // The backend for this counter probably did not fill in any values.
248 return;
249 }
250
251 size_t min_bytes = GetMinValue();
252
253 SkPaint paint;
254
255 // Paint the background.
256 paint.setColor(0x99FFFFFF);
257 canvas->drawRect(rect, paint);
258
259 // Establish the graph position.
260 const SkScalar x = rect.x();
261 const SkScalar y = rect.y();
262 const SkScalar width = rect.width();
263 const SkScalar height = rect.height();
264 const SkScalar bottom = y + height;
265 const SkScalar right = x + width;
266
267 // Prepare a path for the data.
268 SkPath path;
269 path.moveTo(x, bottom);
270
271 for (size_t i = 0; i < kMaxSamples; ++i) {
272 int64_t current_bytes = values_[i];
273 double ratio = static_cast<double>(current_bytes - min_bytes) /
274 static_cast<double>(max_bytes - min_bytes);
275 path.lineTo(
276 x + ((static_cast<double>(i) / static_cast<double>(kMaxSamples)) *
277 width),
278 y + ((1.0 - ratio) * height));
279 }
280
281 path.rLineTo(100, 0);
282 path.lineTo(right, bottom);
283 path.close();
284
285 // Draw the graph.
286 paint.setColor(0xAA0000FF);
287 canvas->drawPath(path, paint);
288
289 // Paint the vertical marker for the current frame.
290 const double sample_unit_width = (1.0 / kMaxSamples);
291 const double sample_margin_unit_width = sample_unit_width / 6.0;
292 const double sample_margin_width = width * sample_margin_unit_width;
293 paint.setStyle(SkPaint::Style::kFill_Style);
294 paint.setColor(SK_ColorGRAY);
295 double sample_x =
296 x + width * (static_cast<double>(current_sample_) / kMaxSamples) -
297 sample_margin_width;
298 const auto marker_rect = SkRect::MakeLTRB(
299 sample_x, y,
300 sample_x + width * sample_unit_width + sample_margin_width * 2, bottom);
301 canvas->drawRect(marker_rect, paint);
302}
303
304int64_t CounterValues::GetCurrentValue() const {
305 return values_[current_sample_];
306}
307
308int64_t CounterValues::GetMaxValue() const {
309 auto max = std::numeric_limits<int64_t>::min();
310 for (size_t i = 0; i < kMaxSamples; ++i) {
311 max = std::max<int64_t>(max, values_[i]);
312 }
313 return max;
314}
315
316int64_t CounterValues::GetMinValue() const {
317 auto min = std::numeric_limits<int64_t>::max();
318 for (size_t i = 0; i < kMaxSamples; ++i) {
319 min = std::min<int64_t>(min, values_[i]);
320 }
321 return min;
322}
323
324} // namespace flutter
325