blob: 4d9f8dd4c89a4e56d61abef0e8be6e52ea38bafe [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 "src/ui/scenic/lib/scheduling/frame_stats.h"
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <lib/zx/resource.h>
#include <math.h>
#include <chrono>
#include <list>
#include <string>
#include "src/ui/scenic/lib/scheduling/frame_metrics_registry.cb.h"
using cobalt_registry::kScenicLatchToActualPresentationMetricId;
using cobalt_registry::kScenicRenderTimeMetricId;
using CobaltFrameStatus =
cobalt_registry::ScenicLatchToActualPresentationMetricDimensionFrameStatus;
using cobalt_registry::kScenicRenderTimeIntBucketsFloor;
using cobalt_registry::kScenicRenderTimeIntBucketsNumBuckets;
using cobalt_registry::kScenicRenderTimeIntBucketsStepSize;
namespace scheduling {
namespace {
uint64_t TimestampsToMinuteKey(const FrameStats::Timestamps& timestamps) {
return timestamps.latch_point_time.get() / zx::min(1).get();
}
} // anonymous namespace
FrameStats::FrameStats(inspect::Node inspect_node,
std::shared_ptr<cobalt::CobaltLogger> cobalt_logger)
: inspect_node_(std::move(inspect_node)), cobalt_logger_(std::move(cobalt_logger)) {
inspect_frame_stats_dump_ = inspect_node_.CreateLazyValues("Aggregate Stats", [this] {
inspect::Inspector insp;
ReportStats(&insp);
return fit::make_ok_promise(std::move(insp));
});
InitializeFrameTimeBucketConfig();
cobalt_logging_task_.PostDelayed(async_get_default_dispatcher(), kCobaltDataCollectionInterval);
}
void FrameStats::InitializeFrameTimeBucketConfig() {
cobalt::IntegerBuckets bucket_proto;
cobalt::LinearIntegerBuckets* linear = bucket_proto.mutable_linear();
linear->set_floor(kScenicRenderTimeIntBucketsFloor);
linear->set_num_buckets(kScenicRenderTimeIntBucketsNumBuckets);
linear->set_step_size(kScenicRenderTimeIntBucketsStepSize);
frame_times_bucket_config_ = cobalt::config::IntegerBucketConfig::CreateFromProto(bucket_proto);
}
void FrameStats::RecordFrame(Timestamps timestamps, zx::duration display_vsync_interval) {
++frame_count_;
uint32_t latch_to_actual_presentation_bucket_index =
GetCobaltBucketIndex(timestamps.actual_presentation_time - timestamps.latch_point_time);
if (timestamps.actual_presentation_time == FrameRenderer::kTimeDropped) {
RecordDroppedFrame(timestamps);
cobalt_dropped_frame_times_histogram_[latch_to_actual_presentation_bucket_index]++;
} else if (timestamps.actual_presentation_time - (display_vsync_interval / 2) >=
timestamps.target_presentation_time) {
RecordDelayedFrame(timestamps);
cobalt_delayed_frame_times_histogram_[latch_to_actual_presentation_bucket_index]++;
} else {
RecordOnTimeFrame(timestamps);
cobalt_on_time_frame_times_histogram_[latch_to_actual_presentation_bucket_index]++;
}
frame_times_.push_front(timestamps);
if (frame_times_.size() > kNumFramesToReport) {
frame_times_.pop_back();
}
cobalt_render_times_histogram_[GetCobaltBucketIndex(timestamps.render_done_time -
timestamps.render_start_time)]++;
}
uint32_t FrameStats::GetCobaltBucketIndex(zx::duration duration) {
return frame_times_bucket_config_->BucketIndex(duration.to_usecs() / 100);
}
void FrameStats::RecordDroppedFrame(const Timestamps& timestamps) {
++dropped_frame_count_;
dropped_frames_.push_front(timestamps);
if (dropped_frames_.size() > kNumDroppedFramesToReport) {
dropped_frames_.pop_back();
}
AddHistory(HistoryStats{
.key = TimestampsToMinuteKey(timestamps),
.total_frames = 1,
.dropped_frames = 1,
});
}
void FrameStats::RecordDelayedFrame(const Timestamps& timestamps) {
++delayed_frame_count_;
delayed_frames_.push_front(timestamps);
if (delayed_frames_.size() > kNumDelayedFramesToReport) {
delayed_frames_.pop_back();
}
AddHistory(HistoryStats{
.key = TimestampsToMinuteKey(timestamps),
.total_frames = 1,
.rendered_frames = 1,
.delayed_rendered_frames = 1,
.render_time = timestamps.actual_presentation_time - timestamps.latch_point_time,
.delayed_frame_render_time =
timestamps.actual_presentation_time - timestamps.latch_point_time,
});
}
void FrameStats::RecordOnTimeFrame(const Timestamps& timestamps) {
AddHistory(HistoryStats{
.key = TimestampsToMinuteKey(timestamps),
.total_frames = 1,
.rendered_frames = 1,
.render_time = timestamps.actual_presentation_time - timestamps.latch_point_time,
});
}
void FrameStats::AddHistory(const HistoryStats& stats) {
// Ensure we truncated the timestamp to minutes instead of nanoseconds.
FX_DCHECK(stats.key < 1000000000LU);
HistoryStats* target = nullptr;
if (!history_stats_.empty() && history_stats_.back().key == stats.key) {
target = &history_stats_.back();
} else {
target = &history_stats_.emplace_back(HistoryStats{.key = stats.key});
}
(*target) += stats;
while (history_stats_.size() > kNumMinutesHistory) {
history_stats_.pop_front();
}
}
void FrameStats::LogFrameTimes() {
TRACE_DURATION("gfx", "FrameStats::LogFrameTimes");
if (unlikely(cobalt_logger_ == nullptr)) {
FX_LOGS(ERROR) << "Cobalt logger in Scenic is not initialized!";
// Stop logging frame times into Cobalt;
return;
}
if (!cobalt_on_time_frame_times_histogram_.empty()) {
cobalt_logger_->LogIntHistogram(
kScenicLatchToActualPresentationMetricId, CobaltFrameStatus::OnTime, "" /*component*/,
CreateCobaltBucketsFromHistogram(cobalt_on_time_frame_times_histogram_));
cobalt_on_time_frame_times_histogram_.clear();
}
if (!cobalt_dropped_frame_times_histogram_.empty()) {
cobalt_logger_->LogIntHistogram(
kScenicLatchToActualPresentationMetricId, CobaltFrameStatus::Dropped, "" /*component*/,
CreateCobaltBucketsFromHistogram(cobalt_dropped_frame_times_histogram_));
cobalt_dropped_frame_times_histogram_.clear();
}
if (!cobalt_delayed_frame_times_histogram_.empty()) {
cobalt_logger_->LogIntHistogram(
kScenicLatchToActualPresentationMetricId, CobaltFrameStatus::Delayed, "" /*component*/,
CreateCobaltBucketsFromHistogram(cobalt_delayed_frame_times_histogram_));
cobalt_delayed_frame_times_histogram_.clear();
}
if (!cobalt_render_times_histogram_.empty()) {
cobalt_logger_->LogIntHistogram(
kScenicRenderTimeMetricId, 0, "" /*component*/,
CreateCobaltBucketsFromHistogram(cobalt_render_times_histogram_));
cobalt_render_times_histogram_.clear();
}
cobalt_logging_task_.PostDelayed(async_get_default_dispatcher(), kCobaltDataCollectionInterval);
}
std::vector<fuchsia::cobalt::HistogramBucket> FrameStats::CreateCobaltBucketsFromHistogram(
const CobaltFrameHistogram& histogram) {
TRACE_DURATION("gfx", "FrameStats::CreateCobaltBucketsFromHistogram");
std::vector<fuchsia::cobalt::HistogramBucket> buckets;
for (const auto& pair : histogram) {
fuchsia::cobalt::HistogramBucket bucket;
bucket.index = pair.first;
bucket.count = pair.second;
buckets.push_back(std::move(bucket));
}
return buckets;
}
/* static */
zx::duration FrameStats::CalculateMeanDuration(
const std::deque<Timestamps>& timestamps,
std::function<zx::duration(const Timestamps&)> duration_func, uint32_t percentile) {
FX_DCHECK(percentile <= 100);
const size_t num_frames = timestamps.size();
std::list<const zx::duration> durations;
for (auto& times : timestamps) {
durations.emplace_back(duration_func(times));
}
durations.sort(std::greater<zx::duration>());
// Time the sorted durations to only calculate the desired percentile.
double trim_index =
static_cast<double>(num_frames) * static_cast<double>((100 - percentile)) / 100.;
size_t trim = ceil(trim_index);
FX_DCHECK(trim <= num_frames);
for (size_t i = 0; i < trim; i++) {
durations.pop_back();
}
if (durations.size() == 0u) {
return zx::duration(0);
}
auto total_duration = zx::duration(0);
for (auto& duration : durations) {
total_duration += duration;
}
uint64_t num_durations = durations.size();
return total_duration / num_durations;
}
void FrameStats::ReportStats(inspect::Inspector* insp) const {
FX_DCHECK(insp);
FX_DCHECK(dropped_frame_count_ <= frame_count_);
FX_DCHECK(delayed_frame_count_ <= frame_count_);
double dropped_percentage = frame_count_ > 0 ? dropped_frame_count_ * 100.0 / frame_count_ : 0.0;
FX_DCHECK(delayed_frame_count_ <= frame_count_);
double delayed_percentage = frame_count_ > 0 ? delayed_frame_count_ * 100.0 / frame_count_ : 0.0;
// Stats for the entire history.
{
inspect::Node node = insp->GetRoot().CreateChild("0 - Entire History");
node.CreateUint("Total Frame Count", frame_count_, insp);
node.CreateUint("Dropped Frame Count", dropped_frame_count_, insp);
node.CreateDouble("Dropped Frame Percentage", dropped_percentage, insp);
node.CreateUint("Delayed Frame Count (missed VSYNC)", delayed_frame_count_, insp);
node.CreateDouble("Delayed Frame Percentage", delayed_percentage, insp);
insp->emplace(std::move(node));
}
auto prediction_accuracy = [](const Timestamps& times) -> zx::duration {
return times.actual_presentation_time - times.target_presentation_time;
};
auto total_frame_time = [](const Timestamps& times) -> zx::duration {
return times.actual_presentation_time - times.latch_point_time;
};
auto latency = [](const Timestamps& times) -> zx::duration {
return times.actual_presentation_time - times.render_done_time;
};
// Stats for the last kNumFramesToReport frames.
{
inspect::Node node = insp->GetRoot().CreateChild("1 - Recent Frame Stats (times in ms)");
constexpr double kUSecsToMSecs = 0.001;
node.CreateUint("Count", frame_times_.size(), insp);
node.CreateDouble(
"Mean Prediction Accuracy (95 percentile)",
kUSecsToMSecs * CalculateMeanDuration(frame_times_, prediction_accuracy, 95).to_usecs(),
insp);
node.CreateDouble(
"Mean Total Frame Time (95 percentile)",
kUSecsToMSecs * CalculateMeanDuration(frame_times_, total_frame_time, 95).to_usecs(), insp);
node.CreateDouble("Mean Total Frame Latency (95 percentile)",
kUSecsToMSecs * CalculateMeanDuration(frame_times_, latency, 95).to_usecs(),
insp);
insp->emplace(std::move(node));
}
// Stats for the last kNumFramesToReport frames.
{
inspect::Node node =
insp->GetRoot().CreateChild("2 - Recent Delayed Frame Stats (times in ms)");
node.CreateUint("Count", delayed_frames_.size(), insp);
constexpr double kUSecsToMSecs = 0.001;
node.CreateDouble(
"Mean Prediction Accuracy (95 percentile)",
kUSecsToMSecs * CalculateMeanDuration(delayed_frames_, prediction_accuracy, 95).to_usecs(),
insp);
node.CreateDouble(
"Mean Total Frame Time (95 percentile)",
kUSecsToMSecs * CalculateMeanDuration(delayed_frames_, total_frame_time, 95).to_usecs(),
insp);
node.CreateDouble(
"Mean Total Frame Latency (95 percentile)",
kUSecsToMSecs * CalculateMeanDuration(delayed_frames_, latency, 95).to_usecs(), insp);
insp->emplace(std::move(node));
}
{
inspect::Node node = insp->GetRoot().CreateChild("frame_history");
inspect::Node minutes_ago_node = node.CreateChild("minutes_ago");
size_t minutes_ago = 0;
HistoryStats total = {};
for (auto it = history_stats_.rbegin(); it != history_stats_.rend(); ++it, ++minutes_ago) {
total += *it;
inspect::Node cur = minutes_ago_node.CreateChild(std::to_string(minutes_ago));
it->RecordToNode(&cur, insp);
insp->emplace(std::move(cur));
}
auto total_node = node.CreateChild("total");
total.RecordToNode(&total_node, insp);
insp->emplace(std::move(total_node));
insp->emplace(std::move(minutes_ago_node));
insp->emplace(std::move(node));
}
}
} // namespace scheduling