| // Copyright 2021 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <lib/fit/defer.h> |
| #include <lib/ktrace/ktrace_internal.h> |
| #include <lib/unittest/unittest.h> |
| |
| #include <ktl/algorithm.h> |
| #include <ktl/limits.h> |
| #include <ktl/unique_ptr.h> |
| #include <vm/vm_aspace.h> |
| |
| #include <ktl/enforce.h> |
| |
| namespace ktrace_tests { |
| |
| // A test version of KTraceState which overrides the ReportStaticNames and |
| // ReportThreadProcessNames behaviors for testing purposes. |
| class TestKTraceState : public ::internal::KTraceState { |
| public: |
| using StartMode = internal::KTraceState::StartMode; |
| static constexpr uint32_t kDefaultBufferSize = 4096; |
| |
| // Figure out how many 32 byte records we should be able to fit into our |
| // default buffer size, minus the two metadata records we consume up front. Since we |
| // always allocate buffers in multiples of page size, we should be able to |
| // assert that this is an integral number of records. |
| static_assert(kDefaultBufferSize > (sizeof(ktrace_rec_32b_t) * 2)); |
| static_assert(((kDefaultBufferSize - (sizeof(ktrace_rec_32b_t) * 2)) % 32) == 0); |
| static constexpr uint32_t kMax32bRecords = |
| (kDefaultBufferSize - (sizeof(ktrace_rec_32b_t) * 2)) / 32; |
| |
| static bool InitStartTest() { |
| BEGIN_TEST; |
| |
| constexpr uint32_t kAllGroups = KTRACE_GRP_ALL; |
| |
| { |
| // Construct a ktrace state and initialize it, providing no group mask. |
| // No buffer should be allocated, and no calls should be made to any of |
| // the report hooks. The only thing which should stick during this |
| // operation is our target bufsize. |
| TestKTraceState state; |
| ASSERT_TRUE(state.Init(kDefaultBufferSize, 0)); |
| { |
| Guard<SpinLock, IrqSave> guard{&state.write_lock_}; |
| EXPECT_NULL(state.buffer_); |
| EXPECT_EQ(0u, state.bufsize_); |
| EXPECT_EQ(kDefaultBufferSize, state.target_bufsize_); |
| EXPECT_EQ(0u, state.static_name_report_count_); |
| EXPECT_EQ(0u, state.thread_name_report_count_); |
| EXPECT_EQ(0u, state.grpmask()); |
| } |
| |
| // Attempting to start with no groups specified is not allowed. We should |
| // get "INVALID_ARGS" back. |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, state.Start(0, StartMode::Saturate)); |
| |
| // Now go ahead and call start. This should cause the buffer to become |
| // allocated, and for both static and thread names to be reported (static |
| // before thread) |
| ASSERT_OK(state.Start(kAllGroups, StartMode::Saturate)); |
| { |
| Guard<SpinLock, IrqSave> guard{&state.write_lock_}; |
| EXPECT_NONNULL(state.buffer_); |
| EXPECT_GT(state.bufsize_, 0u); |
| EXPECT_LE(state.bufsize_, state.target_bufsize_); |
| EXPECT_EQ(kDefaultBufferSize, state.target_bufsize_); |
| EXPECT_EQ(1u, state.static_name_report_count_); |
| EXPECT_EQ(1u, state.thread_name_report_count_); |
| EXPECT_LE(state.last_static_name_report_time_, state.last_thread_name_report_time_); |
| EXPECT_EQ(KTRACE_GRP_TO_MASK(kAllGroups), state.grpmask()); |
| } |
| } |
| |
| { |
| // Perform a similar test, but this time passing a non-zero group mask to |
| // init. This should cause tracing to go live immediately. |
| TestKTraceState state; |
| ASSERT_TRUE(state.Init(kDefaultBufferSize, kAllGroups)); |
| |
| { |
| Guard<SpinLock, IrqSave> guard{&state.write_lock_}; |
| EXPECT_NONNULL(state.buffer_); |
| EXPECT_GT(state.bufsize_, 0u); |
| EXPECT_LE(state.bufsize_, state.target_bufsize_); |
| EXPECT_EQ(kDefaultBufferSize, state.target_bufsize_); |
| EXPECT_EQ(1u, state.static_name_report_count_); |
| EXPECT_EQ(1u, state.thread_name_report_count_); |
| EXPECT_LE(state.last_static_name_report_time_, state.last_thread_name_report_time_); |
| EXPECT_EQ(KTRACE_GRP_TO_MASK(kAllGroups), state.grpmask()); |
| } |
| } |
| |
| { |
| // Initialize a trace, then start it in circular mode. |
| TestKTraceState state; |
| ASSERT_TRUE(state.Init(kDefaultBufferSize, 0)); |
| ASSERT_OK(state.Start(kAllGroups, StartMode::Circular)); |
| |
| // Stopping and starting the trace again in circular mode should be OK. |
| ASSERT_OK(state.Stop()); |
| ASSERT_OK(state.Start(kAllGroups, StartMode::Circular)); |
| |
| // Stopping and starting the trace again in saturate mode should be an |
| // error. |
| ASSERT_OK(state.Stop()); |
| ASSERT_EQ(ZX_ERR_BAD_STATE, state.Start(kAllGroups, StartMode::Saturate)); |
| |
| // Rewinding the buffer should fix the issue. |
| // error. |
| ASSERT_OK(state.Rewind()); |
| ASSERT_OK(state.Start(kAllGroups, StartMode::Saturate)); |
| } |
| |
| END_TEST; |
| } |
| |
| static bool NamesTest() { |
| BEGIN_TEST; |
| // Create a small trace buffer and initialize it. |
| constexpr uint32_t kGroups = 0x3; |
| TestKTraceState state; |
| ASSERT_TRUE(state.Init(kDefaultBufferSize, kGroups)); |
| |
| // Immediately after initialization, ktrace will write two metadata records |
| // expressing the version of the trace buffer format, as well as the |
| // resolution of the timestamps in the trace. Make sure that the offset |
| // reflects this. |
| uint32_t expected_offset = sizeof(ktrace_rec_32b_t) * 2; |
| ASSERT_TRUE(state.CheckExpectedOffset(expected_offset)); |
| |
| constexpr struct NameTestVector { |
| uint32_t tag; |
| uint32_t id; |
| uint32_t arg; |
| bool always; |
| bool expected_present; |
| const char* name; |
| } kTestVectors[] = { |
| // clang-format off |
| { KTRACE_TAG(0x1, 0x1, 8), 0xbaad0000, 0xf00d0000, false, true, "Aria" }, |
| { KTRACE_TAG(0x2, 0x2, 16), 0xbaad0001, 0xf00d0001, false, true, "Andrew Adrian" }, |
| { KTRACE_TAG(0x3, 0x4, 24), 0xbaad0002, 0xf00d0002, false, false, "Aurora Angel Aaron" }, |
| { KTRACE_TAG(0x4, 0x8, 32), 0xbaad0003, 0xf00d0003, false, false, |
| "Axel Addison Austin Aubrey" }, |
| { KTRACE_TAG(0x5, 0x1, 40), 0xbaad0004, 0xf00d0004, true, true, |
| "Aaliyah Anna Alice Amir Allison Ariana" }, |
| { KTRACE_TAG(0x6, 0x1, 48), 0xbaad0005, 0xf00d0005, true, true, |
| "Autumn Ayden Ashton August Adeline Adriel Athena" }, |
| { KTRACE_TAG(0x7, 0x1, 56), 0xbaad0006, 0xf00d0006, true, true, |
| "Archer Adalynn Arthur Alex Alaia Arianna", }, |
| { KTRACE_TAG(0x8, 0x1, 64), 0xbaad0007, 0xf00d0007, true, true, |
| "Ayla Alexandra Alan Ariel Adalyn Amaya Ace Amara Abraham" }, |
| // clang-format on |
| }; |
| |
| // A small helper which computes the expected size of a name test vector. |
| auto ExpectedNameRecordSize = [](const NameTestVector& vec) -> uint32_t { |
| // Strings are limited to ZX_MAX_NAME_LEN characters, including their null terminator. |
| size_t string_storage = ktl::min(strlen(vec.name) + 1, ZX_MAX_NAME_LEN); |
| |
| // Total storage is the storage for the name header, plus the string |
| // storage, all rounded up to the nearest 8 bytes. |
| return (KTRACE_NAMESIZE + static_cast<uint32_t>(string_storage) + 7) & ~0x7; |
| }; |
| |
| // Add all of the name test vectors to the trace buffer. Verify that the |
| // buffer grows as we would expect while we do so. |
| uint32_t expected_present_count = 0; |
| for (const auto& vec : kTestVectors) { |
| ASSERT_TRUE(state.CheckExpectedOffset(expected_offset)); |
| |
| state.WriteNameEtc(vec.tag, vec.id, vec.arg, vec.name, vec.always); |
| if (vec.expected_present) { |
| expected_offset += ExpectedNameRecordSize(vec); |
| ++expected_present_count; |
| } |
| |
| ASSERT_TRUE(state.CheckExpectedOffset(expected_offset)); |
| } |
| |
| // Now, stop the trace, enumerate the buffer, and make sure that the records |
| // we expect are present. |
| uint32_t records_enumerated = 0; |
| uint32_t vec_id = 0; |
| auto checker = [&](const ktrace_header_t* hdr) -> bool { |
| BEGIN_TEST; |
| |
| ASSERT_NONNULL(hdr); |
| const ktrace_rec_name_t& rec = *(reinterpret_cast<const ktrace_rec_name_t*>(hdr)); |
| |
| // Skip any records which should not have made it into the trace buffer. |
| while ((vec_id < ktl::size(kTestVectors)) && !kTestVectors[vec_id].expected_present) { |
| ++vec_id; |
| } |
| |
| // We should still have a test vector to compare. |
| ASSERT_LT(vec_id, ktl::size(kTestVectors)); |
| const auto& vec = kTestVectors[vec_id]; |
| |
| // The individual fields of the tag should all match, except for the |
| // length, which should have been overwritten when the record was |
| // added. |
| EXPECT_EQ(ExpectedNameRecordSize(vec), KTRACE_LEN(rec.tag)); |
| EXPECT_EQ(KTRACE_GROUP(vec.tag), KTRACE_GROUP(rec.tag)); |
| EXPECT_EQ(KTRACE_EVENT(vec.tag), KTRACE_EVENT(rec.tag)); |
| EXPECT_EQ(KTRACE_FLAGS(vec.tag), KTRACE_FLAGS(rec.tag)); |
| |
| // ID and arg should have been directly copied into the record. |
| EXPECT_EQ(vec.id, rec.id); |
| EXPECT_EQ(vec.arg, rec.arg); |
| |
| // Name should match, up to the limit of ZX_MAX_NAME_LEN - 1, and |
| // the record should be null terminated. |
| const size_t expected_name_len = ktl::min(strlen(vec.name), ZX_MAX_NAME_LEN - 1); |
| ASSERT_EQ(expected_name_len, strlen(rec.name)); |
| EXPECT_BYTES_EQ(reinterpret_cast<const uint8_t*>(vec.name), |
| reinterpret_cast<const uint8_t*>(rec.name), expected_name_len); |
| EXPECT_EQ(0, rec.name[expected_name_len]); |
| |
| ++vec_id; |
| END_TEST; |
| }; |
| |
| ASSERT_OK(state.Stop()); |
| ASSERT_TRUE(state.TestAllRecords(records_enumerated, checker)); |
| EXPECT_EQ(expected_present_count, records_enumerated); |
| |
| END_TEST; |
| } |
| |
| static bool WriteRecordsTest() { |
| BEGIN_TEST; |
| |
| // Create a small trace buffer and initialize it. |
| constexpr uint32_t kGroups = 0x3; |
| TestKTraceState state; |
| ASSERT_TRUE(state.Init(kDefaultBufferSize, kGroups)); |
| |
| uint32_t expected_offset = sizeof(ktrace_rec_32b_t) * 2; |
| ASSERT_TRUE(state.CheckExpectedOffset(expected_offset)); |
| |
| // Exercise each of the supported forms of WriteRecord. There are 7 in total: |
| // 1) No payload |
| // 2) Payloads with 1-4 u32 arguments. |
| // 3) Payloads with 1-2 u64 arguments. |
| constexpr uint64_t kFirstTS = 0x1234567890abcdef; |
| constexpr uint32_t kBaseSz = sizeof(ktrace_header_t); |
| constexpr uint32_t kBaseU32 = 0xbaad0000; |
| constexpr uint64_t kBaseU64 = 0xbaadbaadf00d0000; |
| |
| auto MakeTag = [](uint32_t wr_ndx, uint32_t arg_cnt, uint32_t arg_sz) { |
| const uint32_t sz = (kBaseSz + (arg_cnt * arg_sz) + 0x7) & ~0x7; |
| return KTRACE_TAG(wr_ndx + 1, 1, sz); |
| }; |
| auto TS = [](uint32_t wr_ndx) { return kFirstTS + wr_ndx; }; |
| auto U32 = [](uint32_t wr_ndx, uint32_t arg) { return kBaseU32 + (wr_ndx * 16) + arg; }; |
| auto U64 = [](uint32_t wr_ndx, uint32_t arg) { return kBaseU64 + (wr_ndx * 16) + arg; }; |
| |
| state.WriteRecord(MakeTag(0, 0, 0), TS(0)); |
| state.WriteRecord(MakeTag(1, 1, 4), TS(1), U32(1, 0)); |
| state.WriteRecord(MakeTag(2, 2, 4), TS(2), U32(2, 0), U32(2, 1)); |
| state.WriteRecord(MakeTag(3, 3, 4), TS(3), U32(3, 0), U32(3, 1), U32(3, 2)); |
| state.WriteRecord(MakeTag(4, 4, 4), TS(4), U32(4, 0), U32(4, 1), U32(4, 2), U32(4, 3)); |
| state.WriteRecord(MakeTag(5, 1, 8), TS(5), U64(5, 0)); |
| state.WriteRecord(MakeTag(6, 2, 8), TS(6), U64(6, 0), U64(6, 1)); |
| |
| // Now, stop the trace and read the records back out and verify their contents. |
| constexpr struct { |
| uint32_t num_args; |
| uint32_t arg_size; |
| } kTestVectors[] = {{0, 0}, {1, 4}, {2, 4}, {3, 4}, {4, 4}, {1, 8}, {2, 8}}; |
| |
| uint32_t records_enumerated = 0; |
| uint32_t vec_id = 0; |
| auto checker = [&](const ktrace_header_t* hdr) -> bool { |
| BEGIN_TEST; |
| |
| ASSERT_NONNULL(hdr); |
| const ktrace_header_t& rec = *(reinterpret_cast<const ktrace_header_t*>(hdr)); |
| |
| ASSERT_LT(vec_id, ktl::size(kTestVectors)); |
| const auto& vec = kTestVectors[vec_id]; |
| |
| const uint32_t expected_size = static_cast<uint32_t>( |
| (sizeof(ktrace_header_t) + (vec.num_args * vec.arg_size) + 0x7) & ~0x7); |
| const uint32_t expected_tag = MakeTag(vec_id, vec.num_args, vec.arg_size); |
| |
| // Check the tag fields |
| EXPECT_EQ(expected_size, KTRACE_LEN(rec.tag)); |
| EXPECT_EQ(KTRACE_GROUP(expected_tag), KTRACE_GROUP(rec.tag)); |
| EXPECT_EQ(KTRACE_EVENT(expected_tag), KTRACE_EVENT(rec.tag)); |
| EXPECT_EQ(KTRACE_FLAGS(expected_tag), KTRACE_FLAGS(rec.tag)); |
| |
| // Check the timestamp |
| EXPECT_EQ(TS(vec_id), rec.ts); |
| |
| // Check the payload |
| switch (vec.arg_size) { |
| case 0: |
| break; |
| |
| case 4: { |
| auto payload = reinterpret_cast<const uint32_t*>(hdr + 1); |
| for (uint32_t i = 0; i < vec.num_args; ++i) { |
| ASSERT_EQ(U32(vec_id, i), payload[i]); |
| } |
| } break; |
| |
| case 8: { |
| auto payload = reinterpret_cast<const uint64_t*>(hdr + 1); |
| for (uint32_t i = 0; i < vec.num_args; ++i) { |
| ASSERT_EQ(U64(vec_id, i), payload[i]); |
| } |
| } break; |
| |
| default: |
| ASSERT_TRUE(false); |
| break; |
| } |
| |
| ++vec_id; |
| END_TEST; |
| }; |
| |
| ASSERT_OK(state.Stop()); |
| ASSERT_TRUE(state.TestAllRecords(records_enumerated, checker)); |
| EXPECT_EQ(7u, records_enumerated); |
| |
| END_TEST; |
| } |
| |
| static bool WriteTinyRecordsTest() { |
| BEGIN_TEST; |
| |
| // Create a small trace buffer and initialize it. |
| constexpr uint32_t kGroups = 0x3; |
| TestKTraceState state; |
| ASSERT_TRUE(state.Init(kDefaultBufferSize, kGroups)); |
| |
| uint32_t expected_offset = sizeof(ktrace_rec_32b_t) * 2; |
| ASSERT_TRUE(state.CheckExpectedOffset(expected_offset)); |
| |
| // Write a some tiny records |
| constexpr uint32_t kBaseArg = 0xbaad0000; |
| constexpr uint32_t kCnt = 15; |
| |
| auto MakeTag = [](uint32_t wr_ndx) { return KTRACE_TAG(wr_ndx + 1, 1, 0); }; |
| auto Arg = [](uint32_t wr_ndx) { return kBaseArg + wr_ndx; }; |
| uint64_t prev_ts = 0; |
| |
| for (uint32_t i = 0; i < kCnt; ++i) { |
| state.WriteRecordTiny(MakeTag(i), Arg(i)); |
| } |
| |
| // Stop and validate. |
| uint32_t records_enumerated = 0; |
| uint32_t vec_id = 0; |
| auto checker = [&](const ktrace_header_t* hdr) -> bool { |
| BEGIN_TEST; |
| |
| ASSERT_NONNULL(hdr); |
| const ktrace_header_t& rec = *(reinterpret_cast<const ktrace_header_t*>(hdr)); |
| |
| // Check the tag fields |
| const uint32_t expected_tag = MakeTag(vec_id); |
| EXPECT_EQ(16u, KTRACE_LEN(rec.tag)); |
| EXPECT_EQ(KTRACE_GROUP(expected_tag), KTRACE_GROUP(rec.tag)); |
| EXPECT_EQ(KTRACE_EVENT(expected_tag), KTRACE_EVENT(rec.tag)); |
| EXPECT_EQ(KTRACE_FLAGS(expected_tag), KTRACE_FLAGS(rec.tag)); |
| |
| // Check the timestamp. We are not allowed to provide an explicit |
| // timestamp, so the best we can do is make sure that they are |
| // monotonically increasing. |
| EXPECT_LE(prev_ts, rec.ts); |
| prev_ts = rec.ts; |
| |
| // Check the payload. Tiny records store their argument in the TID |
| // field. |
| EXPECT_LE(Arg(vec_id), rec.tid); |
| |
| ++vec_id; |
| END_TEST; |
| }; |
| |
| ASSERT_OK(state.Stop()); |
| ASSERT_TRUE(state.TestAllRecords(records_enumerated, checker)); |
| EXPECT_EQ(kCnt, records_enumerated); |
| |
| END_TEST; |
| } |
| |
| static bool SaturationTest() { |
| BEGIN_TEST; |
| |
| // Create a small trace buffer and initialize it. |
| constexpr uint32_t kGroups = KTRACE_GRP_PROBE; |
| TestKTraceState state; |
| ASSERT_TRUE(state.Init(kDefaultBufferSize, kGroups)); |
| |
| // Write the (max - 1) 32 byte records to the buffer, then write a single 24 byte record. |
| for (uint32_t i = 0; i < kMax32bRecords - 1; ++i) { |
| state.WriteRecord(TAG_PROBE_32(1), kRecordCurrentTimestamp, 5u, 6u, 7u, 8u); |
| } |
| state.WriteRecord(TAG_PROBE_24(1), kRecordCurrentTimestamp, 5u, 6u); |
| |
| // The buffer will not think that it is full just yet. |
| uint32_t rcnt = 0; |
| auto checker = [&](const ktrace_header_t* hdr) -> bool { return true; }; |
| EXPECT_EQ(KTRACE_GRP_TO_MASK(kGroups), state.grpmask()); |
| ASSERT_OK(state.Stop()); |
| EXPECT_TRUE(state.TestAllRecords(rcnt, checker)); |
| EXPECT_TRUE(state.CheckExpectedOffset(kDefaultBufferSize - 8)); |
| EXPECT_EQ(kMax32bRecords, rcnt); |
| |
| // Now write one more record, this time with a different payload. |
| ASSERT_OK(state.Start(kGroups, internal::KTraceState::StartMode::Saturate)); |
| state.WriteRecord(TAG_PROBE_32(1), kRecordCurrentTimestamp, 0xbaadf00d, 0xf00dbaad, ~0xbaadf00d, |
| ~0xf00dbaad); |
| |
| // The buffer should now think that it is full (the group mask will be |
| // cleared), and all of the original records should be present (but not the |
| // new one). |
| EXPECT_EQ(0u, state.grpmask()); |
| ASSERT_OK(state.Stop()); |
| |
| auto payload_checker = [&](const ktrace_header_t* hdr) -> bool { |
| BEGIN_TEST; |
| ASSERT_NONNULL(hdr); |
| |
| auto payload = reinterpret_cast<const uint32_t*>(hdr + 1); |
| const uint32_t len = KTRACE_LEN(hdr->tag); |
| switch (len) { |
| case 32: |
| EXPECT_EQ(7u, payload[2]); |
| EXPECT_EQ(8u, payload[3]); |
| __FALLTHROUGH; |
| case 24: |
| EXPECT_EQ(5u, payload[0]); |
| EXPECT_EQ(6u, payload[1]); |
| break; |
| default: |
| EXPECT_EQ(32u, len); |
| break; |
| } |
| |
| END_TEST; |
| }; |
| EXPECT_TRUE(state.TestAllRecords(rcnt, payload_checker)); |
| EXPECT_EQ(kMax32bRecords, rcnt); |
| |
| END_TEST; |
| } |
| |
| static bool RewindTest() { |
| BEGIN_TEST; |
| |
| // Create a small trace buffer and initialize it. |
| constexpr uint32_t kGroups = KTRACE_GRP_PROBE; |
| TestKTraceState state; |
| ASSERT_TRUE(state.Init(kDefaultBufferSize, kGroups)); |
| |
| uint32_t expected_offset = sizeof(ktrace_rec_32b_t) * 2; |
| ASSERT_TRUE(state.CheckExpectedOffset(expected_offset)); |
| |
| // Write a couple of records. |
| state.WriteRecord(TAG_PROBE_32(1), kRecordCurrentTimestamp, 5u, 6u); |
| state.WriteRecord(TAG_PROBE_32(1), kRecordCurrentTimestamp, 5u, 6u); |
| |
| // The offset should have moved, and the number of records in the buffer should now be 2. |
| uint32_t rcnt = 0; |
| auto checker = [&](const ktrace_header_t* hdr) -> bool { return true; }; |
| EXPECT_TRUE(state.CheckExpectedOffset(expected_offset, CheckOp::LT)); |
| EXPECT_EQ(KTRACE_GRP_TO_MASK(kGroups), state.grpmask()); |
| ASSERT_OK(state.Stop()); |
| EXPECT_TRUE(state.TestAllRecords(rcnt, checker)); |
| EXPECT_EQ(2u, rcnt); |
| |
| // Rewind. The offset should return to the beginning, and there |
| // should be no records in the buffer. |
| ASSERT_OK(state.Rewind()); |
| EXPECT_TRUE(state.CheckExpectedOffset(expected_offset)); |
| EXPECT_EQ(0u, state.grpmask()); |
| EXPECT_TRUE(state.TestAllRecords(rcnt, checker)); |
| EXPECT_EQ(0u, rcnt); |
| |
| // Start again, and this time saturate the buffer |
| ASSERT_OK(state.Start(kGroups, StartMode::Saturate)); |
| for (uint32_t i = 0; i < kMax32bRecords + 10; ++i) { |
| state.WriteRecord(TAG_PROBE_32(1), kRecordCurrentTimestamp, 5u, 6u, 7u, 8u); |
| } |
| EXPECT_EQ(0u, state.grpmask()); |
| ASSERT_OK(state.Stop()); |
| EXPECT_TRUE(state.TestAllRecords(rcnt, checker)); |
| EXPECT_EQ(kMax32bRecords, rcnt); |
| |
| // Finally, rewind again. The offset should return to the |
| // beginning, and there should be no records in the buffer. |
| ASSERT_OK(state.Rewind()); |
| EXPECT_TRUE(state.CheckExpectedOffset(expected_offset)); |
| EXPECT_EQ(0u, state.grpmask()); |
| EXPECT_TRUE(state.TestAllRecords(rcnt, checker)); |
| EXPECT_EQ(0u, rcnt); |
| |
| END_TEST; |
| } |
| |
| static bool StateCheckTest() { |
| BEGIN_TEST; |
| |
| constexpr uint32_t kAllGroups = KTRACE_GRP_ALL; |
| constexpr uint32_t kSomeGroups = 0x3; |
| |
| { |
| TestKTraceState state; |
| ASSERT_TRUE(state.Init(kDefaultBufferSize, 0)); |
| |
| // We didn't provide a non-zero initial set of groups, so the trace should |
| // not be started right now. Stopping, rewinding, and reading are all |
| // legal (although, stopping does nothing). We have not allocated our |
| // buffer yet, so not even the static metadata should be available to |
| // read. |
| ASSERT_OK(state.Stop()); |
| ASSERT_EQ(0u, state.ReadUser(nullptr, 0, 0)); |
| ASSERT_OK(state.Rewind()); |
| ASSERT_EQ(KTRACE_GRP_TO_MASK(0u), state.grpmask()); |
| |
| // Starting should succeed. |
| ASSERT_OK(state.Start(kAllGroups, StartMode::Saturate)); |
| ASSERT_EQ(KTRACE_GRP_TO_MASK(kAllGroups), state.grpmask()); |
| |
| // Now that we are started, rewinding or should fail because of the state |
| // check. |
| ASSERT_EQ(ZX_ERR_BAD_STATE, state.Rewind()); |
| ASSERT_EQ(ZX_ERR_BAD_STATE, state.ReadUser(nullptr, 0, 0)); |
| ASSERT_EQ(KTRACE_GRP_TO_MASK(kAllGroups), state.grpmask()); |
| |
| // Starting while already started should succeed, but change the active |
| // group mask. |
| ASSERT_OK(state.Start(kSomeGroups, StartMode::Saturate)); |
| ASSERT_EQ(KTRACE_GRP_TO_MASK(kSomeGroups), state.grpmask()); |
| |
| // Stopping is still OK, and actually does something now (it clears the |
| // group mask). |
| ASSERT_OK(state.Stop()); |
| ASSERT_EQ(KTRACE_GRP_TO_MASK(0u), state.grpmask()); |
| |
| // Now that we are stopped, we can read, rewind, and stop again. Since we |
| // have started before, we expect that the amount of data available to |
| // read should be equal to the size of the two static metadata records. |
| const ssize_t expected_size = sizeof(ktrace_rec_32b_t) * 2; |
| ASSERT_EQ(expected_size, state.ReadUser(nullptr, 0, 0)); |
| ASSERT_OK(state.Rewind()); |
| } |
| |
| { |
| // Same checks as before, but this time start in the started state after |
| // init by providing a non-zero set of groups. |
| TestKTraceState state; |
| ASSERT_TRUE(state.Init(kDefaultBufferSize, kAllGroups)); |
| ASSERT_EQ(KTRACE_GRP_TO_MASK(kAllGroups), state.grpmask()); |
| |
| // We are started, so rewinding or reading should fail because of the |
| // state check. |
| ASSERT_EQ(ZX_ERR_BAD_STATE, state.Rewind()); |
| ASSERT_EQ(ZX_ERR_BAD_STATE, state.ReadUser(nullptr, 0, 0)); |
| ASSERT_EQ(KTRACE_GRP_TO_MASK(kAllGroups), state.grpmask()); |
| |
| // "Restarting" should change the active group mask. |
| ASSERT_OK(state.Start(kSomeGroups, StartMode::Saturate)); |
| ASSERT_EQ(KTRACE_GRP_TO_MASK(kSomeGroups), state.grpmask()); |
| |
| // Stopping should work. |
| ASSERT_OK(state.Stop()); |
| ASSERT_EQ(KTRACE_GRP_TO_MASK(0u), state.grpmask()); |
| |
| // Stopping again, rewinding, and reading are all OK now. |
| const ssize_t expected_size = sizeof(ktrace_rec_32b_t) * 2; |
| ASSERT_OK(state.Stop()); |
| ASSERT_EQ(expected_size, state.ReadUser(nullptr, 0, 0)); |
| ASSERT_OK(state.Rewind()); |
| ASSERT_OK(state.Stop()); |
| } |
| |
| END_TEST; |
| } |
| |
| static bool CircularWriteTest() { |
| BEGIN_TEST; |
| |
| enum class Padding { Needed, NotNeeded }; |
| constexpr uint32_t kAllGroups = KTRACE_GRP_ALL; |
| constexpr ktl::array kPasses = {Padding::Needed, Padding::NotNeeded}; |
| |
| // Define a couple of lambdas and small amount of state which we will be |
| // using to verify the contents of the trace buffer after writing different |
| // patterns to it. |
| // |
| // The test will be conducted in two passes; one pass where we fill up our |
| // buffer perfectly when we wrap, and another where we do not (and need a |
| // padding record). In both passes, we start by writing two records while |
| // we are in "saturation" mode. These records are part of the "static" |
| // region of the trace and should always be present. These static records |
| // also have distinct patterns that we can look for when verifying the |
| // contents of the trace. |
| // |
| // After writing these records and verifying the contents of the buffer, we |
| // restart in circular mode and write just enough in the way of records to |
| // fill the trace buffer, but not wrap it. These new "circular" records |
| // contain distinct payloads based on the order in which they were written |
| // and can be used for verification. In addition, we expect a padding |
| // record to be present during the padding pass, but _only_ once and _only_ |
| // after we have forced the buffer to wrap. |
| uint32_t record_count = 0; |
| uint32_t expected_first_circular = 0; |
| uint32_t enumerated_records = 0; |
| bool saw_padding = false; |
| |
| auto ResetPayloadCheckerState = [&](uint32_t first_circular) { |
| record_count = 0; |
| expected_first_circular = first_circular; |
| enumerated_records = 0; |
| saw_padding = false; |
| }; |
| |
| for (const Padding pass : kPasses) { |
| auto payload_checker = [&](const ktrace_header_t* hdr) { |
| BEGIN_TEST; |
| |
| ASSERT_NONNULL(hdr); |
| auto payload = reinterpret_cast<const uint32_t*>(hdr + 1); |
| const uint32_t len = KTRACE_LEN(hdr->tag); |
| |
| if (record_count == 0) { |
| // Record number #0 should always be present, always be 32 bytes |
| // long, and always have the 0xAAAAAAAA, 0, 0, 0 payload. |
| ASSERT_EQ(32u, len); |
| EXPECT_EQ(0xaaaaaaaa, payload[0]); |
| EXPECT_EQ(0u, payload[1]); |
| EXPECT_EQ(0u, payload[2]); |
| EXPECT_EQ(0u, payload[3]); |
| } else if (record_count == 1) { |
| // Record number #1 should always be present, and will have a length |
| // of 24 or 32 bytes, and a 0xbbbbbbbb or 0xcccccccc payload, |
| // depending on whether or not this pass of the test is one where we |
| // expect to need a padding record or not. |
| // long, and always have the 0xAAAAAAAA, 0, 0, 0 payload. |
| if (pass == Padding::Needed) { |
| ASSERT_EQ(24u, len); |
| EXPECT_EQ(0xbbbbbbbb, payload[0]); |
| EXPECT_EQ(0u, payload[1]); |
| } else { |
| ASSERT_EQ(32u, len); |
| EXPECT_EQ(0xcccccccc, payload[0]); |
| EXPECT_EQ(0u, payload[1]); |
| EXPECT_EQ(0u, payload[2]); |
| EXPECT_EQ(0u, payload[3]); |
| } |
| } else { |
| // All subsequent records should either be a padding record, or a 32 |
| // byte record whose payload values are a function of their index. |
| if (KTRACE_GROUP(hdr->tag) != 0) { |
| // A non-zero group indicates a normal record. |
| const uint32_t ndx = record_count + expected_first_circular - 2; |
| ASSERT_EQ(32u, len); |
| EXPECT_EQ(ndx + 0, payload[0]); |
| EXPECT_EQ(ndx + 1, payload[1]); |
| EXPECT_EQ(ndx + 2, payload[2]); |
| EXPECT_EQ(ndx + 3, payload[3]); |
| } else { |
| // A group of 0 indicates a padding record. |
| if (pass == Padding::Needed) { |
| // should only ever see (at most) a single padding record per check. |
| ASSERT_FALSE(saw_padding); |
| saw_padding = true; |
| } else { |
| ASSERT_TRUE(false); // we should not be seeing padding on a non-padding pass. |
| } |
| |
| // Don't count padding records in the record count. |
| --record_count; |
| } |
| } |
| |
| ++record_count; |
| END_TEST; |
| }; |
| |
| // Allocate our trace buffer and auto-start it during init in non-circular |
| // mode. |
| TestKTraceState state; |
| ASSERT_TRUE(state.Init(kDefaultBufferSize, kAllGroups)); |
| |
| // In order to run this test, we need enough space in our buffer after the |
| // 2 reserved metadata records for a least two "static" records, and a |
| // small number of extra records. |
| constexpr uint32_t kOverhead = sizeof(ktrace_rec_32b_t) * 2; |
| constexpr uint32_t kExtraRecords = 5; |
| const uint32_t kStaticOverhead = 32 + ((pass == Padding::Needed) ? 24 : 32); |
| ASSERT_GE(kDefaultBufferSize, (kOverhead + kStaticOverhead + (32 * kExtraRecords))); |
| |
| state.WriteRecord(TAG_PROBE_32(1), kRecordCurrentTimestamp, 0xaaaaaaaa, 0u, 0u, 0u); |
| if (pass == Padding::Needed) { |
| ASSERT_NE(0u, (kDefaultBufferSize - (kOverhead + kStaticOverhead)) % 32); |
| state.WriteRecord(TAG_PROBE_24(1), kRecordCurrentTimestamp, 0xbbbbbbbb, 0u); |
| } else { |
| ASSERT_EQ(0u, (kDefaultBufferSize - (kOverhead + kStaticOverhead)) % 32); |
| state.WriteRecord(TAG_PROBE_32(1), kRecordCurrentTimestamp, 0xcccccccc, 0u, 0u, 0u); |
| } |
| ASSERT_TRUE(state.CheckExpectedOffset(kOverhead + kStaticOverhead)); |
| |
| // Stop the trace and verify that we have the records we expect. Right |
| // now, we expect to find only a pair of static records after the |
| // metadata. |
| const uint32_t kMaxCircular32bRecords = |
| (kDefaultBufferSize - (kOverhead + kStaticOverhead)) / 32u; |
| ASSERT_OK(state.Stop()); |
| ResetPayloadCheckerState(0); |
| EXPECT_TRUE(state.TestAllRecords(enumerated_records, payload_checker)); |
| EXPECT_EQ(2u, enumerated_records); |
| EXPECT_FALSE(saw_padding); |
| |
| // OK, now restart in circular mode, and write the maximum number of 32 |
| // byte records we can, without causing a wrap. |
| ASSERT_OK(state.Start(kAllGroups, StartMode::Circular)); |
| for (uint32_t i = 0; i < kMaxCircular32bRecords; ++i) { |
| const uint32_t ndx = i; |
| state.WriteRecord(TAG_PROBE_32(1), kRecordCurrentTimestamp, ndx + 0, ndx + 1, ndx + 2, |
| ndx + 3); |
| } |
| |
| // Stop, and check the contents. |
| ASSERT_OK(state.Stop()); |
| ResetPayloadCheckerState(0); |
| EXPECT_TRUE(state.TestAllRecords(enumerated_records, payload_checker)); |
| EXPECT_EQ(2u + kMaxCircular32bRecords, enumerated_records); |
| EXPECT_FALSE(saw_padding); |
| |
| // Start one last time, writing our extra records. This should cause the |
| // circular section of the ktrace buffer to wrap, requiring a padding |
| // record if (and only if) this is the padding pass. Our first "circular" |
| // record should start with a payload index equal to the number of extra |
| // records we wrote. |
| ASSERT_OK(state.Start(kAllGroups, StartMode::Circular)); |
| for (uint32_t i = 0; i < kExtraRecords; ++i) { |
| const uint32_t ndx = i + kMaxCircular32bRecords; |
| state.WriteRecord(TAG_PROBE_32(1), kRecordCurrentTimestamp, ndx + 0, ndx + 1, ndx + 2, |
| ndx + 3); |
| } |
| |
| // Stop, and check the contents. |
| ASSERT_OK(state.Stop()); |
| ResetPayloadCheckerState(kExtraRecords); |
| EXPECT_TRUE(state.TestAllRecords(enumerated_records, payload_checker)); |
| if (pass == Padding::Needed) { |
| EXPECT_EQ(2u + kMaxCircular32bRecords + 1u, enumerated_records); |
| EXPECT_TRUE(saw_padding); |
| } else { |
| EXPECT_EQ(2u + kMaxCircular32bRecords, enumerated_records); |
| EXPECT_FALSE(saw_padding); |
| } |
| } |
| |
| END_TEST; |
| } |
| |
| private: |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // Actual class implementation starts here. All members are private, the only |
| // public members of the class are the static test hooks. |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| // clang-format off |
| enum class CheckOp { LT, LE, EQ, GT, GE }; |
| // clang-format on |
| |
| TestKTraceState() { |
| // disable diagnostic printfs in the test instances of KTrace we create. |
| disable_diags_printfs_ = true; |
| } |
| |
| // TODO(johngro): The default KTraceState implementation never cleans up its |
| // buffer allocation, as it assumes that it is being used as a global |
| // singleton, and that the kernel will never "exit". Test instances of |
| // KTraceState *must* clean themselves up, however. Should we push this |
| // behavior down one level into the default KTraceState implementation's |
| // destructor, even though it (currently) does not ever have any reason to |
| // destruct? |
| ~TestKTraceState() { |
| if (buffer_ != nullptr) { |
| VmAspace* aspace = VmAspace::kernel_aspace(); |
| aspace->FreeRegion(reinterpret_cast<vaddr_t>(buffer_)); |
| } |
| } |
| |
| // We interpose ourselves in the Init path so that we can allocate the side |
| // buffer we will use for validation. |
| [[nodiscard]] bool Init(uint32_t target_bufsize, uint32_t initial_groups) { |
| BEGIN_TEST; |
| |
| // Tests should always be allocating in units of page size. |
| ASSERT_EQ(0u, target_bufsize & (PAGE_SIZE - 1)); |
| // Double init is not allowed. |
| ASSERT_NULL(validation_buffer_.get()); |
| |
| fbl::AllocChecker ac; |
| validation_buffer_.reset(new (&ac) uint8_t[target_bufsize]); |
| ASSERT_TRUE(ac.check()); |
| validation_buffer_size_ = target_bufsize; |
| |
| KTraceState::Init(target_bufsize, initial_groups); |
| |
| // Make sure that the buffer size we requested was allocated exactly. |
| { |
| Guard<SpinLock, IrqSave> guard{&write_lock_}; |
| ASSERT_GE(target_bufsize, bufsize_); |
| } |
| |
| END_TEST; |
| } |
| |
| void ReportStaticNames() override { |
| last_static_name_report_time_ = current_time(); |
| ++static_name_report_count_; |
| } |
| |
| void ReportThreadProcessNames() override { |
| last_thread_name_report_time_ = current_time(); |
| ++thread_name_report_count_; |
| } |
| |
| zx_status_t CopyToUser(void* dst, const void* src, size_t len) override { |
| ::memcpy(dst, src, len); |
| return ZX_OK; |
| } |
| |
| // Check to make sure that the buffer is not operating in circular mode, and |
| // that the write pointer is at the offset we expect. |
| [[nodiscard]] bool CheckExpectedOffset(size_t expected, CheckOp op = CheckOp::EQ) |
| TA_EXCL(write_lock_) { |
| BEGIN_TEST; |
| |
| Guard<SpinLock, IrqSave> guard{&write_lock_}; |
| switch (op) { |
| // clang-format off |
| case CheckOp::LT: EXPECT_LT(expected, wr_); break; |
| case CheckOp::LE: EXPECT_LE(expected, wr_); break; |
| case CheckOp::EQ: EXPECT_EQ(expected, wr_); break; |
| case CheckOp::GT: EXPECT_GT(expected, wr_); break; |
| case CheckOp::GE: EXPECT_GE(expected, wr_); break; |
| // clang-format on |
| default: |
| ASSERT_TRUE(false); |
| } |
| EXPECT_EQ(0u, rd_); |
| EXPECT_EQ(0u, circular_size_); |
| |
| END_TEST; |
| } |
| |
| template <typename Checker> |
| bool TestAllRecords(uint32_t& records_enumerated_out, const Checker& do_check) |
| TA_EXCL(write_lock_) { |
| BEGIN_TEST; |
| |
| // Make sure that we give a value to all of our out parameters. |
| records_enumerated_out = 0; |
| |
| // Make sure that Read reports a reasonable size needed to read the buffer. |
| ssize_t available = ReadUser(nullptr, 0, 0); |
| ASSERT_GE(available, 0); |
| ASSERT_LE(static_cast<size_t>(available), validation_buffer_size_); |
| |
| // Now actually read the data, make sure that we read the same amount that |
| // the size operation told us we would need to read. |
| ASSERT_NONNULL(validation_buffer_.get()); |
| size_t to_validate = ReadUser(validation_buffer_.get(), 0, validation_buffer_size_); |
| ASSERT_EQ(static_cast<size_t>(available), to_validate); |
| |
| uint32_t rd_offset = sizeof(ktrace_rec_32b_t) * 2; |
| ASSERT_GE(to_validate, rd_offset); |
| |
| // We expect all trace buffers to start with a metadata records indicating the |
| // version of the trace buffer format, and the clock resolution. Verify that |
| // these are present. |
| const uint8_t* buffer = validation_buffer_.get(); |
| const ktrace_rec_32b_t& version_rec = reinterpret_cast<const ktrace_rec_32b_t*>(buffer)[0]; |
| const ktrace_rec_32b_t& clock_res_rec = reinterpret_cast<const ktrace_rec_32b_t*>(buffer)[1]; |
| |
| EXPECT_EQ(TAG_VERSION, version_rec.tag); |
| EXPECT_EQ(KTRACE_VERSION, version_rec.a); |
| EXPECT_EQ(sizeof(ktrace_rec_32b_t), KTRACE_LEN(version_rec.tag)); |
| |
| const uint64_t clock_res = ticks_per_second() / 1000; |
| EXPECT_EQ(TAG_TICKS_PER_MS, clock_res_rec.tag); |
| EXPECT_EQ(static_cast<uint32_t>(clock_res), clock_res_rec.a); |
| EXPECT_EQ(static_cast<uint32_t>(clock_res >> 32), clock_res_rec.b); |
| EXPECT_EQ(sizeof(ktrace_rec_32b_t), KTRACE_LEN(clock_res_rec.tag)); |
| |
| // If something goes wrong while testing records, report _which_ record has |
| // trouble, to assist with debugging. |
| auto ReportRecord = fit::defer( |
| [&]() { printf("\nFAILED while enumerating record (%u)\n", records_enumerated_out); }); |
| |
| while (rd_offset < to_validate) { |
| auto hdr = reinterpret_cast<const ktrace_header_t*>(buffer + rd_offset); |
| // Zero length records are not legal. |
| ASSERT_GT(KTRACE_LEN(hdr->tag), 0u); |
| |
| // Make sure the record matches expectations. |
| ASSERT_TRUE(do_check(hdr)); |
| |
| // Advance to the next record. |
| ++records_enumerated_out; |
| rd_offset += KTRACE_LEN(hdr->tag); |
| } |
| |
| EXPECT_EQ(rd_offset, to_validate); |
| |
| ReportRecord.cancel(); |
| END_TEST; |
| } |
| |
| zx_time_t last_static_name_report_time_{0}; |
| zx_time_t last_thread_name_report_time_{0}; |
| uint32_t static_name_report_count_{0}; |
| uint32_t thread_name_report_count_{0}; |
| |
| ktl::unique_ptr<uint8_t[]> validation_buffer_; |
| size_t validation_buffer_size_{0}; |
| }; |
| |
| } // namespace ktrace_tests |
| |
| UNITTEST_START_TESTCASE(ktrace_tests) |
| UNITTEST("init/start", ktrace_tests::TestKTraceState::InitStartTest) |
| UNITTEST("names", ktrace_tests::TestKTraceState::NamesTest) |
| UNITTEST("write record", ktrace_tests::TestKTraceState::WriteRecordsTest) |
| UNITTEST("write tiny record", ktrace_tests::TestKTraceState::WriteTinyRecordsTest) |
| UNITTEST("saturation", ktrace_tests::TestKTraceState::SaturationTest) |
| UNITTEST("rewind", ktrace_tests::TestKTraceState::RewindTest) |
| UNITTEST("state check", ktrace_tests::TestKTraceState::StateCheckTest) |
| UNITTEST("circular", ktrace_tests::TestKTraceState::CircularWriteTest) |
| UNITTEST_END_TESTCASE(ktrace_tests, "ktrace", "KTrace tests") |