blob: 6e77231de1c97b40db44ec8df4c066d4261ef7c7 [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 "src/base/test/status_matchers.h"
#include "src/trace_redaction/collect_timeline_events.h"
#include "test/gtest_and_gmock.h"
#include "protos/perfetto/trace/android/frame_timeline_event.gen.h"
#include "protos/perfetto/trace/android/frame_timeline_event.pbzero.h"
#include "protos/perfetto/trace/trace_packet.gen.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
namespace perfetto::trace_redaction {
namespace {
constexpr uint64_t kTimestampA = 0;
constexpr uint64_t kTimestampB = 1000;
constexpr uint64_t kTimestampC = 2000;
constexpr uint64_t kTimestampD = 3000;
constexpr uint64_t kTimestampE = 3000;
constexpr int64_t kCookieA = 1234;
// Start at 1, amd not zero, because zero hnas special meaning (system uid).
constexpr uint64_t kUidA = 1;
constexpr int32_t kPidNone = 10;
constexpr int32_t kPidA = 11;
} // namespace
class FrameCookieFixture {
protected:
std::string CreateStartEvent(int32_t field_id,
uint64_t ts,
int32_t pid,
int64_t cookie) const {
protos::gen::TracePacket packet;
packet.set_timestamp(ts);
switch (field_id) {
case protos::pbzero::FrameTimelineEvent::
kExpectedSurfaceFrameStartFieldNumber:
CreateExpectedSurfaceFrameStart(pid, cookie,
packet.mutable_frame_timeline_event());
break;
case protos::pbzero::FrameTimelineEvent::
kActualSurfaceFrameStartFieldNumber:
CreateActualSurfaceFrameStart(pid, cookie,
packet.mutable_frame_timeline_event());
break;
case protos::pbzero::FrameTimelineEvent::
kExpectedDisplayFrameStartFieldNumber:
CreateExpectedDisplayFrameStart(pid, cookie,
packet.mutable_frame_timeline_event());
break;
case protos::pbzero::FrameTimelineEvent::
kActualDisplayFrameStartFieldNumber:
CreateActualDisplayFrameStart(pid, cookie,
packet.mutable_frame_timeline_event());
break;
default:
PERFETTO_FATAL("Invalid field id");
break;
}
return packet.SerializeAsString();
}
std::string CreateFrameEnd(uint64_t ts, int64_t cookie) const {
protos::gen::TracePacket packet;
packet.set_timestamp(ts);
auto* start = packet.mutable_frame_timeline_event()->mutable_frame_end();
start->set_cookie(cookie);
return packet.SerializeAsString();
}
void CollectEvents(std::initializer_list<ProcessThreadTimeline::Event> events,
Context* context) const {
CollectTimelineEvents collect;
ASSERT_OK(collect.Begin(context));
for (const auto& event : events) {
context->timeline->Append(event);
}
ASSERT_OK(collect.End(context));
}
void CollectCookies(std::initializer_list<std::string> packets,
Context* context) const {
CollectFrameCookies collect;
ASSERT_OK(collect.Begin(context));
for (const auto& packet : packets) {
protos::pbzero::TracePacket::Decoder decoder(packet);
ASSERT_OK(collect.Collect(decoder, context));
}
ASSERT_OK(collect.End(context));
}
private:
void CreateExpectedSurfaceFrameStart(
int32_t pid,
int64_t cookie,
protos::gen::FrameTimelineEvent* event) const {
auto* start = event->mutable_expected_surface_frame_start();
start->set_cookie(cookie);
start->set_pid(pid);
}
void CreateActualSurfaceFrameStart(
int32_t pid,
int64_t cookie,
protos::gen::FrameTimelineEvent* event) const {
auto* start = event->mutable_actual_surface_frame_start();
start->set_cookie(cookie);
start->set_pid(pid);
}
void CreateExpectedDisplayFrameStart(
int32_t pid,
int64_t cookie,
protos::gen::FrameTimelineEvent* event) const {
auto* start = event->mutable_expected_display_frame_start();
start->set_cookie(cookie);
start->set_pid(pid);
}
void CreateActualDisplayFrameStart(
int32_t pid,
int64_t cookie,
protos::gen::FrameTimelineEvent* event) const {
auto* start = event->mutable_actual_display_frame_start();
start->set_cookie(cookie);
start->set_pid(pid);
}
};
class CollectFrameCookiesTest : public testing::Test,
protected FrameCookieFixture,
public testing::WithParamInterface<int32_t> {
protected:
Context context_;
};
TEST_P(CollectFrameCookiesTest, ExtractsExpectedSurfaceFrameStart) {
auto field_id = GetParam();
auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
CollectCookies({packet}, &context_);
ASSERT_EQ(context_.global_frame_cookies.size(), 1u);
auto& cookie = context_.global_frame_cookies.back();
ASSERT_EQ(cookie.cookie, kCookieA);
ASSERT_EQ(cookie.pid, kPidA);
ASSERT_EQ(cookie.ts, kTimestampA);
}
INSTANTIATE_TEST_SUITE_P(
EveryStartEventType,
CollectFrameCookiesTest,
testing::Values(
protos::pbzero::FrameTimelineEvent::
kExpectedSurfaceFrameStartFieldNumber,
protos::pbzero::FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
protos::pbzero::FrameTimelineEvent::
kExpectedDisplayFrameStartFieldNumber,
protos::pbzero::FrameTimelineEvent::
kActualDisplayFrameStartFieldNumber));
// End events have no influence during the collect phase because they don't have
// a direct connection to a process. They're indirectly connected to a pid via a
// start event (via a common cookie value).
TEST_F(CollectFrameCookiesTest, IgnoresFrameEnd) {
CollectCookies({CreateFrameEnd(kTimestampA, kPidA)}, &context_);
ASSERT_TRUE(context_.global_frame_cookies.empty());
}
class ReduceFrameCookiesTest : public testing::Test,
protected FrameCookieFixture,
public testing::WithParamInterface<int32_t> {
protected:
void SetUp() {
context_.package_uid = kUidA;
// Time A +- Time B +- Time C +- Time D +- Time E
// | |
// +------------ Pid A ---------+
//
// The pid will be active from time b to time d. Time A will be used for
// "before active". Time C will be used for "while active". Time E will be
// used for "after active".
CollectEvents(
{
ProcessThreadTimeline::Event::Open(kTimestampB, kPidA, kPidNone,
kUidA),
ProcessThreadTimeline::Event::Close(kTimestampD, kPidA),
},
&context_);
}
ReduceFrameCookies reduce_;
Context context_;
};
TEST_P(ReduceFrameCookiesTest, RejectBeforeActive) {
auto field_id = GetParam();
// kTimestampA is before pid starts.
auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
CollectCookies({packet}, &context_);
ASSERT_OK(reduce_.Build(&context_));
ASSERT_FALSE(context_.package_frame_cookies.count(kCookieA));
}
TEST_P(ReduceFrameCookiesTest, AcceptDuringActive) {
auto field_id = GetParam();
// kTimestampC is between pid starts and ends.
auto packet = CreateStartEvent(field_id, kTimestampC, kPidA, kCookieA);
CollectCookies({packet}, &context_);
ASSERT_OK(reduce_.Build(&context_));
ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA));
}
TEST_P(ReduceFrameCookiesTest, RejectAfterActive) {
auto field_id = GetParam();
// kTimestampE is after pid ends.
auto packet = CreateStartEvent(field_id, kTimestampE, kPidA, kCookieA);
CollectCookies({packet}, &context_);
ASSERT_OK(reduce_.Build(&context_));
ASSERT_FALSE(context_.package_frame_cookies.count(kCookieA));
}
INSTANTIATE_TEST_SUITE_P(
EveryStartEventType,
ReduceFrameCookiesTest,
testing::Values(
protos::pbzero::FrameTimelineEvent::
kExpectedSurfaceFrameStartFieldNumber,
protos::pbzero::FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
protos::pbzero::FrameTimelineEvent::
kExpectedDisplayFrameStartFieldNumber,
protos::pbzero::FrameTimelineEvent::
kActualDisplayFrameStartFieldNumber));
class FilterCookiesFieldsTest : public testing::Test,
protected FrameCookieFixture,
public testing::WithParamInterface<int32_t> {
protected:
protozero::Field ExtractTimelineEvent(const std::string& packet) const {
protozero::ProtoDecoder packet_decoder(packet);
// There must be one in order for the test to work, so we assume it's there.
return packet_decoder.FindField(
protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber);
}
FilterFrameEvents filter_;
Context context_;
};
// If the event was within a valid pid's lifespan and was connected to the
// package, it should be kept.
TEST_P(FilterCookiesFieldsTest, IncludeIncludedStartCookies) {
context_.package_frame_cookies.insert(kCookieA);
auto field_id = GetParam();
auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
auto timeline_field = ExtractTimelineEvent(packet);
ASSERT_TRUE(filter_.KeepField(context_, timeline_field));
}
// If the event wasn't within a valid pid's lifespans and/or was connected to a
// package, it should be removed.
TEST_P(FilterCookiesFieldsTest, ExcludeMissingStartCookies) {
auto field_id = GetParam();
auto packet = CreateStartEvent(field_id, kTimestampA, kPidA, kCookieA);
auto timeline_field = ExtractTimelineEvent(packet);
ASSERT_FALSE(filter_.KeepField(context_, timeline_field));
}
INSTANTIATE_TEST_SUITE_P(
EveryStartEventType,
FilterCookiesFieldsTest,
testing::Values(
protos::pbzero::FrameTimelineEvent::
kExpectedSurfaceFrameStartFieldNumber,
protos::pbzero::FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
protos::pbzero::FrameTimelineEvent::
kExpectedDisplayFrameStartFieldNumber,
protos::pbzero::FrameTimelineEvent::
kActualDisplayFrameStartFieldNumber));
TEST_F(FilterCookiesFieldsTest, IncludeIncludedEndCookies) {
context_.package_frame_cookies.insert(kCookieA);
auto packet = CreateFrameEnd(kTimestampA, kCookieA);
auto timeline_field = ExtractTimelineEvent(packet);
ASSERT_TRUE(filter_.KeepField(context_, timeline_field));
}
TEST_F(FilterCookiesFieldsTest, ExcludeMissingEndCookies) {
auto packet = CreateFrameEnd(kTimestampA, kCookieA);
auto timeline_field = ExtractTimelineEvent(packet);
ASSERT_FALSE(filter_.KeepField(context_, timeline_field));
}
} // namespace perfetto::trace_redaction