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