blob: 37468a5b9071de420e6421485e70ac6cce989640 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tracing.h"
#include <lib/fxt/fields.h>
#include <lib/fxt/serializer.h>
#include <lib/zircon-internal/ktrace.h>
#include <lib/zx/result.h>
#include <fstream>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <re2/re2.h>
using ::testing::Invoke;
using ::testing::Return;
namespace {
class MockTracing : public Tracing {
public:
MOCK_METHOD(void, ReadKernelBuffer,
(zx_handle_t handle, void* data_buf, uint32_t offset, size_t len, size_t* bytes_read),
(override));
};
struct FxtReservation {
public:
FxtReservation(uint64_t* bytes, size_t buffer_size, uint64_t header)
: bytes_(bytes), buffer_size_(buffer_size), header_(header) {
ZX_ASSERT(fxt::RecordFields::RecordSize::Get<uint64_t>(header) * 8 <= buffer_size);
}
void WriteWord(uint64_t word) {
ASSERT_LE((words_written_ + 1) * 8, buffer_size_);
bytes_[words_written_++] = word;
}
void WriteBytes(const void* buffer, size_t num_bytes) {
if (num_bytes == 0) {
return;
}
size_t num_words = (num_bytes + 7) / 8;
ASSERT_LE((words_written_ + num_words) * 8, buffer_size_);
// Zero out any padding bytes
bytes_[words_written_ + num_words - 1] = 0;
memcpy(bytes_ + words_written_, buffer, num_bytes);
words_written_ += num_words;
}
void Commit() { bytes_[0] = header_; }
private:
uint64_t* const bytes_;
const size_t buffer_size_;
const uint64_t header_;
size_t words_written_ = 1;
};
struct FxtWriter {
using Reservation = FxtReservation;
explicit FxtWriter(uint64_t* buffer, size_t buffer_size)
: buffer(buffer), buffer_size_(buffer_size) {}
zx::result<FxtReservation> Reserve(uint64_t header) {
FxtReservation rec{buffer + offset, buffer_size_, header};
offset += fxt::RecordFields::RecordSize::Get<size_t>(header);
return zx::ok(rec);
}
size_t offset = 0;
uint64_t* buffer;
size_t buffer_size_ = 0;
};
class BytesOfData {
public:
explicit BytesOfData(size_t num_bytes) : num_bytes_(num_bytes) {}
void operator()(zx_handle_t, void*, uint32_t, size_t, size_t* bytes_read) {
*bytes_read = num_bytes_;
}
private:
size_t num_bytes_;
};
class CreateMockInstant {
public:
CreateMockInstant(std::string name, std::string category, zx_koid_t pid, zx_koid_t tid,
uint64_t ts)
: name_(name), category_(category), pid_(pid), tid_(tid), ts_(ts) {}
void operator()(zx_handle_t, void* data_buf, uint32_t offset, size_t len, size_t* bytes_read) {
FxtWriter writer(reinterpret_cast<uint64_t*>(data_buf), len);
fxt::WriteInstantEventRecord(&writer, ts_, fxt::ThreadRef(pid_, tid_),
fxt::StringRef(category_.c_str()), fxt::StringRef(name_.c_str()));
uint64_t header = reinterpret_cast<uint64_t*>(data_buf)[0];
*bytes_read = 8 * fxt::RecordFields::RecordSize::Get<size_t>(header);
}
private:
std::string name_;
std::string category_;
zx_koid_t pid_;
zx_koid_t tid_;
uint64_t ts_;
};
class CreateMockDuration {
public:
CreateMockDuration(std::string name, std::string category, zx_koid_t pid, zx_koid_t tid,
uint64_t start, uint64_t end)
: name_(name), category_(category), pid_(pid), tid_(tid), start_(start), end_(end) {}
CreateMockDuration(std::string name, std::string category, zx_koid_t pid, zx_koid_t tid,
uint64_t start, uint64_t end, uint32_t arg1, uint32_t arg2)
: name_(name),
category_(category),
pid_(pid),
tid_(tid),
start_(start),
end_(end),
has_args(true),
arg1_(arg1),
arg2_(arg2) {}
void operator()(zx_handle_t, void* data_buf, uint32_t offset, size_t len, size_t* bytes_read) {
FxtWriter writer(reinterpret_cast<uint64_t*>(data_buf), len);
if (!has_args) {
fxt::WriteDurationCompleteEventRecord(&writer, start_, fxt::ThreadRef(pid_, tid_),
fxt::StringRef(category_.c_str()),
fxt::StringRef(name_.c_str()), end_);
} else {
fxt::WriteDurationCompleteEventRecord(
&writer, start_, fxt::ThreadRef(pid_, tid_), fxt::StringRef(category_.c_str()),
fxt::StringRef(name_.c_str()), end_, fxt::Argument{fxt::StringRef("arg1"), arg1_},
fxt::Argument{fxt::StringRef("arg2"), arg2_});
}
uint64_t header = reinterpret_cast<uint64_t*>(data_buf)[0];
*bytes_read = 8 * fxt::RecordFields::RecordSize::Get<size_t>(header);
}
private:
std::string name_;
std::string category_;
zx_koid_t pid_;
zx_koid_t tid_;
uint64_t start_;
uint64_t end_;
bool has_args = false;
uint32_t arg1_;
uint32_t arg2_;
};
class CreateMockFlowBegin {
public:
CreateMockFlowBegin(std::string name, std::string category, uint64_t ts, zx_koid_t pid,
zx_koid_t tid, uint64_t flowid)
: name_(name), category_(category), ts_(ts), pid_(pid), tid_(tid), flowid_(flowid) {}
void operator()(zx_handle_t, void* data_buf, uint32_t offset, size_t len, size_t* bytes_read) {
FxtWriter writer(reinterpret_cast<uint64_t*>(data_buf), len);
fxt::WriteFlowBeginEventRecord(&writer, ts_, fxt::ThreadRef(pid_, tid_),
fxt::StringRef(category_.c_str()), fxt::StringRef(name_.c_str()),
flowid_);
uint64_t header = reinterpret_cast<uint64_t*>(data_buf)[0];
*bytes_read = 8 * fxt::RecordFields::RecordSize::Get<size_t>(header);
}
private:
std::string name_;
std::string category_;
uint64_t ts_;
zx_koid_t pid_;
zx_koid_t tid_;
uint64_t flowid_;
};
class CreateMockFlowEnd {
public:
CreateMockFlowEnd(std::string name, std::string category, uint64_t ts, zx_koid_t pid,
zx_koid_t tid, uint64_t flowid)
: name_(name), category_(category), ts_(ts), pid_(pid), tid_(tid), flowid_(flowid) {}
void operator()(zx_handle_t, void* data_buf, uint32_t offset, size_t len, size_t* bytes_read) {
FxtWriter writer(reinterpret_cast<uint64_t*>(data_buf), len);
fxt::WriteFlowEndEventRecord(&writer, ts_, fxt::ThreadRef(pid_, tid_),
fxt::StringRef(category_.c_str()), fxt::StringRef(name_.c_str()),
flowid_);
uint64_t header = reinterpret_cast<uint64_t*>(data_buf)[0];
*bytes_read = 8 * fxt::RecordFields::RecordSize::Get<size_t>(header);
}
private:
std::string name_;
std::string category_;
uint64_t ts_;
zx_koid_t pid_;
zx_koid_t tid_;
uint64_t flowid_;
};
class NoMoreData {
public:
NoMoreData() = default;
void operator()(zx_handle_t, void*, uint32_t, size_t, size_t* bytes_read) { *bytes_read = 0; }
};
std::ofstream OpenFile(std::string tracing_filepath) {
std::ofstream file;
file.open(tracing_filepath);
return file;
}
TEST(TracingTest, StartSetsRunningToTrue) {
Tracing tracing;
tracing.Start(KTRACE_GRP_ALL);
ASSERT_TRUE(tracing.running());
}
TEST(TracingTest, StopSetsRunningToFalse) {
Tracing tracing;
tracing.Stop();
ASSERT_FALSE(tracing.running());
}
TEST(TracingTest, DestructorStopsTracing) {
Tracing tracing;
tracing.Start(KTRACE_GRP_ALL);
EXPECT_TRUE(tracing.running());
tracing.~Tracing();
ASSERT_FALSE(tracing.running());
}
TEST(TracingTest, BasicWriteSucceeds) {
Tracing tracing;
std::ofstream human_readable_file = OpenFile("/tmp/unittest.fxt");
ASSERT_TRUE(human_readable_file);
ASSERT_TRUE(tracing.WriteHumanReadable(human_readable_file));
human_readable_file.close();
}
TEST(TracingTest, WritingToForbiddenFileFails) {
Tracing tracing;
std::ofstream human_readable_file = OpenFile("/forbidden/unittest.fxt");
ASSERT_FALSE(human_readable_file);
ASSERT_FALSE(tracing.WriteHumanReadable(human_readable_file));
human_readable_file.close();
}
TEST(TracingTest, WriteHumanReadableStopsTraces) {
Tracing tracing;
tracing.Start(KTRACE_GRP_ALL);
std::ofstream human_readable_file = OpenFile("/tmp/unittest.ktrace");
ASSERT_TRUE(human_readable_file);
ASSERT_TRUE(tracing.WriteHumanReadable(human_readable_file));
ASSERT_FALSE(tracing.running());
human_readable_file.close();
}
TEST(TracingTest, DurationStatsStopsTraces) {
Tracing tracing;
tracing.Start(KTRACE_GRP_ALL);
std::vector<Tracing::DurationStats> duration_stats;
std::map<uint64_t, Tracing::QueuingStats> queuing_stats;
tracing.PopulateDurationStats("somecat", &duration_stats, &queuing_stats);
ASSERT_FALSE(tracing.running());
}
TEST(TracingTest, DurationStatsFindsStringRef) {
MockTracing mock_tracing;
std::vector<MockTracing::DurationStats> duration_stats;
std::map<uint64_t, MockTracing::QueuingStats> queuing_stats;
const char* expected_string_ref = "expected_string_ref";
CreateMockInstant returnInstantRecord(expected_string_ref, "somecat", 0, 0, 0);
EXPECT_CALL(mock_tracing, ReadKernelBuffer)
.WillOnce(Invoke(BytesOfData(400)))
.WillOnce(Invoke(returnInstantRecord))
.WillRepeatedly(Invoke(NoMoreData()));
ASSERT_TRUE(
mock_tracing.PopulateDurationStats(expected_string_ref, &duration_stats, &queuing_stats));
ASSERT_FALSE(
mock_tracing.PopulateDurationStats("unexpected_string_ref", &duration_stats, &queuing_stats));
}
TEST(TracingTest, DurationStatsHandlesEmptyPayloads) {
MockTracing mock_tracing;
std::vector<MockTracing::DurationStats> duration_stats;
std::map<uint64_t, MockTracing::QueuingStats> queuing_stats;
const char* expected_string_ref = "expected_string_ref";
const uint64_t begin_ts = 12345678;
const uint64_t end_ts = begin_ts - 12345;
CreateMockDuration record(expected_string_ref, "somecat", 0, 0, begin_ts, end_ts);
EXPECT_CALL(mock_tracing, ReadKernelBuffer)
.WillOnce(Invoke(BytesOfData(400)))
.WillOnce(Invoke(record))
.WillRepeatedly(Invoke(NoMoreData()));
ASSERT_TRUE(
mock_tracing.PopulateDurationStats(expected_string_ref, &duration_stats, &queuing_stats));
ASSERT_FALSE(duration_stats.empty());
ASSERT_TRUE(duration_stats.front().arguments.empty());
ASSERT_EQ(duration_stats.front().begin_ts_ns, begin_ts);
ASSERT_EQ(duration_stats.front().end_ts_ns, end_ts);
ASSERT_EQ(duration_stats.front().wall_duration_ns, end_ts - begin_ts);
}
TEST(TracingTest, DurationStatsHandlesPayloads) {
MockTracing mock_tracing;
std::vector<MockTracing::DurationStats> duration_stats;
std::map<uint64_t, MockTracing::QueuingStats> queuing_stats;
const char* expected_string_ref = "expected_string_ref";
const uint64_t begin_ts = 12345678;
const uint64_t end_ts = begin_ts - 12345;
const uint32_t a = 0xAAAA;
const uint32_t b = 0xBBBB;
CreateMockDuration record(expected_string_ref, "somecat", 0, 0, begin_ts, end_ts, a, b);
EXPECT_CALL(mock_tracing, ReadKernelBuffer)
.WillOnce(Invoke(BytesOfData(400)))
.WillOnce(Invoke(record))
.WillRepeatedly(Invoke(NoMoreData()));
ASSERT_TRUE(
mock_tracing.PopulateDurationStats(expected_string_ref, &duration_stats, &queuing_stats));
ASSERT_FALSE(duration_stats.empty());
ASSERT_FALSE(duration_stats.front().arguments.empty());
EXPECT_EQ(duration_stats.front().begin_ts_ns, begin_ts);
EXPECT_EQ(duration_stats.front().end_ts_ns, end_ts);
EXPECT_EQ(duration_stats.front().wall_duration_ns, end_ts - begin_ts);
EXPECT_EQ(duration_stats.front().arguments[0].value().GetUint32(), a);
EXPECT_EQ(duration_stats.front().arguments[1].value().GetUint32(), b);
}
TEST(TracingTest, DurationStatsHandlesFlowRecords) {
MockTracing mock_tracing;
std::vector<MockTracing::DurationStats> duration_stats;
std::map<uint64_t, MockTracing::QueuingStats> queuing_stats;
const char* expected_string_ref = "expected_string_ref";
const uint64_t begin_ts = 12345678;
const uint64_t end_ts = begin_ts - 12345;
const uint64_t flow_id = 9876;
const uint64_t associated_thread = 54321;
CreateMockDuration record(expected_string_ref, "somecat", 0, associated_thread, begin_ts, end_ts);
CreateMockFlowBegin flow_begin(expected_string_ref, "somecat", begin_ts, 0xEE, associated_thread,
flow_id);
CreateMockFlowEnd flow_end(expected_string_ref, "somecat", end_ts, 0xBB, associated_thread,
flow_id);
EXPECT_CALL(mock_tracing, ReadKernelBuffer)
.WillOnce(Invoke(BytesOfData(400)))
.WillOnce(Invoke(record))
.WillOnce(Invoke(flow_begin))
.WillOnce(Invoke(flow_end))
.WillOnce(Invoke(NoMoreData()));
ASSERT_TRUE(
mock_tracing.PopulateDurationStats(expected_string_ref, &duration_stats, &queuing_stats));
ASSERT_FALSE(queuing_stats.empty());
ASSERT_EQ(size_t{1}, queuing_stats.size());
ASSERT_EQ(queuing_stats.begin()->first, flow_id);
ASSERT_EQ(queuing_stats.begin()->second.begin_ts_ns, begin_ts);
ASSERT_EQ(queuing_stats.begin()->second.end_ts_ns, end_ts);
ASSERT_EQ(queuing_stats.begin()->second.queuing_time_ns, end_ts - begin_ts);
ASSERT_EQ(queuing_stats.begin()->second.associated_thread, associated_thread);
}
} // anonymous namespace