blob: dc6d8fd4f9f1fad14283e00b46dcce971a413648 [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/ext/base/status_or.h"
#include "src/base/test/status_matchers.h"
#include "test/gtest_and_gmock.h"
#include "protos/perfetto/trace/android/frame_timeline_event.gen.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 kTimeStep = 1000;
constexpr uint64_t kTimestampA = 0;
constexpr uint64_t kTimestampB = kTimeStep;
constexpr uint64_t kTimestampC = kTimeStep * 2;
constexpr uint64_t kTimestampD = kTimeStep * 3;
constexpr uint64_t kTimestampE = kTimeStep * 4;
constexpr int64_t kCookieA = 1234;
constexpr int64_t kCookieB = 2345;
// 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;
enum class FrameCookieType {
ExpectedSurface,
ExpectedDisplay,
ActualSurface,
ActualDisplay,
};
protos::gen::TracePacket CreateExpectedSurfaceFrameStart(uint64_t ts,
int32_t pid,
int64_t cookie) {
protos::gen::TracePacket packet;
packet.set_timestamp(ts);
auto* start = packet.mutable_frame_timeline_event()
->mutable_expected_surface_frame_start();
start->set_cookie(cookie);
start->set_pid(pid);
return packet;
}
protos::gen::TracePacket CreateActualSurfaceFrameStart(uint64_t ts,
int32_t pid,
int64_t cookie) {
protos::gen::TracePacket packet;
packet.set_timestamp(ts);
auto* start = packet.mutable_frame_timeline_event()
->mutable_actual_surface_frame_start();
start->set_cookie(cookie);
start->set_pid(pid);
return packet;
}
protos::gen::TracePacket CreateExpectedDisplayFrameStart(uint64_t ts,
int32_t pid,
int64_t cookie) {
protos::gen::TracePacket packet;
packet.set_timestamp(ts);
auto* start = packet.mutable_frame_timeline_event()
->mutable_expected_display_frame_start();
start->set_cookie(cookie);
start->set_pid(pid);
return packet;
}
protos::gen::TracePacket CreateActualDisplayFrameStart(uint64_t ts,
int32_t pid,
int64_t cookie) {
protos::gen::TracePacket packet;
packet.set_timestamp(ts);
auto* start = packet.mutable_frame_timeline_event()
->mutable_actual_display_frame_start();
start->set_cookie(cookie);
start->set_pid(pid);
return packet;
}
protos::gen::TracePacket CreateFrameEnd(uint64_t ts, int64_t cookie) {
protos::gen::TracePacket packet;
packet.set_timestamp(ts);
auto* start = packet.mutable_frame_timeline_event()->mutable_frame_end();
start->set_cookie(cookie);
return packet;
}
protos::gen::TracePacket CreateStartPacket(FrameCookieType type,
uint64_t ts,
int32_t pid,
int64_t cookie) {
switch (type) {
case FrameCookieType::ExpectedSurface:
return CreateExpectedSurfaceFrameStart(ts, pid, cookie);
case FrameCookieType::ExpectedDisplay:
return CreateExpectedDisplayFrameStart(ts, pid, cookie);
case FrameCookieType::ActualSurface:
return CreateActualSurfaceFrameStart(ts, pid, cookie);
case FrameCookieType::ActualDisplay:
return CreateActualDisplayFrameStart(ts, pid, cookie);
}
PERFETTO_FATAL("Unhandled case. This should never happen.");
}
void CollectCookies(const std::string& packet, Context* context) {
protos::pbzero::TracePacket::Decoder decoder(packet);
CollectFrameCookies collect_;
ASSERT_OK(collect_.Begin(context));
ASSERT_OK(collect_.Collect(decoder, context));
ASSERT_OK(collect_.End(context));
}
class FrameCookieTest : public testing::Test {
protected:
CollectFrameCookies collect_;
Context context_;
};
TEST_F(FrameCookieTest, ExtractsExpectedSurfaceFrameStart) {
auto packet = CreateExpectedSurfaceFrameStart(kTimestampA, kPidA, kCookieA);
auto bytes = packet.SerializeAsString();
CollectCookies(bytes, &context_);
ASSERT_EQ(context_.global_frame_cookies.size(), 1u);
const auto& cookie = context_.global_frame_cookies.back();
ASSERT_EQ(cookie.cookie, kCookieA);
ASSERT_EQ(cookie.pid, kPidA);
ASSERT_EQ(cookie.ts, kTimestampA);
}
TEST_F(FrameCookieTest, ExtractsActualSurfaceFrameStart) {
auto packet = CreateActualSurfaceFrameStart(kTimestampA, kPidA, kCookieA);
auto bytes = packet.SerializeAsString();
CollectCookies(bytes, &context_);
ASSERT_EQ(context_.global_frame_cookies.size(), 1u);
const auto& cookie = context_.global_frame_cookies.back();
ASSERT_EQ(cookie.cookie, kCookieA);
ASSERT_EQ(cookie.pid, kPidA);
ASSERT_EQ(cookie.ts, kTimestampA);
}
TEST_F(FrameCookieTest, ExtractsExpectedDisplayFrameStart) {
auto packet = CreateExpectedDisplayFrameStart(kTimestampA, kPidA, kCookieA);
auto bytes = packet.SerializeAsString();
CollectCookies(bytes, &context_);
ASSERT_EQ(context_.global_frame_cookies.size(), 1u);
const auto& cookie = context_.global_frame_cookies.back();
ASSERT_EQ(cookie.cookie, kCookieA);
ASSERT_EQ(cookie.pid, kPidA);
ASSERT_EQ(cookie.ts, kTimestampA);
}
TEST_F(FrameCookieTest, ExtractsActualDisplayFrameStart) {
auto packet = CreateActualDisplayFrameStart(kTimestampA, kPidA, kCookieA);
auto bytes = packet.SerializeAsString();
CollectCookies(bytes, &context_);
ASSERT_EQ(context_.global_frame_cookies.size(), 1u);
const auto& cookie = context_.global_frame_cookies.back();
ASSERT_EQ(cookie.cookie, kCookieA);
ASSERT_EQ(cookie.pid, kPidA);
ASSERT_EQ(cookie.ts, kTimestampA);
}
// 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(FrameCookieTest, IgnoresFrameEnd) {
auto packet = CreateFrameEnd(kTimestampA, kCookieA);
auto bytes = packet.SerializeAsString();
CollectCookies(bytes, &context_);
ASSERT_TRUE(context_.global_frame_cookies.empty());
}
class ReduceFrameCookiesTest
: public testing::Test,
public testing::WithParamInterface<FrameCookieType> {
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".
context_.timeline = std::make_unique<ProcessThreadTimeline>();
context_.timeline->Append(ProcessThreadTimeline::Event::Open(
kTimestampB, kPidA, kPidNone, kUidA));
context_.timeline->Append(
ProcessThreadTimeline::Event::Close(kTimestampD, kPidA));
context_.timeline->Sort();
}
ReduceFrameCookies reduce_;
Context context_;
};
TEST_P(ReduceFrameCookiesTest, RejectBeforeStart) {
auto packet = CreateStartPacket(GetParam(), kTimestampA, kPidA, kCookieA);
auto bytes = packet.SerializeAsString();
CollectCookies(bytes, &context_);
ASSERT_OK(reduce_.Build(&context_));
ASSERT_FALSE(context_.package_frame_cookies.count(kCookieA));
}
TEST_P(ReduceFrameCookiesTest, AcceptAtStart) {
auto packet = CreateStartPacket(GetParam(), kTimestampB, kPidA, kCookieA);
auto bytes = packet.SerializeAsString();
CollectCookies(bytes, &context_);
ASSERT_OK(reduce_.Build(&context_));
ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA));
}
TEST_P(ReduceFrameCookiesTest, AcceptBetweenStartAndEnd) {
auto packet = CreateStartPacket(GetParam(), kTimestampC, kPidA, kCookieA);
auto bytes = packet.SerializeAsString();
CollectCookies(bytes, &context_);
ASSERT_OK(reduce_.Build(&context_));
ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA));
}
TEST_P(ReduceFrameCookiesTest, AcceptAtEnd) {
auto packet = CreateStartPacket(GetParam(), kTimestampD, kPidA, kCookieA);
auto bytes = packet.SerializeAsString();
CollectCookies(bytes, &context_);
ASSERT_OK(reduce_.Build(&context_));
ASSERT_TRUE(context_.package_frame_cookies.count(kCookieA));
}
TEST_P(ReduceFrameCookiesTest, RejectAfterEnd) {
auto packet = CreateStartPacket(GetParam(), kTimestampE, kPidA, kCookieA);
auto bytes = packet.SerializeAsString();
CollectCookies(bytes, &context_);
ASSERT_OK(reduce_.Build(&context_));
ASSERT_FALSE(context_.package_frame_cookies.count(kCookieA));
}
INSTANTIATE_TEST_SUITE_P(Default,
ReduceFrameCookiesTest,
testing::Values(FrameCookieType::ExpectedSurface,
FrameCookieType::ExpectedDisplay,
FrameCookieType::ActualSurface,
FrameCookieType::ActualDisplay));
class TransformStartCookiesTest
: public testing::Test,
public testing::WithParamInterface<FrameCookieType> {
protected:
void SetUp() { context_.package_frame_cookies.insert(kCookieA); }
FilterFrameEvents filter_;
Context context_;
};
TEST_P(TransformStartCookiesTest, RetainStartEvent) {
auto packet = CreateStartPacket(GetParam(), kTimestampE, kPidA, kCookieA);
auto bytes = packet.SerializeAsString();
ASSERT_OK(filter_.Transform(context_, &bytes));
protos::gen::TracePacket redacted;
ASSERT_TRUE(redacted.ParseFromString(bytes));
ASSERT_TRUE(redacted.has_frame_timeline_event());
const auto& timeline = redacted.frame_timeline_event();
int64_t cookie;
// Find the cookie from the packet.
switch (GetParam()) {
case FrameCookieType::ExpectedSurface: {
ASSERT_TRUE(timeline.has_expected_surface_frame_start());
const auto& start = timeline.expected_surface_frame_start();
ASSERT_TRUE(start.has_cookie());
cookie = start.cookie();
break;
}
case FrameCookieType::ExpectedDisplay: {
ASSERT_TRUE(timeline.has_expected_display_frame_start());
const auto& start = timeline.expected_display_frame_start();
ASSERT_TRUE(start.has_cookie());
cookie = start.cookie();
break;
}
case FrameCookieType::ActualSurface: {
ASSERT_TRUE(timeline.has_actual_surface_frame_start());
const auto& start = timeline.actual_surface_frame_start();
ASSERT_TRUE(start.has_cookie());
cookie = start.cookie();
break;
}
case FrameCookieType::ActualDisplay: {
ASSERT_TRUE(timeline.has_actual_display_frame_start());
const auto& start = timeline.actual_display_frame_start();
ASSERT_TRUE(start.has_cookie());
cookie = start.cookie();
break;
}
}
ASSERT_EQ(cookie, kCookieA);
}
TEST_P(TransformStartCookiesTest, DropStartEvent) {
// Even those this packet is using PidA, because CookieA is not in the package
// coookie pool, the event should be dropped.
auto packet = CreateStartPacket(GetParam(), kTimestampE, kPidA, kCookieB);
auto bytes = packet.SerializeAsString();
ASSERT_OK(filter_.Transform(context_, &bytes));
protos::gen::TracePacket redacted;
ASSERT_TRUE(redacted.ParseFromString(bytes));
ASSERT_FALSE(redacted.has_frame_timeline_event());
}
INSTANTIATE_TEST_SUITE_P(Default,
TransformStartCookiesTest,
testing::Values(FrameCookieType::ExpectedSurface,
FrameCookieType::ExpectedDisplay,
FrameCookieType::ActualSurface,
FrameCookieType::ActualDisplay));
class TransformEndFrameCookiesTest : public testing::Test {
protected:
void SetUp() { context_.package_frame_cookies.insert(kCookieA); }
FilterFrameEvents filter_;
Context context_;
};
// An end event has no pid. The cookie connects it to a pid. If the start event
// cookie moved from the global pool into the package pool, then end the end
// event should be retained.
TEST_F(TransformStartCookiesTest, Retain) {
auto packet = CreateFrameEnd(kTimestampA, kCookieA);
auto bytes = packet.SerializeAsString();
ASSERT_OK(filter_.Transform(context_, &bytes));
protos::gen::TracePacket redacted;
ASSERT_TRUE(redacted.ParseFromString(bytes));
ASSERT_TRUE(redacted.has_frame_timeline_event());
const auto& timeline = redacted.frame_timeline_event();
ASSERT_TRUE(timeline.has_frame_end());
const auto& end = timeline.frame_end();
ASSERT_EQ(end.cookie(), kCookieA);
}
TEST_F(TransformStartCookiesTest, Drop) {
auto packet = CreateFrameEnd(kTimestampA, kCookieB);
auto bytes = packet.SerializeAsString();
ASSERT_OK(filter_.Transform(context_, &bytes));
protos::gen::TracePacket redacted;
ASSERT_TRUE(redacted.ParseFromString(bytes));
ASSERT_FALSE(redacted.has_frame_timeline_event());
}
} // namespace
} // namespace perfetto::trace_redaction