blob: 90682dc30c55860c3b4f24648a59083b045ba13b [file] [log] [blame]
/*
* Copyright (C) 2017 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/tracing/core/shared_memory_arbiter_impl.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "perfetto/base/utils.h"
#include "perfetto/tracing/core/basic_types.h"
#include "perfetto/tracing/core/commit_data_request.h"
#include "perfetto/tracing/core/shared_memory_abi.h"
#include "perfetto/tracing/core/trace_writer.h"
#include "src/base/test/gtest_test_suite.h"
#include "src/base/test/test_task_runner.h"
#include "src/tracing/core/patch_list.h"
#include "src/tracing/test/aligned_buffer_test.h"
namespace perfetto {
namespace {
using testing::Invoke;
using testing::_;
class MockProducerEndpoint : public TracingService::ProducerEndpoint {
public:
void RegisterDataSource(const DataSourceDescriptor&) override {}
void UnregisterDataSource(const std::string&) override {}
void NotifyFlushComplete(FlushRequestID) override {}
void NotifyDataSourceStarted(DataSourceInstanceID) override {}
void NotifyDataSourceStopped(DataSourceInstanceID) override {}
void ActivateTriggers(const std::vector<std::string>&) {}
SharedMemory* shared_memory() const override { return nullptr; }
size_t shared_buffer_page_size_kb() const override { return 0; }
std::unique_ptr<TraceWriter> CreateTraceWriter(BufferID) override {
return nullptr;
}
SharedMemoryArbiter* GetInProcessShmemArbiter() override { return nullptr; }
MOCK_METHOD2(CommitData, void(const CommitDataRequest&, CommitDataCallback));
MOCK_METHOD2(RegisterTraceWriter, void(uint32_t, uint32_t));
MOCK_METHOD1(UnregisterTraceWriter, void(uint32_t));
};
class SharedMemoryArbiterImplTest : public AlignedBufferTest {
public:
void SetUp() override {
AlignedBufferTest::SetUp();
task_runner_.reset(new base::TestTaskRunner());
arbiter_.reset(new SharedMemoryArbiterImpl(buf(), buf_size(), page_size(),
&mock_producer_endpoint_,
task_runner_.get()));
}
void TearDown() override {
arbiter_.reset();
task_runner_.reset();
}
std::unique_ptr<base::TestTaskRunner> task_runner_;
std::unique_ptr<SharedMemoryArbiterImpl> arbiter_;
MockProducerEndpoint mock_producer_endpoint_;
std::function<void(const std::vector<uint32_t>&)> on_pages_complete_;
};
size_t const kPageSizes[] = {4096, 65536};
INSTANTIATE_TEST_SUITE_P(PageSize,
SharedMemoryArbiterImplTest,
::testing::ValuesIn(kPageSizes));
// The buffer has 14 pages (kNumPages), each will be partitioned in 14 chunks.
// The test requests 30 chunks (2 full pages + 2 chunks from a 3rd page) and
// releases them in different batches. It tests the consistency of the batches
// and the releasing order.
TEST_P(SharedMemoryArbiterImplTest, GetAndReturnChunks) {
SharedMemoryArbiterImpl::set_default_layout_for_testing(
SharedMemoryABI::PageLayout::kPageDiv14);
static constexpr size_t kTotChunks = kNumPages * 14;
SharedMemoryABI::Chunk chunks[kTotChunks];
for (size_t i = 0; i < 14 * 2 + 2; i++) {
chunks[i] = arbiter_->GetNewChunk({}, 0 /*size_hint*/);
ASSERT_TRUE(chunks[i].is_valid());
}
// Finally return the first 28 chunks (full 2 pages) and only the 2nd chunk of
// the 2rd page. Chunks are release in interleaved order: 1,0,3,2,5,4,7,6.
// Check that the notification callback is posted and order is consistent.
auto on_commit_1 = task_runner_->CreateCheckpoint("on_commit_1");
EXPECT_CALL(mock_producer_endpoint_, CommitData(_, _))
.WillOnce(Invoke([on_commit_1](const CommitDataRequest& req,
MockProducerEndpoint::CommitDataCallback) {
ASSERT_EQ(14 * 2 + 1, req.chunks_to_move_size());
for (size_t i = 0; i < 14 * 2; i++) {
ASSERT_EQ(i / 14, req.chunks_to_move()[i].page());
ASSERT_EQ((i % 14) ^ 1, req.chunks_to_move()[i].chunk());
ASSERT_EQ(i % 5, req.chunks_to_move()[i].target_buffer());
}
ASSERT_EQ(2u, req.chunks_to_move()[28].page());
ASSERT_EQ(1u, req.chunks_to_move()[28].chunk());
ASSERT_EQ(42u, req.chunks_to_move()[28].target_buffer());
on_commit_1();
}));
PatchList ignored;
for (size_t i = 0; i < 14 * 2; i++)
arbiter_->ReturnCompletedChunk(std::move(chunks[i ^ 1]), i % 5, &ignored);
arbiter_->ReturnCompletedChunk(std::move(chunks[29]), 42, &ignored);
task_runner_->RunUntilCheckpoint("on_commit_1");
// Then release the 1st chunk of the 3rd page, and check that we get a
// notification for that as well.
auto on_commit_2 = task_runner_->CreateCheckpoint("on_commit_2");
EXPECT_CALL(mock_producer_endpoint_, CommitData(_, _))
.WillOnce(Invoke([on_commit_2](const CommitDataRequest& req,
MockProducerEndpoint::CommitDataCallback) {
ASSERT_EQ(1, req.chunks_to_move_size());
ASSERT_EQ(2u, req.chunks_to_move()[0].page());
ASSERT_EQ(0u, req.chunks_to_move()[0].chunk());
ASSERT_EQ(43u, req.chunks_to_move()[0].target_buffer());
on_commit_2();
}));
arbiter_->ReturnCompletedChunk(std::move(chunks[28]), 43, &ignored);
task_runner_->RunUntilCheckpoint("on_commit_2");
}
// Check that we can actually create up to kMaxWriterID TraceWriter(s).
TEST_P(SharedMemoryArbiterImplTest, WriterIDsAllocation) {
auto checkpoint = task_runner_->CreateCheckpoint("last_unregistered");
int num_unregistered = 0;
{
std::map<WriterID, std::unique_ptr<TraceWriter>> writers;
for (size_t i = 0; i < kMaxWriterID; i++) {
EXPECT_CALL(mock_producer_endpoint_,
RegisterTraceWriter(static_cast<uint32_t>(i + 1), 0));
EXPECT_CALL(mock_producer_endpoint_,
UnregisterTraceWriter(static_cast<uint32_t>(i + 1)))
.WillOnce(Invoke([checkpoint, &num_unregistered](uint32_t) {
num_unregistered++;
if (num_unregistered == kMaxWriterID)
checkpoint();
}));
std::unique_ptr<TraceWriter> writer = arbiter_->CreateTraceWriter(0);
ASSERT_TRUE(writer);
WriterID writer_id = writer->writer_id();
ASSERT_TRUE(writers.emplace(writer_id, std::move(writer)).second);
}
// A further call should return a null impl of trace writer as we exhausted
// writer IDs.
ASSERT_EQ(arbiter_->CreateTraceWriter(0)->writer_id(), 0);
}
// This should run the Register/UnregisterTraceWriter calls expected above.
task_runner_->RunUntilCheckpoint("last_unregistered", 15000);
}
// TODO(primiano): add multi-threaded tests.
} // namespace
} // namespace perfetto