blob: 8acc4509e676b3b009c06509d59ccc4f4ac9e165 [file] [log] [blame]
/*
* Copyright (C) 2022 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_processor/importers/fuchsia/fuchsia_trace_parser.h"
#include "src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "perfetto/trace_processor/trace_blob.h"
#include "src/trace_processor/importers/common/args_tracker.h"
#include "src/trace_processor/importers/common/args_translation_table.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/flow_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
#include "src/trace_processor/importers/proto/additional_modules.h"
#include "src/trace_processor/importers/proto/default_modules.h"
#include "src/trace_processor/importers/proto/proto_trace_parser.h"
#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
#include "src/trace_processor/sorter/trace_sorter.h"
#include "src/trace_processor/storage/metadata.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/util/descriptors.h"
#include "test/gtest_and_gmock.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "protos/perfetto/common/sys_stats_counters.pbzero.h"
#include "protos/perfetto/config/trace_config.pbzero.h"
#include "protos/perfetto/trace/android/packages_list.pbzero.h"
#include "protos/perfetto/trace/chrome/chrome_benchmark_metadata.pbzero.h"
#include "protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h"
#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
#include "protos/perfetto/trace/ftrace/ftrace.pbzero.h"
#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
#include "protos/perfetto/trace/ftrace/generic.pbzero.h"
#include "protos/perfetto/trace/ftrace/power.pbzero.h"
#include "protos/perfetto/trace/ftrace/sched.pbzero.h"
#include "protos/perfetto/trace/ftrace/task.pbzero.h"
#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
#include "protos/perfetto/trace/ps/process_tree.pbzero.h"
#include "protos/perfetto/trace/sys_stats/sys_stats.pbzero.h"
#include "protos/perfetto/trace/trace.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
#include "protos/perfetto/trace/track_event/chrome_thread_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/counter_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
#include "protos/perfetto/trace/track_event/log_message.pbzero.h"
#include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/source_location.pbzero.h"
#include "protos/perfetto/trace/track_event/task_execution.pbzero.h"
#include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
#include "protos/perfetto/trace/track_event/track_event.pbzero.h"
namespace perfetto {
namespace trace_processor {
namespace {
using ::testing::_;
using ::testing::Args;
using ::testing::AtLeast;
using ::testing::DoAll;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::IgnoreResult;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::InvokeArgument;
using ::testing::NiceMock;
using ::testing::Pointwise;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::UnorderedElementsAreArray;
class MockSchedEventTracker : public SchedEventTracker {
public:
explicit MockSchedEventTracker(TraceProcessorContext* context)
: SchedEventTracker(context) {}
MOCK_METHOD(void,
PushSchedSwitch,
(uint32_t cpu,
int64_t timestamp,
uint32_t prev_pid,
base::StringView prev_comm,
int32_t prev_prio,
int64_t prev_state,
uint32_t next_pid,
base::StringView next_comm,
int32_t next_prio),
(override));
};
class MockProcessTracker : public ProcessTracker {
public:
explicit MockProcessTracker(TraceProcessorContext* context)
: ProcessTracker(context) {}
MOCK_METHOD(UniquePid,
SetProcessMetadata,
(uint32_t pid,
std::optional<uint32_t> ppid,
base::StringView process_name,
base::StringView cmdline),
(override));
MOCK_METHOD(UniqueTid,
UpdateThreadName,
(uint32_t tid,
StringId thread_name_id,
ThreadNamePriority priority),
(override));
MOCK_METHOD(void,
UpdateThreadNameByUtid,
(UniqueTid utid,
StringId thread_name_id,
ThreadNamePriority priority),
(override));
MOCK_METHOD(UniqueTid,
UpdateThread,
(uint32_t tid, uint32_t tgid),
(override));
MOCK_METHOD(UniquePid, GetOrCreateProcess, (uint32_t pid), (override));
MOCK_METHOD(void,
SetProcessNameIfUnset,
(UniquePid upid, StringId process_name_id),
(override));
};
class MockBoundInserter : public ArgsTracker::BoundInserter {
public:
MockBoundInserter()
: ArgsTracker::BoundInserter(&tracker_, nullptr, 0u), tracker_(nullptr) {
ON_CALL(*this, AddArg(_, _, _, _)).WillByDefault(ReturnRef(*this));
}
MOCK_METHOD(ArgsTracker::BoundInserter&,
AddArg,
(StringId flat_key,
StringId key,
Variadic v,
ArgsTracker::UpdatePolicy update_policy),
(override));
private:
ArgsTracker tracker_;
};
class MockEventTracker : public EventTracker {
public:
explicit MockEventTracker(TraceProcessorContext* context)
: EventTracker(context) {}
~MockEventTracker() override = default;
MOCK_METHOD(void,
PushSchedSwitch,
(uint32_t cpu,
int64_t timestamp,
uint32_t prev_pid,
base::StringView prev_comm,
int32_t prev_prio,
int64_t prev_state,
uint32_t next_pid,
base::StringView next_comm,
int32_t next_prio));
MOCK_METHOD(std::optional<CounterId>,
PushCounter,
(int64_t timestamp, double value, TrackId track_id),
(override));
};
class MockSliceTracker : public SliceTracker {
public:
explicit MockSliceTracker(TraceProcessorContext* context)
: SliceTracker(context) {}
MOCK_METHOD(std::optional<SliceId>,
Begin,
(int64_t timestamp,
TrackId track_id,
StringId cat,
StringId name,
SetArgsCallback args_callback),
(override));
MOCK_METHOD(std::optional<SliceId>,
End,
(int64_t timestamp,
TrackId track_id,
StringId cat,
StringId name,
SetArgsCallback args_callback),
(override));
MOCK_METHOD(std::optional<SliceId>,
Scoped,
(int64_t timestamp,
TrackId track_id,
StringId cat,
StringId name,
int64_t duration,
SetArgsCallback args_callback),
(override));
MOCK_METHOD(std::optional<SliceId>,
StartSlice,
(int64_t timestamp,
TrackId track_id,
SetArgsCallback args_callback,
std::function<SliceId()> inserter),
(override));
};
class FuchsiaTraceParserTest : public ::testing::Test {
public:
FuchsiaTraceParserTest() {
context_.storage.reset(new TraceStorage());
storage_ = context_.storage.get();
context_.track_tracker.reset(new TrackTracker(&context_));
context_.global_args_tracker.reset(
new GlobalArgsTracker(context_.storage.get()));
context_.global_stack_profile_tracker.reset(
new GlobalStackProfileTracker());
context_.args_tracker.reset(new ArgsTracker(&context_));
context_.args_translation_table.reset(new ArgsTranslationTable(storage_));
context_.metadata_tracker.reset(
new MetadataTracker(context_.storage.get()));
event_ = new MockEventTracker(&context_);
context_.event_tracker.reset(event_);
sched_ = new MockSchedEventTracker(&context_);
context_.sched_tracker.reset(sched_);
process_ = new NiceMock<MockProcessTracker>(&context_);
context_.process_tracker.reset(process_);
slice_ = new NiceMock<MockSliceTracker>(&context_);
context_.slice_tracker.reset(slice_);
context_.slice_translation_table.reset(new SliceTranslationTable(storage_));
context_.clock_tracker.reset(new ClockTracker(&context_));
clock_ = context_.clock_tracker.get();
context_.flow_tracker.reset(new FlowTracker(&context_));
context_.sorter.reset(new TraceSorter(&context_, CreateParser(),
TraceSorter::SortingMode::kFullSort));
context_.descriptor_pool_.reset(new DescriptorPool());
RegisterDefaultModules(&context_);
RegisterAdditionalModules(&context_);
}
void push_word(uint64_t word) { trace_bytes_.push_back(word); }
void ResetTraceBuffers() {
trace_bytes_.clear();
// Write the FXT Magic Bytes
push_word(0x0016547846040010);
}
void SetUp() override { ResetTraceBuffers(); }
util::Status Tokenize() {
const size_t num_bytes = trace_bytes_.size() * sizeof(uint64_t);
std::unique_ptr<uint8_t[]> raw_trace(new uint8_t[num_bytes]);
memcpy(raw_trace.get(), trace_bytes_.data(), num_bytes);
context_.chunk_reader.reset(new FuchsiaTraceTokenizer(&context_));
auto status = context_.chunk_reader->Parse(TraceBlobView(
TraceBlob::TakeOwnership(std::move(raw_trace), num_bytes)));
ResetTraceBuffers();
return status;
}
protected:
std::vector<uint64_t> trace_bytes_;
std::unique_ptr<TraceParser> CreateParser() {
return std::unique_ptr<TraceParser>(new FuchsiaTraceParser(&context_));
}
TraceProcessorContext context_;
MockEventTracker* event_;
MockSchedEventTracker* sched_;
MockProcessTracker* process_;
MockSliceTracker* slice_;
ClockTracker* clock_;
TraceStorage* storage_;
};
TEST_F(FuchsiaTraceParserTest, CorruptedFxt) {
// Invalid record of size 0
push_word(0x0016547846040000);
EXPECT_FALSE(Tokenize().ok());
}
TEST_F(FuchsiaTraceParserTest, InlineInstantEvent) {
// Inline name of 8 bytes
uint64_t name_ref = uint64_t{0x8008} << 48;
// Inline category of 8 bytes
uint64_t category_ref = uint64_t{0x8008} << 32;
// Inline threadref
uint64_t threadref = uint64_t{0};
// Instant Event
uint64_t event_type = 0 << 16;
uint64_t size = 6 << 4;
uint64_t record_type = 4;
auto header =
name_ref | category_ref | threadref | event_type | size | record_type;
push_word(header);
// Timestamp
push_word(0xAAAAAAAAAAAAAAAA);
// Pid + tid
push_word(0xBBBBBBBBBBBBBBBB);
push_word(0xCCCCCCCCCCCCCCCC);
// Inline Category
push_word(0xDDDDDDDDDDDDDDDD);
// Inline Name
push_word(0xEEEEEEEEEEEEEEEE);
EXPECT_TRUE(Tokenize().ok());
EXPECT_EQ(context_.storage->stats()[stats::fuchsia_invalid_event].value, 0);
}
TEST_F(FuchsiaTraceParserTest, FxtWithProtos) {
// Serialize some protos to bytes
protozero::HeapBuffered<protos::pbzero::Trace> protos;
{
auto* packet = protos->add_packet();
packet->set_trusted_packet_sequence_id(1);
packet->set_incremental_state_cleared(true);
auto* thread_desc = packet->set_thread_descriptor();
thread_desc->set_pid(15);
thread_desc->set_tid(16);
thread_desc->set_reference_timestamp_us(1000);
thread_desc->set_reference_thread_time_us(2000);
}
{
auto* packet = protos->add_packet();
packet->set_trusted_packet_sequence_id(1);
auto* event = packet->set_track_event();
event->set_timestamp_delta_us(10); // absolute: 1010.
event->set_thread_time_delta_us(5); // absolute: 2005.
event->add_category_iids(1);
auto* legacy_event = event->set_legacy_event();
legacy_event->set_name_iid(1);
legacy_event->set_phase('B');
}
{
auto* packet = protos->add_packet();
packet->set_trusted_packet_sequence_id(1);
auto* event = packet->set_track_event();
event->set_timestamp_delta_us(10); // absolute: 1020.
event->set_thread_time_delta_us(5); // absolute: 2010.
event->add_category_iids(1);
auto* legacy_event = event->set_legacy_event();
legacy_event->set_name_iid(1);
legacy_event->set_phase('E');
}
{
auto* packet = protos->add_packet();
packet->set_trusted_packet_sequence_id(1);
auto* event = packet->set_track_event();
event->set_timestamp_absolute_us(1005);
event->set_thread_time_absolute_us(2003);
event->add_category_iids(2);
event->add_category_iids(3);
auto* legacy_event = event->set_legacy_event();
legacy_event->set_name_iid(2);
legacy_event->set_phase('X');
legacy_event->set_duration_us(23); // absolute end: 1028.
legacy_event->set_thread_duration_us(12); // absolute end: 2015.
}
protos->Finalize();
std::vector<uint8_t> perfetto_bytes = protos.SerializeAsArray();
// Set up an FXT Perfetto Blob Header
uint64_t blob_type_perfetto = uint64_t{3} << 48;
uint64_t unpadded_blob_size_bytes = uint64_t{perfetto_bytes.size()} << 32;
uint64_t blob_name_ref = uint64_t{0x8008} << 16;
uint64_t size_words = ((perfetto_bytes.size() + 7) / 8 + 2) << 4;
uint64_t record_type = 5;
uint64_t header = blob_type_perfetto | unpadded_blob_size_bytes |
blob_name_ref | size_words | record_type;
// Pad the blob to a multiple of 8 bytes.
while (perfetto_bytes.size() % 8) {
perfetto_bytes.push_back(0);
}
push_word(header);
// Inline Name Ref
push_word(0xBBBBBBBBBBBBBBBB);
trace_bytes_.insert(trace_bytes_.end(),
reinterpret_cast<uint64_t*>(perfetto_bytes.data()),
reinterpret_cast<uint64_t*>(perfetto_bytes.data() +
perfetto_bytes.size()));
EXPECT_CALL(*process_, UpdateThread(16, 15)).WillRepeatedly(Return(1u));
tables::ThreadTable::Row row(16);
row.upid = 1u;
storage_->mutable_thread_table()->Insert(row);
MockBoundInserter inserter;
StringId unknown_cat = storage_->InternString("unknown(1)");
ASSERT_NE(storage_, nullptr);
constexpr TrackId track{0u};
constexpr TrackId thread_time_track{1u};
InSequence in_sequence; // Below slices should be sorted by timestamp.
// Only the begin thread time can be imported into the counter table.
EXPECT_CALL(*event_, PushCounter(1005000, testing::DoubleEq(2003000),
thread_time_track));
EXPECT_CALL(*slice_, StartSlice(1005000, track, _, _))
.WillOnce(DoAll(IgnoreResult(InvokeArgument<3>()),
InvokeArgument<2>(&inserter), Return(SliceId(0u))));
EXPECT_CALL(*event_, PushCounter(1010000, testing::DoubleEq(2005000),
thread_time_track));
EXPECT_CALL(*slice_, StartSlice(1010000, track, _, _))
.WillOnce(DoAll(IgnoreResult(InvokeArgument<3>()),
InvokeArgument<2>(&inserter), Return(SliceId(1u))));
EXPECT_CALL(*event_, PushCounter(1020000, testing::DoubleEq(2010000),
thread_time_track));
EXPECT_CALL(*slice_, End(1020000, track, unknown_cat, kNullStringId, _))
.WillOnce(DoAll(InvokeArgument<4>(&inserter), Return(SliceId(1u))));
auto status = Tokenize();
EXPECT_TRUE(status.ok());
context_.sorter->ExtractEventsForced();
}
TEST_F(FuchsiaTraceParserTest, SchedulerEvents) {
uint64_t thread1_tid = 0x1AAA'AAAA'AAAA'AAAA;
uint64_t thread2_tid = 0x2CCC'CCCC'CCCC'CCCC;
// We'll emit a wake up for thread 1, a switch to thread 2, and a switch back
// to thread 1 and expect to see that the process tracker was properly updated
uint64_t wakeup_record_type = uint64_t{2} << 60;
uint64_t context_switch_record_type = uint64_t{1} << 60;
uint64_t cpu = 1 << 20;
uint64_t record_type = 8;
uint64_t wakeup_size = uint64_t{3} << 4;
uint64_t context_switch_size = uint64_t{4} << 4;
uint64_t wakeup_header = wakeup_record_type | cpu | record_type | wakeup_size;
push_word(wakeup_header);
// Timestamp
push_word(0x1);
// wakeup tid
push_word(thread1_tid);
uint64_t context_switch_header =
context_switch_record_type | cpu | record_type | context_switch_size;
push_word(context_switch_header);
// Timestamp
push_word(0x2);
// outgoing tid
push_word(thread1_tid);
// incoming tid
push_word(thread2_tid);
push_word(context_switch_header);
// Timestamp
push_word(0x3);
// outgoing tid
push_word(thread2_tid);
// incoming tid
push_word(thread1_tid);
// We should get:
// - A thread1 update call on wake up
// - thread1 & thread2 update calls on the first context switch
// - thread2 & thread1 update cals on the second context switch
EXPECT_CALL(*process_, UpdateThread(static_cast<uint32_t>(thread1_tid), _))
.Times(3);
EXPECT_CALL(*process_, UpdateThread(static_cast<uint32_t>(thread2_tid), _))
.Times(2);
EXPECT_TRUE(Tokenize().ok());
EXPECT_EQ(context_.storage->stats()[stats::fuchsia_invalid_event].value, 0);
context_.sorter->ExtractEventsForced();
}
TEST_F(FuchsiaTraceParserTest, LegacySchedulerEvents) {
uint64_t thread1_pid = 0x1AAA'AAAA'AAAA'AAAA;
uint64_t thread1_tid = 0x1BBB'BBBB'BBBB'BBBB;
uint64_t thread2_pid = 0x2CCC'CCCC'CCCC'CCCC;
uint64_t thread2_tid = 0x2DDD'DDDD'DDDD'DDDD;
// We'll emit a wake up for thread 1, a switch to thread 2, and a switch back
// to thread 1 and expect to see that the process tracker was properly updated
uint64_t context_switch_size = uint64_t{6} << 4;
uint64_t cpu = 1 << 16;
uint64_t record_type = 8;
uint64_t outoing_state = 2 << 24;
uint64_t outoing_thread = 0; // Inline thread-ref
uint64_t incoming_thread = 0; // Inline thread-ref
uint64_t outgoing_prio = uint64_t{1} << 44;
uint64_t incoming_prio = uint64_t{1} << 52;
uint64_t outgoing_idle_prio = uint64_t{0} << 44;
uint64_t context_switch_header =
record_type | context_switch_size | cpu | outoing_state | outoing_thread |
incoming_thread | outgoing_prio | incoming_prio;
uint64_t wakeup_header = record_type | context_switch_size | cpu |
outoing_state | outoing_thread | incoming_thread |
outgoing_idle_prio | incoming_prio;
push_word(wakeup_header);
// Timestamp
push_word(0x1);
// outgoing pid+tid
push_word(0); // Idle thread
push_word(0); // Idle thread
// incoming pid+tid
push_word(thread1_pid);
push_word(thread1_tid);
push_word(context_switch_header);
// Timestamp
push_word(0x2);
// outgoing pid+tid
push_word(thread1_pid);
push_word(thread1_tid);
// incoming pid+tid
push_word(thread2_pid);
push_word(thread2_tid);
push_word(context_switch_header);
// Timestamp
push_word(0x3);
// outgoing pid+tid
push_word(thread2_pid);
push_word(thread2_tid);
// incoming pid+tid
push_word(thread1_pid);
push_word(thread1_tid);
// We should get:
// - A thread1 update call on wake up
// - thread1 & thread2 update calls on the first context switch
// - thread2 & thread1 update cals on the second context switch
EXPECT_CALL(*process_, UpdateThread(static_cast<uint32_t>(thread1_tid), _))
.Times(3);
EXPECT_CALL(*process_, UpdateThread(static_cast<uint32_t>(thread2_tid), _))
.Times(2);
EXPECT_TRUE(Tokenize().ok());
EXPECT_EQ(context_.storage->stats()[stats::fuchsia_invalid_event].value, 0);
context_.sorter->ExtractEventsForced();
}
} // namespace
} // namespace trace_processor
} // namespace perfetto