#include "cc/metrics/scroll_jank_v4_frame.h"
#include <algorithm>
#include <map>
#include <memory>
#include <optional>
#include <vector>
#include "base/check_op.h"
#include "cc/metrics/event_metrics.h"
#include "cc/metrics/scroll_jank_v4_frame_stage.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "third_party/abseil-cpp/absl/container/inlined_vector.h"
namespace cc {
namespace {
ScrollEventMetrics* AsScrollUpdateOrEnd(EventMetrics& event) {
switch (event.type()) {
case EventMetrics::EventType::kFirstGestureScrollUpdate:
case EventMetrics::EventType::kGestureScrollUpdate:
case EventMetrics::EventType::kInertialGestureScrollUpdate:
case EventMetrics::EventType::kGestureScrollEnd:
case EventMetrics::EventType::kInertialGestureScrollEnd:
return event.AsScroll();
default:
return nullptr;
}
}
struct FrameBounds {
viz::BeginFrameId min_id;
viz::BeginFrameId max_id;
std::optional<viz::BeginFrameId> min_damaging_id;
ScrollJankV4Frame::BeginFrameArgsForScrollJank some_args;
bool IsDamaging(const viz::BeginFrameId& frame_id) const {
return min_damaging_id.has_value() && frame_id >= *min_damaging_id;
}
};
std::optional<FrameBounds> GetFrameBounds(
const EventMetrics::List& events_metrics) {
std::optional<FrameBounds> result = std::nullopt;
for (const auto& event : events_metrics) {
ScrollEventMetrics* scroll_event = AsScrollUpdateOrEnd(*event);
if (!scroll_event) {
continue;
}
const ScrollEventMetrics::DispatchBeginFrameArgs& args =
scroll_event->dispatch_args();
viz::BeginFrameId frame_id = args.frame_id;
bool is_damaging = [&] {
if (!scroll_event->caused_frame_update()) {
return false;
}
ScrollUpdateEventMetrics* scroll_update_event =
scroll_event->AsScrollUpdate();
if (scroll_update_event == nullptr) {
return false;
}
return scroll_update_event->did_scroll();
}();
if (result.has_value()) {
result->min_id = std::min(result->min_id, frame_id);
result->max_id = std::max(result->max_id, frame_id);
if (is_damaging) {
result->min_damaging_id =
result->min_damaging_id.has_value()
? std::min(*result->min_damaging_id, frame_id)
: frame_id;
}
} else {
result = FrameBounds{
.min_id = frame_id,
.max_id = frame_id,
.min_damaging_id = is_damaging
? std::optional<viz::BeginFrameId>(frame_id)
: std::nullopt,
.some_args =
ScrollJankV4Frame::BeginFrameArgsForScrollJank::From(args),
};
}
}
return result;
}
}
ScrollJankV4Frame::BeginFrameArgsForScrollJank
ScrollJankV4Frame::BeginFrameArgsForScrollJank::From(
const viz::BeginFrameArgs& args) {
return {.frame_time = args.frame_time, .interval = args.interval};
}
ScrollJankV4Frame::BeginFrameArgsForScrollJank
ScrollJankV4Frame::BeginFrameArgsForScrollJank::From(
const ScrollEventMetrics::DispatchBeginFrameArgs& args) {
return {.frame_time = args.frame_time, .interval = args.interval};
}
ScrollJankV4Frame::ScrollJankV4Frame(BeginFrameArgsForScrollJank args,
ScrollDamage damage,
ScrollJankV4FrameStage::List stages)
: args(args), damage(damage), stages(stages) {}
ScrollJankV4Frame::ScrollJankV4Frame(const ScrollJankV4Frame& frame) = default;
ScrollJankV4Frame::~ScrollJankV4Frame() = default;
ScrollJankV4Frame::Timeline ScrollJankV4Frame::CalculateTimeline(
const EventMetrics::List& events_metrics,
const viz::BeginFrameArgs& presented_args,
base::TimeTicks presentation_ts) {
ScrollJankV4Frame::Timeline result;
std::optional<FrameBounds> frame_bounds = GetFrameBounds(events_metrics);
if (!frame_bounds.has_value()) {
return result;
}
if (!frame_bounds->min_damaging_id.has_value() &&
frame_bounds->min_id == frame_bounds->max_id) {
result.emplace_back(
frame_bounds->some_args, NonDamagingFrame{},
ScrollJankV4FrameStage::CalculateStages(
events_metrics, false));
return result;
}
if (frame_bounds->min_damaging_id.has_value() &&
*frame_bounds->min_damaging_id == frame_bounds->min_id) {
result.emplace_back(
BeginFrameArgsForScrollJank::From(presented_args),
DamagingFrame{.presentation_ts = presentation_ts},
ScrollJankV4FrameStage::CalculateStages(
events_metrics, false));
return result;
}
struct ArgsAndEvents {
BeginFrameArgsForScrollJank args;
bool is_damaging;
std::vector<ScrollEventMetrics*> events;
};
std::map<viz::BeginFrameId, ArgsAndEvents> frame_id_to_args_and_events;
for (const auto& event : events_metrics) {
ScrollEventMetrics* scroll_event = AsScrollUpdateOrEnd(*event);
if (!scroll_event) {
continue;
}
const ScrollEventMetrics::DispatchBeginFrameArgs& original_args =
scroll_event->dispatch_args();
bool is_damaging = frame_bounds->IsDamaging(original_args.frame_id);
const viz::BeginFrameId& effective_frame_id =
is_damaging ? presented_args.frame_id : original_args.frame_id;
auto it = frame_id_to_args_and_events.lower_bound(effective_frame_id);
if (it != frame_id_to_args_and_events.end() &&
it->first == effective_frame_id) {
it->second.events.push_back(scroll_event);
} else {
frame_id_to_args_and_events.emplace_hint(
it, effective_frame_id,
ArgsAndEvents{
.args = is_damaging
? BeginFrameArgsForScrollJank::From(presented_args)
: BeginFrameArgsForScrollJank::From(original_args),
.is_damaging = is_damaging,
.events = {scroll_event},
});
}
}
for (const auto& [frame_id, args_and_events] : frame_id_to_args_and_events) {
ScrollDamage damage =
args_and_events.is_damaging
? ScrollDamage{DamagingFrame{.presentation_ts = presentation_ts}}
: ScrollDamage{NonDamagingFrame{}};
result.emplace_back(
args_and_events.args, damage,
ScrollJankV4FrameStage::CalculateStages(
args_and_events.events, false));
}
return result;
}
}