blob: 791e21abac7972f8a0757ee3522a0f89b6c29381 [file] [log] [blame]
/*
* Copyright (C) 2018 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 "perfetto/trace_processor/trace_blob_view.h"
#include "src/trace_processor/importers/proto/proto_trace_parser.h"
#include <map>
#include <random>
#include <vector>
#include "perfetto/trace_processor/basic_types.h"
#include "perfetto/trace_processor/trace_blob.h"
#include "src/trace_processor/importers/common/parser_types.h"
#include "src/trace_processor/importers/proto/packet_sequence_state.h"
#include "src/trace_processor/sorter/trace_sorter.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "test/gtest_and_gmock.h"
namespace perfetto {
namespace trace_processor {
namespace {
using ::testing::_;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::MockFunction;
using ::testing::NiceMock;
class MockTraceParser : public ProtoTraceParser {
public:
MockTraceParser(TraceProcessorContext* context) : ProtoTraceParser(context) {}
MOCK_METHOD(
void,
MOCK_ParseFtracePacket,
(uint32_t cpu, int64_t timestamp, const uint8_t* data, size_t length));
void ParseFtraceEvent(uint32_t cpu,
int64_t timestamp,
TracePacketData data) override {
MOCK_ParseFtracePacket(cpu, timestamp, data.packet.data(),
data.packet.length());
}
MOCK_METHOD(void,
MOCK_ParseTracePacket,
(int64_t ts, const uint8_t* data, size_t length));
void ParseTrackEvent(int64_t, TrackEventData) override {}
void ParseTracePacket(int64_t ts, TracePacketData data) override {
TraceBlobView& tbv = data.packet;
MOCK_ParseTracePacket(ts, tbv.data(), tbv.length());
}
};
class MockTraceStorage : public TraceStorage {
public:
MockTraceStorage() : TraceStorage() {}
MOCK_METHOD(StringId, InternString, (base::StringView view), (override));
};
class TraceSorterTest : public ::testing::Test {
public:
TraceSorterTest() : test_buffer_(TraceBlob::Allocate(8)) {
storage_ = new NiceMock<MockTraceStorage>();
context_.storage.reset(storage_);
CreateSorter();
}
void CreateSorter(bool full_sort = true) {
std::unique_ptr<MockTraceParser> parser(new MockTraceParser(&context_));
parser_ = parser.get();
auto sorting_mode = full_sort ? TraceSorter::SortingMode::kFullSort
: TraceSorter::SortingMode::kDefault;
context_.sorter.reset(
new TraceSorter(&context_, std::move(parser), sorting_mode));
}
protected:
TraceProcessorContext context_;
MockTraceParser* parser_;
NiceMock<MockTraceStorage>* storage_;
TraceBlobView test_buffer_;
};
TEST_F(TraceSorterTest, TestFtrace) {
PacketSequenceState state(&context_);
TraceBlobView view = test_buffer_.slice_off(0, 1);
EXPECT_CALL(*parser_, MOCK_ParseFtracePacket(0, 1000, view.data(), 1));
context_.sorter->PushFtraceEvent(0 /*cpu*/, 1000 /*timestamp*/,
std::move(view), state.current_generation());
context_.sorter->ExtractEventsForced();
}
TEST_F(TraceSorterTest, TestTracePacket) {
PacketSequenceState state(&context_);
TraceBlobView view = test_buffer_.slice_off(0, 1);
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1000, view.data(), 1));
context_.sorter->PushTracePacket(1000, state.current_generation(),
std::move(view));
context_.sorter->ExtractEventsForced();
}
TEST_F(TraceSorterTest, Ordering) {
PacketSequenceState state(&context_);
TraceBlobView view_1 = test_buffer_.slice_off(0, 1);
TraceBlobView view_2 = test_buffer_.slice_off(0, 2);
TraceBlobView view_3 = test_buffer_.slice_off(0, 3);
TraceBlobView view_4 = test_buffer_.slice_off(0, 4);
InSequence s;
EXPECT_CALL(*parser_, MOCK_ParseFtracePacket(0, 1000, view_1.data(), 1));
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1001, view_2.data(), 2));
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1100, view_3.data(), 3));
EXPECT_CALL(*parser_, MOCK_ParseFtracePacket(2, 1200, view_4.data(), 4));
context_.sorter->PushFtraceEvent(2 /*cpu*/, 1200 /*timestamp*/,
std::move(view_4),
state.current_generation());
context_.sorter->PushTracePacket(1001, state.current_generation(),
std::move(view_2));
context_.sorter->PushTracePacket(1100, state.current_generation(),
std::move(view_3));
context_.sorter->PushFtraceEvent(0 /*cpu*/, 1000 /*timestamp*/,
std::move(view_1),
state.current_generation());
context_.sorter->ExtractEventsForced();
}
TEST_F(TraceSorterTest, IncrementalExtraction) {
CreateSorter(false);
PacketSequenceState state(&context_);
TraceBlobView view_1 = test_buffer_.slice_off(0, 1);
TraceBlobView view_2 = test_buffer_.slice_off(0, 2);
TraceBlobView view_3 = test_buffer_.slice_off(0, 3);
TraceBlobView view_4 = test_buffer_.slice_off(0, 4);
TraceBlobView view_5 = test_buffer_.slice_off(0, 5);
// Flush at the start of packet sequence to match behavior of the
// service.
context_.sorter->NotifyFlushEvent();
context_.sorter->PushTracePacket(1200, state.current_generation(),
std::move(view_2));
context_.sorter->PushTracePacket(1100, state.current_generation(),
std::move(view_1));
// No data should be exttracted at this point because we haven't
// seen two flushes yet.
context_.sorter->NotifyReadBufferEvent();
// Now that we've seen two flushes, we should be ready to start extracting
// data on the next OnReadBufer call (after two flushes as usual).
context_.sorter->NotifyFlushEvent();
context_.sorter->NotifyReadBufferEvent();
context_.sorter->NotifyFlushEvent();
context_.sorter->NotifyFlushEvent();
context_.sorter->PushTracePacket(1400, state.current_generation(),
std::move(view_4));
context_.sorter->PushTracePacket(1300, state.current_generation(),
std::move(view_3));
// This ReadBuffer call should finally extract until the first OnReadBuffer
// call.
{
InSequence s;
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1100, test_buffer_.data(), 1));
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1200, test_buffer_.data(), 2));
}
context_.sorter->NotifyReadBufferEvent();
context_.sorter->NotifyFlushEvent();
context_.sorter->PushTracePacket(1500, state.current_generation(),
std::move(view_5));
// Nothing should be extracted as we haven't seen the second flush.
context_.sorter->NotifyReadBufferEvent();
// Now we've seen the second flush we should extract the next two packets.
context_.sorter->NotifyFlushEvent();
{
InSequence s;
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1300, test_buffer_.data(), 3));
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1400, test_buffer_.data(), 4));
}
context_.sorter->NotifyReadBufferEvent();
// The forced extraction should get the last packet.
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1500, test_buffer_.data(), 5));
context_.sorter->ExtractEventsForced();
}
// Simulate a producer bug where the third packet is emitted
// out of order. Verify that we track the stats correctly.
TEST_F(TraceSorterTest, OutOfOrder) {
CreateSorter(false);
PacketSequenceState state(&context_);
TraceBlobView view_1 = test_buffer_.slice_off(0, 1);
TraceBlobView view_2 = test_buffer_.slice_off(0, 2);
TraceBlobView view_3 = test_buffer_.slice_off(0, 3);
TraceBlobView view_4 = test_buffer_.slice_off(0, 4);
context_.sorter->NotifyFlushEvent();
context_.sorter->NotifyFlushEvent();
context_.sorter->PushTracePacket(1200, state.current_generation(),
std::move(view_2));
context_.sorter->PushTracePacket(1100, state.current_generation(),
std::move(view_1));
context_.sorter->NotifyReadBufferEvent();
// Both of the packets should have been pushed through.
context_.sorter->NotifyFlushEvent();
context_.sorter->NotifyFlushEvent();
{
InSequence s;
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1100, test_buffer_.data(), 1));
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1200, test_buffer_.data(), 2));
}
context_.sorter->NotifyReadBufferEvent();
// Now, pass the third packet out of order.
context_.sorter->NotifyFlushEvent();
context_.sorter->NotifyFlushEvent();
context_.sorter->PushTracePacket(1150, state.current_generation(),
std::move(view_3));
context_.sorter->NotifyReadBufferEvent();
// The third packet should still be pushed through.
context_.sorter->NotifyFlushEvent();
context_.sorter->NotifyFlushEvent();
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1150, test_buffer_.data(), 3));
context_.sorter->NotifyReadBufferEvent();
// But we should also increment the stat that this was out of order.
ASSERT_EQ(
context_.storage->stats()[stats::sorter_push_event_out_of_order].value,
1);
// Push the fourth packet also out of order but after third.
context_.sorter->NotifyFlushEvent();
context_.sorter->NotifyFlushEvent();
context_.sorter->PushTracePacket(1170, state.current_generation(),
std::move(view_4));
context_.sorter->NotifyReadBufferEvent();
// The fourt packet should still be pushed through.
EXPECT_CALL(*parser_, MOCK_ParseTracePacket(1170, test_buffer_.data(), 4));
context_.sorter->ExtractEventsForced();
// But we should also increment the stat that this was out of order.
ASSERT_EQ(
context_.storage->stats()[stats::sorter_push_event_out_of_order].value,
2);
}
// Simulates a random stream of ftrace events happening on random CPUs.
// Tests that the output of the TraceSorter matches the timestamp order
// (% events happening at the same time on different CPUs).
TEST_F(TraceSorterTest, MultiQueueSorting) {
PacketSequenceState state(&context_);
std::minstd_rand0 rnd_engine(0);
std::map<int64_t /*ts*/, std::vector<uint32_t /*cpu*/>> expectations;
EXPECT_CALL(*parser_, MOCK_ParseFtracePacket(_, _, _, _))
.WillRepeatedly(Invoke([&expectations](uint32_t cpu, int64_t timestamp,
const uint8_t*, size_t) {
EXPECT_EQ(expectations.begin()->first, timestamp);
auto& cpus = expectations.begin()->second;
bool cpu_found = false;
for (auto it = cpus.begin(); it < cpus.end(); it++) {
if (*it != cpu)
continue;
cpu_found = true;
cpus.erase(it);
break;
}
if (cpus.empty())
expectations.erase(expectations.begin());
EXPECT_TRUE(cpu_found);
}));
// Allocate a 1000 byte trace blob and push one byte chunks to be sorted with
// random timestamps. This will stress test the sorter with worst case
// scenarios and will (and has many times) expose any subtle bugs hiding in
// the sorter logic.
TraceBlobView tbv(TraceBlob::Allocate(1000));
for (uint16_t i = 0; i < 1000; i++) {
int64_t ts = abs(static_cast<int64_t>(rnd_engine()));
uint8_t num_cpus = rnd_engine() % 3;
for (uint8_t j = 0; j < num_cpus; j++) {
uint32_t cpu = static_cast<uint32_t>(rnd_engine() % 32);
expectations[ts].push_back(cpu);
context_.sorter->PushFtraceEvent(cpu, ts, tbv.slice_off(i, 1),
state.current_generation());
}
}
context_.sorter->ExtractEventsForced();
EXPECT_TRUE(expectations.empty());
}
} // namespace
} // namespace trace_processor
} // namespace perfetto