blob: 14e2c2f46ac37b61a88f8ffed03f3b6a4ee79920 [file] [log] [blame]
/*
* Copyright (C) 2019 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 <array>
#include <atomic>
#include <chrono>
#include <deque>
#include <thread>
#include "perfetto/ext/base/metatrace.h"
#include "perfetto/ext/base/thread_annotations.h"
#include "src/base/test/test_task_runner.h"
#include "test/gtest_and_gmock.h"
namespace perfetto {
namespace {
namespace m = ::perfetto::metatrace;
using ::testing::Invoke;
class MetatraceTest : public ::testing::Test {
public:
void SetUp() override { m::Disable(); }
void TearDown() override {
task_runner_.RunUntilIdle();
m::Disable();
}
void Enable(uint32_t tags) {
m::Enable([this] { ReadCallback(); }, &task_runner_, tags);
}
MOCK_METHOD(void, ReadCallback, ());
base::TestTaskRunner task_runner_;
};
TEST_F(MetatraceTest, TagEnablingLogic) {
EXPECT_CALL(*this, ReadCallback()).Times(0);
for (int iteration = 0; iteration < 3; iteration++) {
ASSERT_EQ(m::RingBuffer::GetSizeForTesting(), 0u);
// No events should be traced before enabling.
m::TraceCounter(m::TAG_ANY, /*id=*/1, /*value=*/42);
{ m::ScopedEvent evt(m::TAG_ANY, /*id=*/1); }
ASSERT_EQ(m::RingBuffer::GetSizeForTesting(), 0u);
// Enable tags bit 1 (=2) and 2 (=4) and verify that only those events are
// added.
auto t_start = metatrace::TraceTimeNowNs();
Enable(/*tags=*/2 | 4);
m::TraceCounter(/*tag=*/1, /*id=*/42, /*value=*/10); // No.
m::TraceCounter(/*tag=*/2, /*id=*/42, /*value=*/11); // Yes.
m::TraceCounter(/*tag=*/4, /*id=*/42, /*value=*/12); // Yes.
m::TraceCounter(/*tag=*/1 | 2, /*id=*/42, /*value=*/13); // Yes.
m::TraceCounter(/*tag=*/1 | 4, /*id=*/42, /*value=*/14); // Yes.
m::TraceCounter(/*tag=*/2 | 4, /*id=*/42, /*value=*/15); // Yes.
m::TraceCounter(/*tag=*/4 | 8, /*id=*/42, /*value=*/16); // Yes.
m::TraceCounter(/*tag=*/1 | 8, /*id=*/42, /*value=*/17); // No.
m::TraceCounter(m::TAG_ANY, /*id=*/42, /*value=*/18); // Yes.
{ m::ScopedEvent evt(/*tag=*/1, /*id=*/20); } // No.
{ m::ScopedEvent evt(/*tag=*/8, /*id=*/21); } // No.
{ m::ScopedEvent evt(/*tag=*/2, /*id=*/22); } // Yes.
{ m::ScopedEvent evt(/*tag=*/4 | 8, /*id=*/23); } // Yes.
{ m::ScopedEvent evt(m::TAG_ANY, /*id=*/24); } // Yes.
{
auto it = m::RingBuffer::GetReadIterator();
ASSERT_TRUE(it);
ASSERT_EQ(it->counter_value, 11);
ASSERT_TRUE(++it);
ASSERT_EQ(it->counter_value, 12);
ASSERT_TRUE(++it);
ASSERT_EQ(it->counter_value, 13);
ASSERT_TRUE(++it);
ASSERT_EQ(it->counter_value, 14);
}
// Test that destroying and re-creating the iterator resumes reading from
// the right place.
{
auto it = m::RingBuffer::GetReadIterator();
ASSERT_TRUE(++it);
ASSERT_EQ(it->counter_value, 15);
ASSERT_TRUE(++it);
ASSERT_EQ(it->counter_value, 16);
ASSERT_TRUE(++it);
ASSERT_EQ(it->counter_value, 18);
ASSERT_TRUE(++it);
ASSERT_EQ(it->type_and_id, 22);
ASSERT_TRUE(++it);
ASSERT_EQ(it->type_and_id, 23);
ASSERT_TRUE(++it);
ASSERT_EQ(it->type_and_id, 24);
ASSERT_FALSE(++it);
}
// Test that we can write pids up to 32 bit TIDs (I observed up to 262144
// from /proc/sys/kernel/pid_max) and up to 2 days of timestamps.
{
auto* record = m::RingBuffer::AppendNewRecord();
record->counter_value = 42;
constexpr uint64_t kTwoDays = 48ULL * 3600 * 1000 * 1000 * 1000;
record->set_timestamp(t_start + kTwoDays);
record->thread_id = 0xbabaf00d;
record->type_and_id = m::Record::kTypeCounter;
auto it = m::RingBuffer::GetReadIterator();
ASSERT_TRUE(it);
ASSERT_EQ(it->timestamp_ns(), t_start + kTwoDays);
ASSERT_EQ(it->thread_id, 0xbabaf00d);
ASSERT_FALSE(++it);
}
m::Disable();
}
}
// Test that overruns are handled properly and that the writer re-synchronizes
// after the reader catches up.
TEST_F(MetatraceTest, HandleOverruns) {
int cnt = 0;
int exp_cnt = 0;
for (size_t iteration = 0; iteration < 3; iteration++) {
Enable(m::TAG_ANY);
std::string checkpoint_name = "ReadTask " + std::to_string(iteration);
auto checkpoint = task_runner_.CreateCheckpoint(checkpoint_name);
EXPECT_CALL(*this, ReadCallback()).WillOnce(Invoke(checkpoint));
for (size_t i = 0; i < m::RingBuffer::kCapacity; i++)
m::TraceCounter(/*tag=*/1, /*id=*/42, /*value=*/cnt++);
ASSERT_EQ(m::RingBuffer::GetSizeForTesting(), m::RingBuffer::kCapacity);
ASSERT_FALSE(m::RingBuffer::has_overruns());
for (int n = 0; n < 3; n++)
m::TraceCounter(/*tag=*/1, /*id=*/42, /*value=*/-1); // Will overrun.
ASSERT_TRUE(m::RingBuffer::has_overruns());
ASSERT_EQ(m::RingBuffer::GetSizeForTesting(), m::RingBuffer::kCapacity);
for (auto it = m::RingBuffer::GetReadIterator(); it; ++it)
ASSERT_EQ(it->counter_value, exp_cnt++);
ASSERT_EQ(m::RingBuffer::GetSizeForTesting(), 0u);
task_runner_.RunUntilCheckpoint(checkpoint_name);
m::Disable();
}
}
// Sets up a scenario where the writer writes constantly (however, guaranteeing
// to not overrun) and the reader catches up. Tests that all events are seen
// consistently without gaps.
TEST_F(MetatraceTest, InterleavedReadWrites) {
Enable(m::TAG_ANY);
constexpr int kMaxValue = m::RingBuffer::kCapacity * 3;
std::atomic<int> last_value_read{-1};
auto read_task = [&last_value_read] {
int last = last_value_read;
for (auto it = m::RingBuffer::GetReadIterator(); it; ++it) {
if (it->type_and_id.load(std::memory_order_acquire) == 0)
break;
// TSan doesn't know about the happens-before relationship between the
// type_and_id marker and the value being valid. Fixing this properly
// would require making all accesses to the metatrace object as
// std::atomic and read them with memory_order_relaxed, which is overkill.
PERFETTO_ANNOTATE_BENIGN_RACE_SIZED(&it->counter_value, sizeof(int), "")
int32_t counter_value = it->counter_value;
EXPECT_EQ(counter_value, last + 1);
last = counter_value;
}
// The read pointer is incremented only after destroying the iterator.
// Publish the last read value after the loop.
last_value_read = last;
};
EXPECT_CALL(*this, ReadCallback()).WillRepeatedly(Invoke(read_task));
// The writer will write continuously counters from 0 to kMaxValue.
auto writer_done = task_runner_.CreateCheckpoint("writer_done");
std::thread writer_thread([this, &writer_done, &last_value_read] {
for (int i = 0; i < kMaxValue; i++) {
m::TraceCounter(/*tag=*/1, /*id=*/1, i);
const int kCapacity = static_cast<int>(m::RingBuffer::kCapacity);
// Wait for the reader to avoid overruns.
// Using memory_order_relaxed because the QEMU arm emulator seems to incur
// in very high costs when dealing with full barriers, causing timeouts.
for (int sleep_us = 1;
i - last_value_read.load(std::memory_order_relaxed) >= kCapacity - 1;
sleep_us = std::min(sleep_us * 10, 1000)) {
std::this_thread::sleep_for(std::chrono::microseconds(sleep_us));
}
}
task_runner_.PostTask(writer_done);
});
task_runner_.RunUntilCheckpoint("writer_done");
writer_thread.join();
read_task(); // Do a final read pass.
EXPECT_FALSE(m::RingBuffer::has_overruns());
EXPECT_EQ(last_value_read, kMaxValue - 1);
}
// Try to hit potential thread races:
// - Test that the read callback is posted only once per cycle.
// - Test that the final size of the ring buffeer is sane.
// - Test that event records are consistent within each thread's event stream.
TEST_F(MetatraceTest, ThreadRaces) {
for (size_t iteration = 0; iteration < 10; iteration++) {
Enable(m::TAG_ANY);
std::string checkpoint_name = "ReadTask " + std::to_string(iteration);
auto checkpoint = task_runner_.CreateCheckpoint(checkpoint_name);
EXPECT_CALL(*this, ReadCallback()).WillOnce(Invoke(checkpoint));
auto thread_main = [](uint16_t thd_idx) {
for (size_t i = 0; i < m::RingBuffer::kCapacity + 500; i++)
m::TraceCounter(/*tag=*/1, thd_idx, static_cast<int>(i));
};
constexpr size_t kNumThreads = 8;
std::array<std::thread, kNumThreads> threads;
for (size_t thd_idx = 0; thd_idx < kNumThreads; thd_idx++)
threads[thd_idx] = std::thread(thread_main, thd_idx);
for (auto& t : threads)
t.join();
task_runner_.RunUntilCheckpoint(checkpoint_name);
ASSERT_EQ(m::RingBuffer::GetSizeForTesting(), m::RingBuffer::kCapacity);
std::array<int, kNumThreads> last_val{}; // Last value for each thread.
for (auto it = m::RingBuffer::GetReadIterator(); it; ++it) {
if (it->type_and_id.load(std::memory_order_acquire) == 0)
break;
using Record = m::Record;
ASSERT_EQ(it->type_and_id & Record::kTypeMask, Record::kTypeCounter);
auto thd_idx = static_cast<size_t>(it->type_and_id & ~Record::kTypeMask);
ASSERT_EQ(it->counter_value, last_val[thd_idx]);
last_val[thd_idx]++;
}
m::Disable();
}
}
} // namespace
} // namespace perfetto