| /* |
| * 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 "perfetto/tracing/core/shared_memory_abi.h" |
| |
| #include "gtest/gtest.h" |
| #include "perfetto/tracing/core/basic_types.h" |
| #include "src/base/test/gtest_test_suite.h" |
| #include "src/tracing/test/aligned_buffer_test.h" |
| |
| namespace perfetto { |
| namespace { |
| |
| using testing::ValuesIn; |
| using Chunk = SharedMemoryABI::Chunk; |
| using ChunkHeader = SharedMemoryABI::ChunkHeader; |
| |
| using SharedMemoryABITest = AlignedBufferTest; |
| |
| size_t const kPageSizes[] = {4096, 8192, 16384, 32768, 65536}; |
| INSTANTIATE_TEST_SUITE_P(PageSize, SharedMemoryABITest, ValuesIn(kPageSizes)); |
| |
| TEST_P(SharedMemoryABITest, NominalCases) { |
| SharedMemoryABI abi(buf(), buf_size(), page_size()); |
| |
| ASSERT_EQ(buf(), abi.start()); |
| ASSERT_EQ(buf() + buf_size(), abi.end()); |
| ASSERT_EQ(buf_size(), abi.size()); |
| ASSERT_EQ(page_size(), abi.page_size()); |
| ASSERT_EQ(kNumPages, abi.num_pages()); |
| |
| for (size_t i = 0; i < kNumPages; i++) { |
| ASSERT_TRUE(abi.is_page_free(i)); |
| ASSERT_FALSE(abi.is_page_complete(i)); |
| // GetFreeChunks() should return 0 for an unpartitioned page. |
| ASSERT_EQ(0u, abi.GetFreeChunks(i)); |
| } |
| |
| ASSERT_TRUE(abi.TryPartitionPage(0, SharedMemoryABI::kPageDiv1)); |
| ASSERT_EQ(0x01u, abi.GetFreeChunks(0)); |
| |
| ASSERT_TRUE(abi.TryPartitionPage(1, SharedMemoryABI::kPageDiv2)); |
| ASSERT_EQ(0x03u, abi.GetFreeChunks(1)); |
| |
| ASSERT_TRUE(abi.TryPartitionPage(2, SharedMemoryABI::kPageDiv4)); |
| ASSERT_EQ(0x0fu, abi.GetFreeChunks(2)); |
| |
| ASSERT_TRUE(abi.TryPartitionPage(3, SharedMemoryABI::kPageDiv7)); |
| ASSERT_EQ(0x7fu, abi.GetFreeChunks(3)); |
| |
| ASSERT_TRUE(abi.TryPartitionPage(4, SharedMemoryABI::kPageDiv14)); |
| ASSERT_EQ(0x3fffu, abi.GetFreeChunks(4)); |
| |
| // Repartitioning an existing page must fail. |
| ASSERT_FALSE(abi.TryPartitionPage(0, SharedMemoryABI::kPageDiv1)); |
| ASSERT_FALSE(abi.TryPartitionPage(4, SharedMemoryABI::kPageDiv14)); |
| |
| for (size_t i = 0; i <= 4; i++) { |
| ASSERT_FALSE(abi.is_page_free(i)); |
| ASSERT_FALSE(abi.is_page_complete(i)); |
| } |
| |
| uint16_t last_chunk_id = 0; |
| uint16_t last_writer_id = 0; |
| uint8_t* last_chunk_begin = nullptr; |
| uint8_t* last_chunk_end = nullptr; |
| |
| for (size_t page_idx = 0; page_idx <= 4; page_idx++) { |
| uint8_t* const page_start = buf() + page_idx * page_size(); |
| uint8_t* const page_end = page_start + page_size(); |
| const size_t num_chunks = |
| SharedMemoryABI::GetNumChunksForLayout(abi.GetPageLayout(page_idx)); |
| Chunk chunks[14]; |
| |
| for (size_t chunk_idx = 0; chunk_idx < num_chunks; chunk_idx++) { |
| Chunk& chunk = chunks[chunk_idx]; |
| ChunkHeader header{}; |
| |
| ASSERT_EQ(SharedMemoryABI::kChunkFree, |
| abi.GetChunkState(page_idx, chunk_idx)); |
| uint16_t chunk_id = ++last_chunk_id; |
| last_writer_id = (last_writer_id + 1) & kMaxWriterID; |
| uint16_t writer_id = last_writer_id; |
| header.chunk_id.store(chunk_id); |
| header.writer_id.store(writer_id); |
| |
| uint16_t packets_count = static_cast<uint16_t>(chunk_idx * 10); |
| const uint8_t kFlagsMask = (1 << 6) - 1; |
| uint8_t flags = static_cast<uint8_t>((0xffu - chunk_idx) & kFlagsMask); |
| header.packets.store({packets_count, flags}); |
| |
| chunk = abi.TryAcquireChunkForWriting(page_idx, chunk_idx, &header); |
| ASSERT_TRUE(chunk.is_valid()); |
| ASSERT_EQ(SharedMemoryABI::kChunkBeingWritten, |
| abi.GetChunkState(page_idx, chunk_idx)); |
| |
| // Sanity check chunk bounds. |
| size_t expected_chunk_size = |
| (page_size() - sizeof(SharedMemoryABI::PageHeader)) / num_chunks; |
| expected_chunk_size = expected_chunk_size - (expected_chunk_size % 4); |
| ASSERT_EQ(expected_chunk_size, chunk.size()); |
| ASSERT_EQ(expected_chunk_size - sizeof(SharedMemoryABI::ChunkHeader), |
| chunk.payload_size()); |
| ASSERT_GT(chunk.begin(), page_start); |
| ASSERT_GT(chunk.begin(), last_chunk_begin); |
| ASSERT_GE(chunk.begin(), last_chunk_end); |
| ASSERT_LE(chunk.end(), page_end); |
| ASSERT_GT(chunk.end(), chunk.begin()); |
| ASSERT_EQ(chunk.end(), chunk.begin() + chunk.size()); |
| last_chunk_begin = chunk.begin(); |
| last_chunk_end = chunk.end(); |
| |
| ASSERT_EQ(chunk_id, chunk.header()->chunk_id.load()); |
| ASSERT_EQ(writer_id, chunk.header()->writer_id.load()); |
| ASSERT_EQ(packets_count, chunk.header()->packets.load().count); |
| ASSERT_EQ(flags, chunk.header()->packets.load().flags); |
| ASSERT_EQ(std::make_pair(packets_count, flags), |
| chunk.GetPacketCountAndFlags()); |
| |
| chunk.IncrementPacketCount(); |
| ASSERT_EQ(packets_count + 1, chunk.header()->packets.load().count); |
| |
| chunk.IncrementPacketCount(); |
| ASSERT_EQ(packets_count + 2, chunk.header()->packets.load().count); |
| |
| chunk.SetFlag( |
| SharedMemoryABI::ChunkHeader::kLastPacketContinuesOnNextChunk); |
| ASSERT_TRUE( |
| chunk.header()->packets.load().flags & |
| SharedMemoryABI::ChunkHeader::kLastPacketContinuesOnNextChunk); |
| |
| // Reacquiring the same chunk should fail. |
| ASSERT_FALSE(abi.TryAcquireChunkForWriting(page_idx, chunk_idx, &header) |
| .is_valid()); |
| } |
| |
| // Now release chunks and check the Release() logic. |
| for (size_t chunk_idx = 0; chunk_idx < num_chunks; chunk_idx++) { |
| Chunk& chunk = chunks[chunk_idx]; |
| |
| size_t res = abi.ReleaseChunkAsComplete(std::move(chunk)); |
| ASSERT_EQ(page_idx, res); |
| ASSERT_EQ(chunk_idx == num_chunks - 1, abi.is_page_complete(page_idx)); |
| ASSERT_EQ(SharedMemoryABI::kChunkComplete, |
| abi.GetChunkState(page_idx, chunk_idx)); |
| } |
| |
| // Now acquire all chunks for reading. |
| for (size_t chunk_idx = 0; chunk_idx < num_chunks; chunk_idx++) { |
| Chunk& chunk = chunks[chunk_idx]; |
| chunk = abi.TryAcquireChunkForReading(page_idx, chunk_idx); |
| ASSERT_TRUE(chunk.is_valid()); |
| ASSERT_EQ(SharedMemoryABI::kChunkBeingRead, |
| abi.GetChunkState(page_idx, chunk_idx)); |
| } |
| |
| // Finally release all chunks as free. |
| for (size_t chunk_idx = 0; chunk_idx < num_chunks; chunk_idx++) { |
| Chunk& chunk = chunks[chunk_idx]; |
| |
| // If this was the last chunk in the page, the full page should be marked |
| // as free. |
| size_t res = abi.ReleaseChunkAsFree(std::move(chunk)); |
| ASSERT_EQ(page_idx, res); |
| ASSERT_EQ(chunk_idx == num_chunks - 1, abi.is_page_free(page_idx)); |
| ASSERT_EQ(SharedMemoryABI::kChunkFree, |
| abi.GetChunkState(page_idx, chunk_idx)); |
| } |
| } |
| } |
| |
| } // namespace |
| } // namespace perfetto |