Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions common/settings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

namespace flutter {

constexpr FrameTiming::Phase FrameTiming::kPhases[FrameTiming::kCount];

Settings::Settings() = default;

Settings::Settings(const Settings& other) = default;
Expand Down Expand Up @@ -51,6 +53,8 @@ std::string Settings::ToString() const {
stream << "icu_data_path: " << icu_data_path << std::endl;
stream << "assets_dir: " << assets_dir << std::endl;
stream << "assets_path: " << assets_path << std::endl;
stream << "frame_rasterized_callback set: " << !!frame_rasterized_callback
<< std::endl;
return stream.str();
}

Expand Down
23 changes: 23 additions & 0 deletions common/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,27 @@

#include "flutter/fml/closure.h"
#include "flutter/fml/mapping.h"
#include "flutter/fml/time/time_point.h"
#include "flutter/fml/unique_fd.h"

namespace flutter {

class FrameTiming {
public:
enum Phase { kBuildStart, kBuildFinish, kRasterStart, kRasterFinish, kCount };

static constexpr Phase kPhases[kCount] = {kBuildStart, kBuildFinish,
kRasterStart, kRasterFinish};

fml::TimePoint Get(Phase phase) const { return data_[phase]; }
fml::TimePoint Set(Phase phase, fml::TimePoint value) {
return data_[phase] = value;
}

private:
fml::TimePoint data_[kCount];
};

using TaskObserverAdd =
std::function<void(intptr_t /* key */, fml::closure /* callback */)>;
using TaskObserverRemove = std::function<void(intptr_t /* key */)>;
Expand All @@ -32,6 +49,8 @@ using MappingCallback = std::function<std::unique_ptr<fml::Mapping>(void)>;
using MappingsCallback =
std::function<std::vector<std::unique_ptr<const fml::Mapping>>(void)>;

using FrameRasterizedCallback = std::function<void(const FrameTiming&)>;

struct Settings {
Settings();

Expand Down Expand Up @@ -149,6 +168,10 @@ struct Settings {
fml::UniqueFD::traits_type::InvalidValue();
std::string assets_path;

// Callback to handle the timings of a rasterized frame. This is called as
// soon as a frame is rasterized.
FrameRasterizedCallback frame_rasterized_callback;

std::string ToString() const;
};

Expand Down
5 changes: 5 additions & 0 deletions flow/layers/layer_tree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ LayerTree::LayerTree()

LayerTree::~LayerTree() = default;

void LayerTree::RecordBuildTime(fml::TimePoint start) {
build_start_ = start;
build_finish_ = fml::TimePoint::Now();
}

void LayerTree::Preroll(CompositorContext::ScopedFrame& frame,
bool ignore_raster_cache) {
TRACE_EVENT0("flutter", "LayerTree::Preroll");
Expand Down
12 changes: 6 additions & 6 deletions flow/layers/layer_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,10 @@ class LayerTree {

void set_frame_size(const SkISize& frame_size) { frame_size_ = frame_size; }

void set_construction_time(const fml::TimeDelta& delta) {
construction_time_ = delta;
}

const fml::TimeDelta& construction_time() const { return construction_time_; }
void RecordBuildTime(fml::TimePoint begin_start);
fml::TimePoint build_start() const { return build_start_; }
fml::TimePoint build_finish() const { return build_finish_; }
fml::TimeDelta build_time() const { return build_finish_ - build_start_; }

// The number of frame intervals missed after which the compositor must
// trace the rasterized picture to a trace file. Specify 0 to disable all
Expand All @@ -75,7 +74,8 @@ class LayerTree {
private:
SkISize frame_size_; // Physical pixels.
std::shared_ptr<Layer> root_layer_;
fml::TimeDelta construction_time_;
fml::TimePoint build_start_;
fml::TimePoint build_finish_;
uint32_t rasterizer_tracing_threshold_;
bool checkerboard_raster_cache_images_;
bool checkerboard_offscreen_layers_;
Expand Down
116 changes: 116 additions & 0 deletions lib/stub_ui/lib/src/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,98 @@ typedef PlatformMessageResponseCallback = void Function(ByteData data);
typedef PlatformMessageCallback = void Function(
String name, ByteData data, PlatformMessageResponseCallback callback);

/// Various important time points in the lifetime of a frame.
///
/// [FrameTiming] records a timestamp of each phase for performance analysis.
enum FramePhase {
/// When the UI thread starts building a frame.
///
/// See also [FrameTiming.buildDuration].
buildStart,

/// When the UI thread finishes building a frame.
///
/// See also [FrameTiming.buildDuration].
buildFinish,

/// When the GPU thread starts rasterizing a frame.
///
/// See also [FrameTiming.rasterDuration].
rasterStart,

/// When the GPU thread finishes rasterizing a frame.
///
/// See also [FrameTiming.rasterDuration].
rasterFinish,
}

/// Time-related performance metrics of a frame.
///
/// See [Window.onReportTimings] for how to get this.
///
/// The metrics in debug mode (`flutter run` without any flags) may be very
/// different from those in profile and release modes due to the debug overhead.
/// Therefore it's recommended to only monitor and analyze performance metrics
/// in profile and release modes.
class FrameTiming {
/// Construct [FrameTiming] with raw timestamps in microseconds.
///
/// List [timestamps] must have the same number of elements as
/// [FramePhase.values].
///
/// This constructor is usually only called by the Flutter engine, or a test.
/// To get the [FrameTiming] of your app, see [Window.onReportTimings].
FrameTiming(List<int> timestamps)
: assert(timestamps.length == FramePhase.values.length), _timestamps = timestamps;

/// This is a raw timestamp in microseconds from some epoch. The epoch in all
/// [FrameTiming] is the same, but it may not match [DateTime]'s epoch.
int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index];

Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]);

/// The duration to build the frame on the UI thread.
///
/// The build starts approximately when [Window.onBeginFrame] is called. The
/// [Duration] in the [Window.onBeginFrame] callback is exactly the
/// `Duration(microseconds: timestampInMicroseconds(FramePhase.buildStart))`.
///
/// The build finishes when [Window.render] is called.
///
/// {@template dart.ui.FrameTiming.fps_smoothness_milliseconds}
/// To ensure smooth animations of X fps, this should not exceed 1000/X
/// milliseconds.
/// {@endtemplate}
/// {@template dart.ui.FrameTiming.fps_milliseconds}
/// That's about 16ms for 60fps, and 8ms for 120fps.
/// {@endtemplate}
Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart);

/// The duration to rasterize the frame on the GPU thread.
///
/// {@macro dart.ui.FrameTiming.fps_smoothness_milliseconds}
/// {@macro dart.ui.FrameTiming.fps_milliseconds}
Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart);

/// The timespan between build start and raster finish.
///
/// To achieve the lowest latency on an X fps display, this should not exceed
/// 1000/X milliseconds.
/// {@macro dart.ui.FrameTiming.fps_milliseconds}
///
/// See also [buildDuration] and [rasterDuration].
Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.buildStart);

final List<int> _timestamps; // in microseconds

String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms';

@override
String toString() {
return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, totalSpan: ${_formatMS(totalSpan)})';
}
}

