/* * 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 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