blob: 912ee2b33d73d6de8c25115f7fa7a2415c5b7cab [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/tracing/common/etw_consumer_win.h"
#include <windows.h>
#include <stdint.h>
#include <algorithm>
#include <optional>
#include <queue>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/cstring_view.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/perfetto/include/perfetto/protozero/message.h"
#include "third_party/perfetto/include/perfetto/protozero/message_handle.h"
#include "third_party/perfetto/include/perfetto/protozero/scattered_heap_buffer.h"
#include "third_party/perfetto/include/perfetto/tracing/trace_writer_base.h"
#include "third_party/perfetto/protos/perfetto/trace/etw/etw.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/etw/etw_event.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/etw/etw_event_bundle.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h"
namespace tracing {
namespace {
// A trace writer that creates TracePacket messages on the heap and sends their
// serialized form to an owner-provided callback.
class FakeTraceWriter : public perfetto::TraceWriterBase,
public protozero::MessageFinalizationListener {
public:
using TracePacketHandle =
protozero::MessageHandle<perfetto::protos::pbzero::TracePacket>;
explicit FakeTraceWriter(
base::RepeatingCallback<void(std::vector<uint8_t>)> on_packet)
: on_packet_(std::move(on_packet)) {}
// perfetto::TraceWriterBase:
TracePacketHandle NewTracePacket() override {
packet_ = std::make_unique<
protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>>();
TracePacketHandle handle(packet_->get());
handle.set_finalization_listener(this);
return handle;
}
void FinishTracePacket() override { NOTREACHED(); }
void Flush(std::function<void()> callback = {}) override {}
uint64_t written() const override { return 0u; }
uint64_t drop_count() const override { return 0u; }
// protozero::MessageFinalizationListener:
void OnMessageFinalized(protozero::Message* message) override {
on_packet_.Run(packet_->SerializeAsArray());
packet_.reset();
}
private:
base::RepeatingCallback<void(std::vector<uint8_t>)> on_packet_;
std::unique_ptr<
protozero::HeapBuffered<perfetto::protos::pbzero::TracePacket>>
packet_;
};
struct ProcessData {
uint32_t process_id;
uint32_t parent_id;
uint32_t session_id;
std::string image_file_name;
std::wstring command_line;
};
struct ThreadData {
uint32_t process_id;
uint32_t thread_id;
std::optional<std::wstring> thread_name;
};
struct CSwitchData {
uint32_t new_thread_id;
uint32_t old_thread_id;
};
struct ReadyThreadData {
uint32_t t_thread_id;
};
// Returns the MOF encoding of a sid, including the leading uint32_t and
// TOKEN_USER.
base::HeapArray<uint8_t> EncodeSid(size_t pointer_size) {
static constexpr uint8_t kLeadingBytes[] = {0x04, 0x00, 0x00, 0x00};
static constexpr uint8_t kTokenUserBytes[] = {
0x20, 0xA8, 0xA4, 0x5C, 0x86, 0xD1, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static constexpr uint8_t kSidBytes[] = {0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x05, 0x12, 0x00, 0x00, 0x00};
std::vector<uint8_t> buffer;
auto iter = std::back_inserter(buffer);
std::ranges::copy(base::as_byte_span(kLeadingBytes), iter);
std::ranges::copy(base::span(kTokenUserBytes).first(2 * pointer_size), iter);
std::ranges::copy(base::as_byte_span(kSidBytes), iter);
return base::HeapArray<uint8_t>::CopiedFrom({buffer});
}
// Returns the MOF encoding of a Process event.
base::HeapArray<uint8_t> EncodeProcess(const ProcessData& process,
int version,
size_t pointer_size) {
std::vector<uint8_t> buffer;
auto iter = std::back_inserter(buffer);
if (version == 0) {
// ProcessId and ParentId are pointer-sized in version 0.
if (pointer_size == sizeof(uint64_t)) {
uint64_t value = process.process_id;
std::ranges::copy(base::byte_span_from_ref(value), iter);
value = process.parent_id;
std::ranges::copy(base::byte_span_from_ref(value), iter);
} else {
CHECK_EQ(pointer_size, sizeof(uint32_t));
uint32_t value = process.process_id;
std::ranges::copy(base::byte_span_from_ref(value), iter);
value = process.parent_id;
std::ranges::copy(base::byte_span_from_ref(value), iter);
}
std::ranges::copy(EncodeSid(pointer_size), iter);
std::ranges::copy(process.image_file_name, iter);
buffer.insert(buffer.end(), '\0'); // ImageFileName terminator
} else {
if (version == 1) {
// PageDirectoryBase
buffer.insert(buffer.end(), pointer_size, 0);
} else if (version >= 2) {
// UniqueProcessKey
buffer.insert(buffer.end(), pointer_size, 0);
}
std::ranges::copy(base::byte_span_from_ref(process.process_id), iter);
std::ranges::copy(base::byte_span_from_ref(process.parent_id), iter);
std::ranges::copy(base::byte_span_from_ref(process.session_id), iter);
buffer.insert(buffer.end(), sizeof(int32_t), 0); // ExitStatus
if (version >= 3) {
buffer.insert(buffer.end(), pointer_size, 0); // DirectoryTableBase
}
std::ranges::copy(EncodeSid(pointer_size), iter);
std::ranges::copy(process.image_file_name, iter);
buffer.insert(buffer.end(), '\0'); // ImageFileName terminator
if (version >= 2) {
std::ranges::copy(base::as_byte_span(process.command_line), iter);
buffer.insert(buffer.end(), sizeof(wchar_t), 0); // terminator
}
if (version >= 4) {
buffer.insert(buffer.end(), sizeof(wchar_t), 0); // PackageFullName
buffer.insert(buffer.end(), sizeof(wchar_t), 0); // ApplicationId
}
}
return base::HeapArray<uint8_t>::CopiedFrom({buffer});
}
// Returns the MOF encoding of a Thread event (v4 by default).
base::HeapArray<uint8_t> EncodeThread(const ThreadData& thread,
int version = 4) {
std::vector<uint8_t> buffer;
auto iter = std::back_inserter(buffer);
if (version == 0) {
std::ranges::copy(base::byte_span_from_ref(thread.thread_id), iter);
std::ranges::copy(base::byte_span_from_ref(thread.process_id), iter);
} else {
std::ranges::copy(base::byte_span_from_ref(thread.process_id), iter);
std::ranges::copy(base::byte_span_from_ref(thread.thread_id), iter);
uintptr_t a_pointer = 0;
uint32_t an_int = 0;
// StackBase
std::ranges::copy(base::byte_span_from_ref(++a_pointer), iter);
// StackLimit
std::ranges::copy(base::byte_span_from_ref(++a_pointer), iter);
// UserStackBase
std::ranges::copy(base::byte_span_from_ref(++a_pointer), iter);
// UserStackLimit
std::ranges::copy(base::byte_span_from_ref(++a_pointer), iter);
// StartAddr (1, 2) / Affinity (>=3)
std::ranges::copy(base::byte_span_from_ref(++a_pointer), iter);
// Win32StartAddr
std::ranges::copy(base::byte_span_from_ref(++a_pointer), iter);
if (version == 1) {
// WaitMode
buffer.insert(buffer.end(), 0x0a);
} else if (version >= 2) {
// TebBase
std::ranges::copy(base::byte_span_from_ref(++a_pointer), iter);
// SubProcessTag
std::ranges::copy(base::byte_span_from_ref(++an_int), iter);
}
if (version >= 3) {
buffer.insert(buffer.end(), 0x0a); // BasePriority
buffer.insert(buffer.end(), 0x0b); // PagePriority
buffer.insert(buffer.end(), 0x0c); // IoPriority
buffer.insert(buffer.end(), 0x0d); // ThreadFlags
}
if (version >= 4 && thread.thread_name.has_value()) {
std::ranges::copy(base::as_byte_span(*thread.thread_name), iter);
buffer.insert(buffer.end(), sizeof(wchar_t), 0); // ThreadName terminator
}
}
return base::HeapArray<uint8_t>::CopiedFrom({buffer});
}
base::HeapArray<uint8_t> EncodeThreadSetName(uint32_t process_id,
uint32_t thread_id,
base::wcstring_view thread_name) {
std::vector<uint8_t> buffer;
auto iter = std::back_inserter(buffer);
std::ranges::copy(base::byte_span_from_ref(process_id), iter);
std::ranges::copy(base::byte_span_from_ref(thread_id), iter);
std::ranges::copy(base::as_byte_span(thread_name), iter);
buffer.insert(buffer.end(), sizeof(wchar_t), 0); // ThreadName terminator
return base::HeapArray<uint8_t>::CopiedFrom({buffer});
}
// Returns the MOF encoding of a v2 CSwitch event.
base::HeapArray<uint8_t> EncodeCSwitch(const CSwitchData& c_switch) {
std::vector<uint8_t> buffer;
auto iter = std::back_inserter(buffer);
std::ranges::copy(base::byte_span_from_ref(c_switch.new_thread_id), iter);
std::ranges::copy(base::byte_span_from_ref(c_switch.old_thread_id), iter);
buffer.insert(buffer.end(), 0x01); // NewThreadPriority
buffer.insert(buffer.end(), 0x02); // OldThreadPriority
buffer.insert(buffer.end(), 0x03); // PreviousCState
buffer.insert(buffer.end(), 0x42); // SpareByte
buffer.insert(buffer.end(), 36); // OldThreadWaitReason = WR_RUNDOWN
buffer.insert(buffer.end(), 1); // OldThreadWaitMode = USER_MODE
buffer.insert(buffer.end(), 7); // OldThreadState = DEFERRED_READY
buffer.insert(buffer.end(), 0x04); // OldThreadWaitIdealProcessor
const uint32_t new_thread_wait_time = 0x05;
std::ranges::copy(base::byte_span_from_ref(new_thread_wait_time), iter);
buffer.insert(buffer.end(), sizeof(uint32_t), 0x42); // Reserved
return base::HeapArray<uint8_t>::CopiedFrom({buffer});
}
// Returns the MOF encoding of a v2 ReadyThread event.
base::HeapArray<uint8_t> EncodeReadyThread(
const ReadyThreadData& ready_thread) {
std::vector<uint8_t> buffer;
auto iter = std::back_inserter(buffer);
std::ranges::copy(base::byte_span_from_ref(ready_thread.t_thread_id), iter);
buffer.insert(buffer.end(), 0x01); // AdjustReason
buffer.insert(buffer.end(), 0); // AdjustIncrement
buffer.insert(buffer.end(), 0x01); // Flag = THREAD_READIED
buffer.insert(buffer.end(), sizeof(uint8_t), 0x42); // Reserved
return base::HeapArray<uint8_t>::CopiedFrom({buffer});
}
} // namespace
// A test fixture that instantiates an EtwConsumer and sends it some events to
// preconfigure active threads of each process category (a client proc, a
// system proc, and an "other" proc).
class EtwConsumerTest : public testing::Test {
protected:
// Identifiers of pre-configured procs and threads.
static constexpr uint32_t kClientPid = 0x1000;
static constexpr uint32_t kSystemPid = 0x2000;
static constexpr uint32_t kOtherPid = 0x3000;
static constexpr uint32_t kClientTid = kClientPid + 0x100;
static constexpr uint32_t kClientTid2 = kClientTid + 1;
static constexpr uint32_t kSystemTid = kSystemPid + 0x100;
static constexpr uint32_t kOtherTid = kOtherPid + 0x100;
// Holds a serialized TracePacket message and a decoder that reads from it.
class MessageAndDecoder {
public:
explicit MessageAndDecoder(std::vector<uint8_t> data)
: data_(std::move(data)), decoder_(data_.data(), data_.size()) {}
MessageAndDecoder(const MessageAndDecoder&) = delete;
MessageAndDecoder& operator=(const MessageAndDecoder&) = delete;
const perfetto::protos::pbzero::TracePacket::Decoder& decoder() const {
return decoder_;
}
private:
std::vector<uint8_t> data_;
perfetto::protos::pbzero::TracePacket::Decoder decoder_;
};
// testing::Test:
void SetUp() override {
// Send data collection start events for three processes w/ a thread each.
SendProcessDcStartEvent(EncodeProcess({.process_id = kSystemPid,
.parent_id = 4,
.session_id = 0xFFFF,
.image_file_name = "ntoskrnl.exe",
.command_line = L"ntoskrnl.exe"}));
SendThreadDcStartEvent(
EncodeThread({.process_id = kSystemPid, .thread_id = kSystemTid}));
SendProcessDcStartEvent(EncodeProcess({.process_id = kClientPid,
.parent_id = kSystemPid,
.session_id = 4,
.image_file_name = "chrome.exe",
.command_line = L"chrome.exe"}));
SendThreadDcStartEvent(
EncodeThread({.process_id = kClientPid, .thread_id = kClientTid}));
SendThreadDcStartEvent(
EncodeThread({.process_id = kClientPid, .thread_id = kClientTid2}));
SendProcessDcStartEvent(EncodeProcess({.process_id = kOtherPid,
.parent_id = kSystemPid,
.session_id = 4,
.image_file_name = "cmd.exe",
.command_line = L"cmd.exe"}));
SendThreadDcStartEvent(
EncodeThread({.process_id = kOtherPid, .thread_id = kOtherTid}));
}
void TearDown() override {
// Send data collection end events for the threads and processes.
SendThreadDcEndEvent(
EncodeThread({.process_id = kOtherPid, .thread_id = kOtherTid}));
SendProcessDcEndEvent(EncodeProcess({.process_id = kOtherPid}));
SendThreadDcEndEvent(
EncodeThread({.process_id = kClientPid, .thread_id = kClientTid2}));
SendThreadDcEndEvent(
EncodeThread({.process_id = kClientPid, .thread_id = kClientTid}));
SendProcessDcEndEvent(EncodeProcess({.process_id = kClientPid}));
SendThreadDcEndEvent(
EncodeThread({.process_id = kSystemPid, .thread_id = kSystemTid}));
SendProcessDcEndEvent(EncodeProcess({.process_id = kSystemPid}));
}
// Validates the TracePacket processed by `decoder` and populates `event`
// with a decoder for the first ETW event contained therein.
void ValidateAndDecodeEtwEvent(
const MessageAndDecoder& decoder,
std::optional<perfetto::protos::pbzero::EtwTraceEvent::Decoder>& event) {
auto& trace_packet_decoder = decoder.decoder();
ASSERT_TRUE(trace_packet_decoder.has_timestamp());
ASSERT_NE(trace_packet_decoder.timestamp(), 0u);
ASSERT_TRUE(trace_packet_decoder.has_etw_events());
perfetto::protos::pbzero::EtwTraceEventBundle::Decoder bundle(
trace_packet_decoder.etw_events());
ASSERT_TRUE(bundle.has_event());
event.emplace(*bundle.event());
ASSERT_TRUE(event->has_timestamp());
ASSERT_TRUE(event->has_cpu());
ASSERT_EQ(event->cpu(), kTestProcessorIndex);
}
// Generates an ETW CSwitch event with `packet_data` as its payload and sends
// it to the EtwConsumer for processing. If the EtwConsumer generates a
// TracePacket containing a `CSwitchEtwEvent`, a new decoder is constructed
// from it.
void ProcessCSwitchEvent(base::span<const uint8_t> packet_data) {
SendThreadEvent(/*version=*/2u, /*opcode=*/36u, kSystemTid, packet_data);
}
// Validates the TracePacket processed by `decoder` and populates `c_switch`
// with a decoder for the first ETW event contained therein.
void ValidateAndDecodeCSwitch(
const MessageAndDecoder& decoder,
std::optional<perfetto::protos::pbzero::CSwitchEtwEvent::Decoder>&
c_switch) {
std::optional<perfetto::protos::pbzero::EtwTraceEvent::Decoder> event;
ValidateAndDecodeEtwEvent(decoder, event);
ASSERT_TRUE(event->has_c_switch());
c_switch.emplace(event->c_switch());
}
// Generates an ETW ReadyThread event with `packet_data` as its payload and
// sends it to the EtwConsumer for processing. If the EtwConsumer generates a
// TracePacket containing a `ReadyThreadEtwEvent`, a new decoder is
// constructed from it.
void ProcessReadyThreadEvent(uint32_t thread_id,
base::span<const uint8_t> packet_data) {
SendThreadEvent(/*version=*/2u, /*opcode=*/50u, thread_id, packet_data);
}
// Validates the TracePacket processed by `decoder` and populates
// `ready_thread` with a decoder for the first ETW event contained therein.
void ValidateAndDecodeReadyThread(
const MessageAndDecoder& decoder,
std::optional<perfetto::protos::pbzero::EtwTraceEvent::Decoder>& event,
std::optional<perfetto::protos::pbzero::ReadyThreadEtwEvent::Decoder>&
ready_thread) {
ValidateAndDecodeEtwEvent(decoder, event);
ASSERT_TRUE(event->has_ready_thread());
ready_thread.emplace(event->ready_thread());
}
void SendProcessStartEvent(base::span<const uint8_t> packet_data) {
SendProcessEvent(/*version=*/4u, /*opcode=*/1u, kSystemTid, packet_data);
}
void SendProcessEndEvent(base::span<const uint8_t> packet_data) {
SendProcessEvent(/*version=*/4u, /*opcode=*/2u, kSystemTid, packet_data);
}
void SendProcessDcStartEvent(base::span<const uint8_t> packet_data) {
SendProcessEvent(/*version=*/4u, /*opcode=*/3u, kSystemTid, packet_data);
}
void SendProcessDcEndEvent(base::span<const uint8_t> packet_data) {
SendProcessEvent(/*version=*/4u, /*opcode=*/4u, kSystemTid, packet_data);
}
void SendThreadStartEvent(base::span<const uint8_t> packet_data) {
SendThreadEvent(/*version=*/4u, /*opcode=*/1u, kSystemTid, packet_data);
}
void SendThreadEndEvent(base::span<const uint8_t> packet_data) {
SendThreadEvent(/*version=*/4u, /*opcode=*/2u, kSystemTid, packet_data);
}
void SendThreadDcStartEvent(base::span<const uint8_t> packet_data) {
SendThreadEvent(/*version=*/4u, /*opcode=*/3u, kSystemTid, packet_data);
}
void SendThreadDcEndEvent(base::span<const uint8_t> packet_data) {
SendThreadEvent(/*version=*/4u, /*opcode=*/4u, kSystemTid, packet_data);
}
void SendThreadSetName(uint32_t process_id,
uint32_t thread_id,
base::wcstring_view thread_name) {
SendThreadEvent(/*version=*/2, /*opcode=*/72, kSystemTid,
EncodeThreadSetName(process_id, thread_id, thread_name));
}
const ActiveProcesses& active_processes() const {
return etw_consumer_.active_processes();
}
// Returns the collection of decoders for serialized TracePacket messages
// generated by the test's EtwConsumer.
const std::vector<std::unique_ptr<MessageAndDecoder>>& decoders() const {
return decoders_;
}
// Generates an ETW EVENT_RECORD from the Thread provider of a particular
// version and opcode with `packet_data` as its payload and sends it to the
// EtwConsumer for processing. If the EtwConsumer generates a TracePacket,
// `decoder` is constructed from it.
void SendThreadEvent(uint8_t version,
uint8_t opcode,
uint32_t thread_id,
base::span<const uint8_t> packet_data) {
ProcessEvent({0x3d6fa8d1,
0xfe05,
0x11d0,
{0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}},
version, opcode, thread_id, packet_data);
}
// Generates an ETW Process event with `packet_data` as its payload and sends
// it to the EtwConsumer for processing.
void SendProcessEvent(uint8_t version,
uint8_t opcode,
uint32_t thread_id,
base::span<const uint8_t> packet_data) {
ProcessEvent({0x3d6fa8d0,
0xfe05,
0x11d0,
{0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}},
version, opcode, thread_id, packet_data);
}
// Returns the MOF encoding of a Process event (v4 by default).
base::HeapArray<uint8_t> EncodeProcess(const ProcessData& process,
int version = 4) {
// We are using EVENT_HEADER_FLAG_64_BIT_HEADER flag, so the pointer size
// should be 8 bytes.
const size_t pointer_size = EtwConsumer::GetPointerSize(kEventHeaderFlags);
CHECK_EQ(pointer_size, sizeof(uint64_t));
return ::tracing::EncodeProcess(process, version, pointer_size);
}
private:
static constexpr uint16_t kTestProcessorIndex = 47;
static constexpr uint16_t kEventHeaderFlags = EVENT_HEADER_FLAG_64_BIT_HEADER;
// Generates an ETW EVENT_RECORD for a given trace provider of a particular
// version and opcode with `packet_data` as its payload and sends it to the
// EtwConsumer for processing. If the EtwConsumer generates a TracePacket,
// `decoder` is constructed from it.
void ProcessEvent(const GUID& provider,
uint8_t version,
uint8_t opcode,
uint32_t thread_id,
base::span<const uint8_t> packet_data) {
EVENT_RECORD event_record = {
.EventHeader = {.Flags = kEventHeaderFlags,
.ThreadId = thread_id,
.ProviderId = provider,
.EventDescriptor = {.Version = version,
.Opcode = opcode}},
.BufferContext = {.ProcessorIndex = kTestProcessorIndex},
.UserDataLength = base::checked_cast<uint16_t>(packet_data.size()),
.UserData = const_cast<uint8_t*>(packet_data.data()),
.UserContext = &etw_consumer_};
::QueryPerformanceCounter(&event_record.EventHeader.TimeStamp);
etw_consumer_.ProcessEventRecord(&event_record);
EVENT_TRACE_LOGFILE event_trace_logfile = {.Context = &etw_consumer_};
EXPECT_TRUE(etw_consumer_.ProcessBuffer(&event_trace_logfile));
}
// Called by FakeTraceWriter to process the message for a TracePacket.
void OnPacket(std::vector<uint8_t> message) {
decoders_.push_back(
std::make_unique<MessageAndDecoder>(std::move(message)));
}
EtwConsumer etw_consumer_{kClientPid,
std::make_unique<FakeTraceWriter>(
base::BindRepeating(&EtwConsumerTest::OnPacket,
base::Unretained(this)))};
// Serialized TracePacket messages and corresponding decoders emitted by the
// EtwConsumer.
std::vector<std::unique_ptr<MessageAndDecoder>> decoders_;
};
// Tests that no CSwitchEtwEvent is emitted for an empty CSwitch ETW event.
TEST_F(EtwConsumerTest, CSwitchEventIsEmpty) {
ProcessCSwitchEvent({});
ASSERT_TRUE(decoders().empty());
}
// Tests that no CSwitchEtwEvent is emitted for a small CSwitch ETW event.
TEST_F(EtwConsumerTest, CSwitchEventIsTooShort) {
static constexpr uint8_t kData[] = {0x00, 23};
ProcessCSwitchEvent({kData});
ASSERT_TRUE(decoders().empty());
}
// Tests that CSwitchEtwEvent is emitted for a CSwitch ETW event.
TEST_F(EtwConsumerTest, CSwitchEvent) {
ProcessCSwitchEvent(EncodeCSwitch(
{.new_thread_id = kClientTid, .old_thread_id = kClientTid2}));
ASSERT_EQ(decoders().size(), 1u);
std::optional<perfetto::protos::pbzero::CSwitchEtwEvent::Decoder> c_switch;
ASSERT_NO_FATAL_FAILURE(
ValidateAndDecodeCSwitch(*decoders().front(), c_switch));
EXPECT_EQ(kClientTid, c_switch->new_thread_id());
EXPECT_EQ(kClientTid2, c_switch->old_thread_id());
EXPECT_EQ(0x01, c_switch->new_thread_priority());
EXPECT_EQ(0x02, c_switch->old_thread_priority());
EXPECT_EQ(0x03u, c_switch->previous_c_state());
EXPECT_EQ(perfetto::protos::pbzero::CSwitchEtwEvent::WR_RUNDOWN,
c_switch->old_thread_wait_reason_int());
EXPECT_EQ(perfetto::protos::pbzero::CSwitchEtwEvent::USER_MODE,
c_switch->old_thread_wait_mode_int());
EXPECT_EQ(perfetto::protos::pbzero::CSwitchEtwEvent::DEFERRED_READY,
c_switch->old_thread_state_int());
EXPECT_EQ(0x04, c_switch->old_thread_wait_ideal_processor());
EXPECT_EQ(0x05u, c_switch->new_thread_wait_time());
}
// Tests that CSwitch events have the thread IDs filtered as appropriate.
TEST_F(EtwConsumerTest, CSwitchFiltering) {
// Old TID is masked if it doesn't belong to Chrome.
ProcessCSwitchEvent(EncodeCSwitch(
{.new_thread_id = kClientTid, .old_thread_id = kSystemTid}));
ASSERT_EQ(decoders().size(), 1u);
std::optional<perfetto::protos::pbzero::CSwitchEtwEvent::Decoder> c_switch;
ASSERT_NO_FATAL_FAILURE(
ValidateAndDecodeCSwitch(*decoders().back(), c_switch));
EXPECT_TRUE(c_switch->has_new_thread_id());
EXPECT_FALSE(c_switch->has_old_thread_id());
// Both TIDs are masked if neither belongs to Chrome.
ProcessCSwitchEvent(
EncodeCSwitch({.new_thread_id = kOtherTid, .old_thread_id = kSystemTid}));
ASSERT_EQ(decoders().size(), 2u);
ASSERT_NO_FATAL_FAILURE(
ValidateAndDecodeCSwitch(*decoders().back(), c_switch));
EXPECT_FALSE(c_switch->has_new_thread_id());
EXPECT_FALSE(c_switch->has_old_thread_id());
// New TID is masked if it doesn't belong to Chrome.
ProcessCSwitchEvent(
EncodeCSwitch({.new_thread_id = kOtherTid, .old_thread_id = kClientTid}));
ASSERT_EQ(decoders().size(), 3u);
ASSERT_NO_FATAL_FAILURE(
ValidateAndDecodeCSwitch(*decoders().back(), c_switch));
EXPECT_FALSE(c_switch->has_new_thread_id());
EXPECT_TRUE(c_switch->has_old_thread_id());
}
// Tests that no ReadyThreadEtwEvent is emitted for an empty ReadyThread ETW
// event.
TEST_F(EtwConsumerTest, ReadyThreadEventIsEmpty) {
ProcessReadyThreadEvent(kClientTid, {});
ASSERT_TRUE(decoders().empty());
}
// Tests that no ReadyThreadEtwEvent is emitted for a small ReadyThread ETW
// event.
TEST_F(EtwConsumerTest, ReadyThreadEventIsTooShort) {
static constexpr uint8_t kData[] = {0x00, 23};
ProcessReadyThreadEvent(kClientTid, {kData});
ASSERT_TRUE(decoders().empty());
}
// Tests that CSwitchEtwEvent is emitted for a CSwitch ETW event.
TEST_F(EtwConsumerTest, ReadyThreadEvent) {
ProcessReadyThreadEvent(kClientTid,
EncodeReadyThread({.t_thread_id = kClientTid2}));
ASSERT_EQ(decoders().size(), 1u);
std::optional<perfetto::protos::pbzero::EtwTraceEvent::Decoder> event;
std::optional<perfetto::protos::pbzero::ReadyThreadEtwEvent::Decoder>
ready_thread;
ASSERT_NO_FATAL_FAILURE(
ValidateAndDecodeReadyThread(*decoders().front(), event, ready_thread));
EXPECT_EQ(kClientTid, event->thread_id());
EXPECT_EQ(kClientTid2, ready_thread->t_thread_id());
EXPECT_EQ(0x01, ready_thread->adjust_reason_int());
EXPECT_EQ(0, ready_thread->adjust_increment());
EXPECT_EQ(0x01, ready_thread->flag_int());
}
// Tests that ReadyThread events have the thread IDs filtered as appropriate.
TEST_F(EtwConsumerTest, ReadyThreadFiltering) {
// Target TID is masked if it doesn't belong to Chrome.
ProcessReadyThreadEvent(kClientTid,
EncodeReadyThread({.t_thread_id = kSystemTid}));
ASSERT_EQ(decoders().size(), 1u);
std::optional<perfetto::protos::pbzero::EtwTraceEvent::Decoder> event;
std::optional<perfetto::protos::pbzero::ReadyThreadEtwEvent::Decoder>
ready_thread;
ASSERT_NO_FATAL_FAILURE(
ValidateAndDecodeReadyThread(*decoders().back(), event, ready_thread));
EXPECT_TRUE(event->has_thread_id());
EXPECT_FALSE(ready_thread->has_t_thread_id());
// Both TID and target TID are masked if neither belongs to Chrome.
ProcessReadyThreadEvent(kOtherTid,
EncodeReadyThread({.t_thread_id = kSystemTid}));
ASSERT_EQ(decoders().size(), 2u);
ASSERT_NO_FATAL_FAILURE(
ValidateAndDecodeReadyThread(*decoders().back(), event, ready_thread));
EXPECT_FALSE(event->has_thread_id());
EXPECT_FALSE(ready_thread->has_t_thread_id());
// TID is masked if it doesn't belong to Chrome.
ProcessReadyThreadEvent(kOtherTid,
EncodeReadyThread({.t_thread_id = kClientTid}));
ASSERT_EQ(decoders().size(), 3u);
ASSERT_NO_FATAL_FAILURE(
ValidateAndDecodeReadyThread(*decoders().back(), event, ready_thread));
EXPECT_FALSE(event->has_thread_id());
EXPECT_TRUE(ready_thread->has_t_thread_id());
}
TEST_F(EtwConsumerTest, ThreadSetName) {
SendThreadSetName(kClientPid, kClientTid, L"kaboom");
ASSERT_EQ(active_processes().GetThreadName(kClientTid), L"kaboom");
}
TEST_F(EtwConsumerTest, ProcessStartIsEmpty) {
SendProcessStartEvent({});
}
// Tests that different versions of a Process event are handled.
TEST_F(EtwConsumerTest, ProcessVersions) {
static constexpr uint32_t kPid = 0x4000;
for (int version = 0; version <= 4; ++version) {
ASSERT_TRUE(active_processes().GetProcessImageFileName(kPid).empty());
auto payload = EncodeProcess({.process_id = kPid,
.parent_id = kSystemPid,
.image_file_name = "himom"},
/*version=*/version);
SendProcessEvent(/*version=*/version, /*opcode=*/1u, kSystemTid,
payload); // Start
ASSERT_EQ(active_processes().GetProcessImageFileName(kPid), "himom");
SendProcessEvent(/*version=*/version, /*opcode=*/2u, kSystemTid,
payload); // End
ASSERT_TRUE(active_processes().GetProcessImageFileName(kPid).empty());
}
}
TEST_F(EtwConsumerTest, ThreadVersions) {
static constexpr uint32_t kTid = 0x4000;
for (int version = 0; version <= 4; ++version) {
ASSERT_EQ(active_processes().GetThreadCategory(kTid),
ActiveProcesses::Category::kOther);
auto payload = EncodeThread(
{.process_id = kClientPid, .thread_id = kTid, .thread_name = {}},
/*version=*/version);
SendThreadEvent(/*version=*/version, /*opcode=*/1u, kSystemTid,
payload); // Start
ASSERT_EQ(active_processes().GetThreadCategory(kTid),
ActiveProcesses::Category::kClient);
SendThreadEvent(/*version=*/version, /*opcode=*/2u, kSystemTid,
payload); // End
ASSERT_EQ(active_processes().GetThreadCategory(kTid),
ActiveProcesses::Category::kOther);
}
}
TEST_F(EtwConsumerTest, ProcessEndIsEmpty) {
SendProcessEndEvent({});
}
TEST_F(EtwConsumerTest, ThreadStartIsEmpty) {
SendThreadStartEvent({});
}
TEST_F(EtwConsumerTest, ThreadEndIsEmpty) {
SendThreadEndEvent({});
}
} // namespace tracing