/// States that an application can be in.
///
/// The values below describe notifications from the operating system.
Expand Down Expand Up @@ -771,6 +863,30 @@ abstract class Window {
_onDrawFrame = callback;
}

/// A callback that is invoked to report the [FrameTiming] of recently
/// rasterized frames.
///
/// This can be used to see if the application has missed frames (through
/// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high
/// latencies (through [FrameTiming.totalSpan]).
///
/// Unlike [Timeline], the timing information here is available in the release
/// mode (additional to the profile and the debug mode). Hence this can be
/// used to monitor the application's performance in the wild.
///
/// The callback may not be immediately triggered after each frame. Instead,
/// it tries to batch frames together and send all their timings at once to
/// decrease the overhead (as this is available in the release mode). The
/// timing of any frame will be sent within about 1 second even if there are
/// no later frames to batch.
TimingsCallback get onReportTimings => _onReportTimings;
TimingsCallback _onReportTimings;
Zone _onReportTimingsZone;
set onReportTimings(TimingsCallback callback) {
_onReportTimings = callback;
_onReportTimingsZone = Zone.current;
}

/// A callback that is invoked when pointer data is available.
///
/// The framework invokes this callback in the same zone in which the
Expand Down
11 changes: 11 additions & 0 deletions lib/ui/hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,17 @@ void _beginFrame(int microseconds) {
_invoke1<Duration>(window.onBeginFrame, window._onBeginFrameZone, Duration(microseconds: microseconds));
}

@pragma('vm:entry-point')
// ignore: unused_element
void _reportTimings(List<int> timings) {
assert(timings.length % FramePhase.values.length == 0);
final List<FrameTiming> frameTimings = <FrameTiming>[];
for (int i = 0; i < timings.length; i += FramePhase.values.length) {
frameTimings.add(FrameTiming(timings.sublist(i, i + FramePhase.values.length)));
}
_invoke1(window.onReportTimings, window._onReportTimingsZone, frameTimings);
}

@pragma('vm:entry-point')
// ignore: unused_element
void _drawFrame() {
Expand Down
Loading