blob: aa642a0c9fe852db3b3fa194cd2ac74c890d3f40 [file] [log] [blame]
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "src/trace_redaction/collect_frame_cookies.h"
#include "perfetto/base/status.h"
#include "perfetto/protozero/field.h"
#include "perfetto/protozero/proto_decoder.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "src/trace_redaction/proto_util.h"
#include "src/trace_redaction/trace_redaction_framework.h"
#include "protos/perfetto/trace/android/frame_timeline_event.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
namespace perfetto::trace_redaction {
namespace {
using FrameTimelineEvent = protos::pbzero::FrameTimelineEvent;
struct Frame {
uint32_t id;
uint32_t pid;
uint32_t cookie;
};
constexpr Frame kActualDisplayFrameStart = {
FrameTimelineEvent::kActualDisplayFrameStartFieldNumber,
FrameTimelineEvent::ActualDisplayFrameStart::kPidFieldNumber,
FrameTimelineEvent::ActualDisplayFrameStart::kCookieFieldNumber,
};
constexpr Frame kExpectedDisplayFrameStart = {
FrameTimelineEvent::kExpectedDisplayFrameStartFieldNumber,
FrameTimelineEvent::ExpectedDisplayFrameStart::kPidFieldNumber,
FrameTimelineEvent::ExpectedDisplayFrameStart::kCookieFieldNumber,
};
constexpr Frame kActualSurfaceFrameStart = {
FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
FrameTimelineEvent::ActualSurfaceFrameStart::kPidFieldNumber,
FrameTimelineEvent::ActualSurfaceFrameStart::kCookieFieldNumber,
};
constexpr Frame kExpectedSurfaceFrameStart = {
FrameTimelineEvent::kExpectedSurfaceFrameStartFieldNumber,
FrameTimelineEvent::ExpectedSurfaceFrameStart::kPidFieldNumber,
FrameTimelineEvent::ExpectedSurfaceFrameStart::kCookieFieldNumber,
};
// Do not use `pid` from `kFrameEnd`.
constexpr Frame kFrameEnd = {
FrameTimelineEvent::kFrameEndFieldNumber,
0,
FrameTimelineEvent::FrameEnd::kCookieFieldNumber,
};
} // namespace
base::Status CollectFrameCookies::Begin(Context* context) const {
if (context->global_frame_cookies.empty()) {
return base::OkStatus();
}
return base::ErrStatus("FindFrameCookies: frame cookies already populated");
}
base::Status CollectFrameCookies::Collect(
const protos::pbzero::TracePacket::Decoder& packet,
Context* context) const {
// A frame cookie needs a time and pid for a timeline query. Ignore packets
// without a timestamp.
if (!packet.has_timestamp() || !packet.has_frame_timeline_event()) {
return base::OkStatus();
}
auto timestamp = packet.timestamp();
// Only use the start frames. They are the only ones with a pid. End events
// use the cookies to reference the pid in a start event.
auto handlers = {
kActualDisplayFrameStart,
kActualSurfaceFrameStart,
kExpectedDisplayFrameStart,
kExpectedSurfaceFrameStart,
};
// Timeline Event Decoder.
protozero::ProtoDecoder decoder(packet.frame_timeline_event());
// If no handler worked, cookie will not get added to the global cookie field.
for (const auto& handler : handlers) {
auto outer = decoder.FindField(handler.id);
if (!outer.valid()) {
continue;
}
protozero::ProtoDecoder inner(outer.as_bytes());
auto pid = inner.FindField(handler.pid);
auto cookie = inner.FindField(handler.cookie);
// This should be handled, but it is not valid. Drop the event by not adding
// it to the global_frame_cookies list.
if (!pid.valid() || !cookie.valid()) {
continue;
}
FrameCookie frame_cookie;
frame_cookie.pid = pid.as_int32();
frame_cookie.cookie = cookie.as_int64();
frame_cookie.ts = timestamp;
context->global_frame_cookies.push_back(frame_cookie);
break;
}
return base::OkStatus();
}
base::Status ReduceFrameCookies::Build(Context* context) const {
if (!context->package_uid.has_value()) {
return base::ErrStatus("ReduceFrameCookies: missing package uid.");
}
if (!context->timeline) {
return base::ErrStatus("ReduceFrameCookies: missing timeline.");
}
// Even though it is rare, it is possible for there to be no SurfaceFlinger
// frame cookies. Even through the main path handles this, we use this early
// exit to document this edge case.
if (context->global_frame_cookies.empty()) {
return base::OkStatus();
}
// Filter the global cookies down to cookies that belong to the target package
// (uid).
for (const auto& cookie : context->global_frame_cookies) {
if (context->timeline->PidConnectsToUid(cookie.ts, cookie.pid,
*context->package_uid)) {
context->package_frame_cookies.insert(cookie.cookie);
}
}
return base::OkStatus();
}
base::Status FilterFrameEvents::Transform(const Context& context,
std::string* packet) const {
if (packet == nullptr || packet->empty()) {
return base::ErrStatus("FilterFrameEvents: null or empty packet.");
}
protozero::ProtoDecoder decoder(*packet);
if (!decoder
.FindField(
protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber)
.valid()) {
return base::OkStatus();
}
protozero::HeapBuffered<protos::pbzero::TracePacket> message;
for (auto field = decoder.ReadField(); field.valid();
field = decoder.ReadField()) {
if (field.id() ==
protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber) {
if (KeepField(context, field)) {
proto_util::AppendField(field, message.get());
}
} else {
proto_util::AppendField(field, message.get());
}
}
packet->assign(message.SerializeAsString());
return base::OkStatus();
}
bool FilterFrameEvents::KeepField(const Context& context,
const protozero::Field& field) const {
PERFETTO_DCHECK(field.id() ==
protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber);
protozero::ProtoDecoder timeline_event_decoder(field.as_bytes());
auto handlers = {
kActualDisplayFrameStart,
kActualSurfaceFrameStart,
kExpectedDisplayFrameStart,
kExpectedSurfaceFrameStart,
kFrameEnd,
};
const auto& cookies = context.package_frame_cookies;
for (const auto& handler : handlers) {
auto event = timeline_event_decoder.FindField(handler.id);
if (!event.valid()) {
continue;
}
protozero::ProtoDecoder event_decoder(event.as_bytes());
auto cookie = event_decoder.FindField(handler.cookie);
if (cookie.valid() && cookies.count(cookie.as_int64())) {
return true;
}
}
return false;
}
} // namespace perfetto::trace_redaction