| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // TODO(CF-887): Use std::map instead of FBL in this file. |
| #include <lib/inspect/cpp/vmo/block.h> |
| #include <lib/inspect/cpp/vmo/scanner.h> |
| #include <lib/inspect/cpp/vmo/snapshot.h> |
| #include <lib/inspect/cpp/vmo/state.h> |
| #include <threads.h> |
| |
| #include <iostream> |
| #include <memory> |
| |
| #include <fbl/intrusive_wavl_tree.h> |
| #include <fbl/vector.h> |
| #include <pretty/hexdump.h> |
| #include <zxtest/zxtest.h> |
| |
| namespace { |
| |
| using inspect::ByteVectorProperty; |
| using inspect::DoubleArray; |
| using inspect::DoubleProperty; |
| using inspect::IntArray; |
| using inspect::IntProperty; |
| using inspect::Link; |
| using inspect::Node; |
| using inspect::Snapshot; |
| using inspect::StringProperty; |
| using inspect::UintArray; |
| using inspect::UintProperty; |
| using inspect::internal::ArrayBlockFormat; |
| using inspect::internal::ArrayBlockPayload; |
| using inspect::internal::Block; |
| using inspect::internal::BlockIndex; |
| using inspect::internal::BlockType; |
| using inspect::internal::ExtentBlockFields; |
| using inspect::internal::HeaderBlockFields; |
| using inspect::internal::Heap; |
| using inspect::internal::kMagicNumber; |
| using inspect::internal::kNumOrders; |
| using inspect::internal::LinkBlockDisposition; |
| using inspect::internal::LinkBlockPayload; |
| using inspect::internal::NameBlockFields; |
| using inspect::internal::PropertyBlockFormat; |
| using inspect::internal::PropertyBlockPayload; |
| using inspect::internal::ScanBlocks; |
| using inspect::internal::State; |
| using inspect::internal::ValueBlockFields; |
| |
| zx::vmo MakeVmo(size_t size) { |
| zx::vmo ret; |
| EXPECT_OK(zx::vmo::create(size, 0, &ret)); |
| return ret; |
| } |
| |
| // Container for scanned blocks from the buffer. |
| // TODO(CF-236): Use std::map instead of intrusive containers when |
| // libstd++ is available. |
| struct ScannedBlock : public fbl::WAVLTreeContainable<std::unique_ptr<ScannedBlock>> { |
| BlockIndex index; |
| const Block* block; |
| |
| ScannedBlock(BlockIndex index, const Block* block) : index(index), block(block) {} |
| |
| BlockIndex GetKey() const { return index; } |
| }; |
| |
| // Helper to just print blocks, return empty string to allow triggering as part of context for an |
| // assertion. |
| std::string print_block(const Block* block) { |
| hexdump8(block, sizeof(Block)); |
| return ""; |
| } |
| |
| void CompareBlock(const Block* actual, const Block expected) { |
| if (memcmp((const uint8_t*)(&expected), (const uint8_t*)(actual), sizeof(Block)) != 0) { |
| std::cout << "Block header contents did not match" << print_block(&expected) |
| << print_block(actual); |
| EXPECT_TRUE(false); |
| } |
| } |
| |
| template <typename T> |
| void CompareArray(const Block* block, const T* expected, size_t count) { |
| EXPECT_EQ(0, |
| memcmp(reinterpret_cast<const uint8_t*>(expected), |
| reinterpret_cast<const uint8_t*>(&block->payload) + 8, sizeof(int64_t) * count)); |
| } |
| |
| Block MakeBlock(uint64_t header) { |
| Block ret; |
| ret.header = header; |
| ret.payload.u64 = 0; |
| return ret; |
| } |
| |
| Block MakeBlock(uint64_t header, const char payload[9]) { |
| Block ret; |
| ret.header = header; |
| memcpy(ret.payload.data, payload, 8); |
| return ret; |
| } |
| |
| Block MakeBlock(uint64_t header, uint64_t payload) { |
| Block ret; |
| ret.header = header; |
| ret.payload.u64 = payload; |
| return ret; |
| } |
| |
| Block MakeIntBlock(uint64_t header, int64_t payload) { |
| Block ret; |
| ret.header = header; |
| ret.payload.i64 = payload; |
| return ret; |
| } |
| |
| Block MakeDoubleBlock(uint64_t header, double payload) { |
| Block ret; |
| ret.header = header; |
| ret.payload.f64 = payload; |
| return ret; |
| } |
| |
| Block MakeHeader(uint64_t generation) { |
| Block ret; |
| ret.header = HeaderBlockFields::Type::Make(BlockType::kHeader) | |
| HeaderBlockFields::Order::Make(0) | HeaderBlockFields::Version::Make(0); |
| memcpy(&ret.header_data[4], kMagicNumber, 4); |
| ret.payload.u64 = generation; |
| return ret; |
| } |
| |
| Snapshot SnapshotAndScan(const zx::vmo& vmo, |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>>* blocks, |
| size_t* free_blocks, size_t* allocated_blocks) { |
| *free_blocks = *allocated_blocks = 0; |
| |
| Snapshot snapshot; |
| Snapshot::Create(vmo, &snapshot); |
| if (snapshot) { |
| ScanBlocks(snapshot.data(), snapshot.size(), [&](BlockIndex index, const Block* block) { |
| if (GetType(block) == BlockType::kFree) { |
| *free_blocks += 1; |
| } else { |
| *allocated_blocks += 1; |
| } |
| blocks->insert(std::make_unique<ScannedBlock>(index, block)); |
| return true; |
| }); |
| } |
| return snapshot; |
| } |
| |
| TEST(State, CreateAndCopy) { |
| auto state = State::CreateWithSize(4096); |
| ASSERT_TRUE(state); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| EXPECT_EQ(1u, allocated_blocks); |
| EXPECT_EQ(8u, free_blocks); |
| blocks.clear(); |
| |
| zx::vmo copy; |
| ASSERT_TRUE(state->Copy(©)); |
| |
| snapshot = SnapshotAndScan(copy, &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| EXPECT_EQ(1u, allocated_blocks); |
| EXPECT_EQ(8u, free_blocks); |
| } |
| |
| TEST(State, CreateIntProperty) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| IntProperty a = state->CreateIntProperty("a", 0, 0); |
| IntProperty b = state->CreateIntProperty("b", 0, 0); |
| IntProperty c = state->CreateIntProperty("c", 0, 0); |
| |
| a.Set(10); |
| b.Add(5); |
| b.Subtract(10); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header and 2 for each metric. |
| EXPECT_EQ(7u, allocated_blocks); |
| EXPECT_EQ(6u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(12)); |
| CompareBlock(blocks.find(1)->block, |
| MakeIntBlock(ValueBlockFields::Type::Make(BlockType::kIntValue) | |
| ValueBlockFields::NameIndex::Make(2), |
| 10)); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| CompareBlock(blocks.find(3)->block, |
| MakeIntBlock(ValueBlockFields::Type::Make(BlockType::kIntValue) | |
| ValueBlockFields::NameIndex::Make(4), |
| -5)); |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "b\0\0\0\0\0\0\0")); |
| CompareBlock(blocks.find(5)->block, |
| MakeIntBlock(ValueBlockFields::Type::Make(BlockType::kIntValue) | |
| ValueBlockFields::NameIndex::Make(6), |
| 0)); |
| CompareBlock(blocks.find(6)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "c\0\0\0\0\0\0\0")); |
| } |
| |
| TEST(State, CreateUintProperty) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| UintProperty a = state->CreateUintProperty("a", 0, 0); |
| UintProperty b = state->CreateUintProperty("b", 0, 0); |
| UintProperty c = state->CreateUintProperty("c", 0, 0); |
| |
| a.Set(10); |
| b.Add(15); |
| b.Subtract(10); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header and 2 for each metric. |
| EXPECT_EQ(7u, allocated_blocks); |
| EXPECT_EQ(6u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(12)); |
| CompareBlock(blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kUintValue) | |
| ValueBlockFields::NameIndex::Make(2), |
| 10)); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| CompareBlock(blocks.find(3)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kUintValue) | |
| ValueBlockFields::NameIndex::Make(4), |
| 5)); |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "b\0\0\0\0\0\0\0")); |
| CompareBlock(blocks.find(5)->block, |
| MakeIntBlock(ValueBlockFields::Type::Make(BlockType::kUintValue) | |
| ValueBlockFields::NameIndex::Make(6), |
| 0)); |
| CompareBlock(blocks.find(6)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "c\0\0\0\0\0\0\0")); |
| } |
| |
| TEST(State, CreateDoubleProperty) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| DoubleProperty a = state->CreateDoubleProperty("a", 0, 0); |
| DoubleProperty b = state->CreateDoubleProperty("b", 0, 0); |
| DoubleProperty c = state->CreateDoubleProperty("c", 0, 0); |
| |
| a.Set(3.25); |
| b.Add(0.5); |
| b.Subtract(0.25); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header and 2 for each metric. |
| EXPECT_EQ(7u, allocated_blocks); |
| EXPECT_EQ(6u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(12)); |
| CompareBlock(blocks.find(1)->block, |
| MakeDoubleBlock(ValueBlockFields::Type::Make(BlockType::kDoubleValue) | |
| ValueBlockFields::NameIndex::Make(2), |
| 3.25)); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| CompareBlock(blocks.find(3)->block, |
| MakeDoubleBlock(ValueBlockFields::Type::Make(BlockType::kDoubleValue) | |
| ValueBlockFields::NameIndex::Make(4), |
| 0.25)); |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "b\0\0\0\0\0\0\0")); |
| CompareBlock(blocks.find(5)->block, |
| MakeIntBlock(ValueBlockFields::Type::Make(BlockType::kDoubleValue) | |
| ValueBlockFields::NameIndex::Make(6), |
| 0)); |
| CompareBlock(blocks.find(6)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "c\0\0\0\0\0\0\0")); |
| } |
| |
| TEST(State, CreateArrays) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| IntArray a = state->CreateIntArray("a", 0, 10, ArrayBlockFormat::kDefault); |
| UintArray b = state->CreateUintArray("b", 0, 10, ArrayBlockFormat::kDefault); |
| DoubleArray c = state->CreateDoubleArray("c", 0, 10, ArrayBlockFormat::kDefault); |
| |
| a.Add(0, 10); |
| a.Set(1, -10); |
| a.Subtract(2, 9); |
| // out of bounds |
| a.Set(10, -10); |
| a.Add(10, 0xFF); |
| a.Subtract(10, 0xDD); |
| |
| b.Add(0, 10); |
| b.Set(1, 10); |
| b.Subtract(1, 9); |
| // out of bounds |
| b.Set(10, 10); |
| b.Add(10, 10); |
| b.Subtract(10, 10); |
| |
| c.Add(0, .25); |
| c.Set(1, 1.25); |
| c.Subtract(1, .5); |
| // out of bounds |
| c.Set(10, 10); |
| c.Add(10, 10); |
| c.Subtract(10, 10); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header and 2 for each metric. |
| EXPECT_EQ(7u, allocated_blocks); |
| EXPECT_EQ(4u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(42)); |
| |
| { |
| CompareBlock(blocks.find(1)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| CompareBlock( |
| blocks.find(8)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::Order::Make(3) | ValueBlockFields::NameIndex::Make(1), |
| ArrayBlockPayload::EntryType::Make(BlockType::kIntValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kDefault) | |
| ArrayBlockPayload::Count::Make(10))); |
| int64_t a_array_values[] = {10, -10, -9, 0, 0, 0, 0, 0, 0, 0}; |
| CompareArray(blocks.find(8)->block, a_array_values, 10); |
| } |
| |
| { |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "b\0\0\0\0\0\0\0")); |
| |
| CompareBlock( |
| blocks.find(16)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::Order::Make(3) | ValueBlockFields::NameIndex::Make(2), |
| ArrayBlockPayload::EntryType::Make(BlockType::kUintValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kDefault) | |
| ArrayBlockPayload::Count::Make(10))); |
| uint64_t b_array_values[] = {10, 1, 0, 0, 0, 0, 0, 0, 0, 0}; |
| CompareArray(blocks.find(16)->block, b_array_values, 10); |
| } |
| |
| { |
| CompareBlock(blocks.find(3)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "c\0\0\0\0\0\0\0")); |
| |
| CompareBlock( |
| blocks.find(24)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::Order::Make(3) | ValueBlockFields::NameIndex::Make(3), |
| ArrayBlockPayload::EntryType::Make(BlockType::kDoubleValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kDefault) | |
| ArrayBlockPayload::Count::Make(10))); |
| double c_array_values[] = {.25, .75, 0, 0, 0, 0, 0, 0, 0, 0}; |
| CompareArray(blocks.find(24)->block, c_array_values, 10); |
| } |
| } |
| |
| TEST(State, CreateArrayChildren) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| Node root = state->CreateNode("root", 0); |
| |
| IntArray a = root.CreateIntArray("a", 10); |
| UintArray b = root.CreateUintArray("b", 10); |
| DoubleArray c = root.CreateDoubleArray("c", 10); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header and 2 for each metric. |
| EXPECT_EQ(9u, allocated_blocks); |
| EXPECT_EQ(4u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(8)); |
| |
| CompareBlock( |
| blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::ParentIndex::Make(0) | ValueBlockFields::NameIndex::Make(2), |
| 3)); |
| |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(4), |
| "root\0\0\0\0")); |
| |
| { |
| CompareBlock(blocks.find(3)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| CompareBlock( |
| blocks.find(8)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::Order::Make(3) | |
| ValueBlockFields::NameIndex::Make(3), |
| ArrayBlockPayload::EntryType::Make(BlockType::kIntValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kDefault) | |
| ArrayBlockPayload::Count::Make(10))); |
| int64_t a_array_values[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
| CompareArray(blocks.find(8)->block, a_array_values, 10); |
| } |
| |
| { |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "b\0\0\0\0\0\0\0")); |
| |
| CompareBlock( |
| blocks.find(16)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::Order::Make(3) | |
| ValueBlockFields::NameIndex::Make(4), |
| ArrayBlockPayload::EntryType::Make(BlockType::kUintValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kDefault) | |
| ArrayBlockPayload::Count::Make(10))); |
| uint64_t b_array_values[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
| CompareArray(blocks.find(16)->block, b_array_values, 10); |
| } |
| |
| { |
| CompareBlock(blocks.find(5)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "c\0\0\0\0\0\0\0")); |
| |
| CompareBlock( |
| blocks.find(24)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::Order::Make(3) | |
| ValueBlockFields::NameIndex::Make(5), |
| ArrayBlockPayload::EntryType::Make(BlockType::kDoubleValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kDefault) | |
| ArrayBlockPayload::Count::Make(10))); |
| double c_array_values[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
| CompareArray(blocks.find(24)->block, c_array_values, 10); |
| } |
| } |
| |
| TEST(State, CreateLinearHistogramChildren) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| Node root = state->CreateNode("root", 0); |
| |
| auto a = root.CreateLinearIntHistogram("a", 10 /*floor*/, 5 /*step_size*/, 6 /*buckets*/); |
| auto b = root.CreateLinearUintHistogram("b", 10 /*floor*/, 5 /*step_size*/, 6 /*buckets*/); |
| auto c = root.CreateLinearDoubleHistogram("c", 10 /*floor*/, 5 /*step_size*/, 6 /*buckets*/); |
| |
| a.Insert(0, 3); |
| a.Insert(10); |
| a.Insert(1000); |
| a.Insert(21); |
| |
| b.Insert(0, 3); |
| b.Insert(10); |
| b.Insert(1000); |
| b.Insert(21); |
| |
| c.Insert(0, 3); |
| c.Insert(10); |
| c.Insert(1000); |
| c.Insert(21); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header and 2 for each metric. |
| EXPECT_EQ(9u, allocated_blocks); |
| EXPECT_EQ(4u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(2 + 6 * 3 + 8 * 3)); |
| |
| CompareBlock( |
| blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::ParentIndex::Make(0) | ValueBlockFields::NameIndex::Make(2), |
| 3)); |
| |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(4), |
| "root\0\0\0\0")); |
| |
| { |
| CompareBlock(blocks.find(3)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| CompareBlock( |
| blocks.find(8)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::Order::Make(3) | |
| ValueBlockFields::NameIndex::Make(3), |
| ArrayBlockPayload::EntryType::Make(BlockType::kIntValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kLinearHistogram) | |
| ArrayBlockPayload::Count::Make(10))); |
| // Array is: |
| // <floor>, <step_size>, <underflow>, <N buckets>..., <overflow> |
| int64_t a_array_values[] = {10, 5, 3, 1, 0, 1, 0, 0, 0, 1}; |
| CompareArray(blocks.find(8)->block, a_array_values, 10); |
| } |
| |
| { |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "b\0\0\0\0\0\0\0")); |
| |
| CompareBlock( |
| blocks.find(16)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::Order::Make(3) | |
| ValueBlockFields::NameIndex::Make(4), |
| ArrayBlockPayload::EntryType::Make(BlockType::kUintValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kLinearHistogram) | |
| ArrayBlockPayload::Count::Make(10))); |
| // Array is: |
| // <floor>, <step_size>, <underflow>, <N buckets>..., <overflow> |
| uint64_t b_array_values[] = {10, 5, 3, 1, 0, 1, 0, 0, 0, 1}; |
| CompareArray(blocks.find(16)->block, b_array_values, 10); |
| } |
| |
| { |
| CompareBlock(blocks.find(5)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "c\0\0\0\0\0\0\0")); |
| |
| CompareBlock( |
| blocks.find(24)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::Order::Make(3) | |
| ValueBlockFields::NameIndex::Make(5), |
| ArrayBlockPayload::EntryType::Make(BlockType::kDoubleValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kLinearHistogram) | |
| ArrayBlockPayload::Count::Make(10))); |
| // Array is: |
| // <floor>, <step_size>, <underflow>, <N buckets>..., <overflow> |
| double c_array_values[] = {10, 5, 3, 1, 0, 1, 0, 0, 0, 1}; |
| CompareArray(blocks.find(24)->block, c_array_values, 10); |
| } |
| } |
| |
| TEST(State, CreateExponentialHistogramChildren) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| Node root = state->CreateNode("root", 0); |
| |
| auto a = root.CreateExponentialIntHistogram("a", 1 /*floor*/, 1 /*initial_step*/, |
| 2 /*step_multiplier*/, 5 /*buckets*/); |
| auto b = root.CreateExponentialUintHistogram("b", 1 /*floor*/, 1 /*initial_step*/, |
| 2 /*step_multiplier*/, 5 /*buckets*/); |
| auto c = root.CreateExponentialDoubleHistogram("c", 1 /*floor*/, 1 /*initial_step*/, |
| 2 /*step_multiplier*/, 5 /*buckets*/); |
| |
| a.Insert(0, 3); |
| a.Insert(4); |
| a.Insert(1000); |
| a.Insert(30); |
| |
| b.Insert(0, 3); |
| b.Insert(4); |
| b.Insert(1000); |
| b.Insert(30); |
| |
| c.Insert(0, 3); |
| c.Insert(4); |
| c.Insert(1000); |
| c.Insert(30); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header and 2 for each metric. |
| EXPECT_EQ(9u, allocated_blocks); |
| EXPECT_EQ(4u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(2 + 8 * 3 + 8 * 3)); |
| |
| CompareBlock( |
| blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::ParentIndex::Make(0) | ValueBlockFields::NameIndex::Make(2), |
| 3)); |
| |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(4), |
| "root\0\0\0\0")); |
| |
| { |
| CompareBlock(blocks.find(3)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| CompareBlock( |
| blocks.find(8)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::Order::Make(3) | |
| ValueBlockFields::NameIndex::Make(3), |
| ArrayBlockPayload::EntryType::Make(BlockType::kIntValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kExponentialHistogram) | |
| ArrayBlockPayload::Count::Make(10))); |
| // Array is: |
| // <floor>, <initial_step>, <step_multipler>, <underflow>, <N buckets>..., <overflow> |
| int64_t a_array_values[] = {1, 1, 2, 3, 0, 0, 1, 0, 0, 2}; |
| CompareArray(blocks.find(8)->block, a_array_values, 10); |
| } |
| |
| { |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "b\0\0\0\0\0\0\0")); |
| |
| CompareBlock( |
| blocks.find(16)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::Order::Make(3) | |
| ValueBlockFields::NameIndex::Make(4), |
| ArrayBlockPayload::EntryType::Make(BlockType::kUintValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kExponentialHistogram) | |
| ArrayBlockPayload::Count::Make(10))); |
| // Array is: |
| // <floor>, <initial_step>, <step_multipler>, <underflow>, <N buckets>..., <overflow> |
| uint64_t b_array_values[] = {1, 1, 2, 3, 0, 0, 1, 0, 0, 2}; |
| CompareArray(blocks.find(16)->block, b_array_values, 10); |
| } |
| |
| { |
| CompareBlock(blocks.find(5)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "c\0\0\0\0\0\0\0")); |
| |
| CompareBlock( |
| blocks.find(24)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kArrayValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::Order::Make(3) | |
| ValueBlockFields::NameIndex::Make(5), |
| ArrayBlockPayload::EntryType::Make(BlockType::kDoubleValue) | |
| ArrayBlockPayload::Flags::Make(ArrayBlockFormat::kExponentialHistogram) | |
| ArrayBlockPayload::Count::Make(10))); |
| // Array is: |
| // <floor>, <initial_step>, <step_multipler>, <underflow>, <N buckets>..., <overflow> |
| double c_array_values[] = {1, 1, 2, 3, 0, 0, 1, 0, 0, 2}; |
| CompareArray(blocks.find(24)->block, c_array_values, 10); |
| } |
| } |
| |
| TEST(State, CreateSmallProperties) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| std::vector<uint8_t> temp = {'8', '8', '8', '8', '8', '8', '8', '8'}; |
| StringProperty a = state->CreateStringProperty("a", 0, "Hello"); |
| ByteVectorProperty b = state->CreateByteVectorProperty("b", 0, temp); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header (1), 2 single extent properties (6) |
| EXPECT_EQ(1u + 6u, allocated_blocks); |
| EXPECT_EQ(6u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(4)); |
| |
| // Property a fits in the first 3 blocks (value, name, extent). |
| CompareBlock(blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kPropertyValue) | |
| ValueBlockFields::NameIndex::Make(2), |
| PropertyBlockPayload::ExtentIndex::Make(3) | |
| PropertyBlockPayload::TotalLength::Make(5))); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| |
| CompareBlock(blocks.find(3)->block, |
| MakeBlock(ExtentBlockFields::Type::Make(BlockType::kExtent), "Hello\0\0\0")); |
| |
| // Property b fits in the next 3 blocks (value, name, extent). |
| |
| CompareBlock(blocks.find(4)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kPropertyValue) | |
| ValueBlockFields::NameIndex::Make(5), |
| PropertyBlockPayload::ExtentIndex::Make(6) | |
| PropertyBlockPayload::TotalLength::Make(8) | |
| PropertyBlockPayload::Flags::Make(PropertyBlockFormat::kBinary))); |
| CompareBlock(blocks.find(5)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "b\0\0\0\0\0\0\0")); |
| |
| CompareBlock(blocks.find(6)->block, |
| MakeBlock(ExtentBlockFields::Type::Make(BlockType::kExtent), "88888888")); |
| } |
| |
| TEST(State, CreateLargeSingleExtentProperties) { |
| auto vmo = MakeVmo(2 * 4096); // Need to extend to 2 pages to store both properties. |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| char input[] = "abcdefg"; |
| size_t input_size = 7; |
| std::vector<uint8_t> contents; |
| contents.reserve(2040); |
| for (int i = 0; i < 2040; i++) { |
| contents.push_back(input[i % input_size]); |
| } |
| std::string str_contents(reinterpret_cast<const char*>(contents.data()), 2040); |
| StringProperty a = state->CreateStringProperty("a", 0, str_contents); |
| ByteVectorProperty b = state->CreateByteVectorProperty("b", 0, contents); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header (1), 2 single extent properties (6) |
| EXPECT_EQ(1u + 6u, allocated_blocks); |
| EXPECT_EQ(7u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(4)); |
| |
| // Property a has the first 2 blocks for value and name, but needs a large block for the |
| // contents. |
| CompareBlock(blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kPropertyValue) | |
| ValueBlockFields::NameIndex::Make(2), |
| PropertyBlockPayload::ExtentIndex::Make(128) | |
| PropertyBlockPayload::TotalLength::Make(2040))); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| CompareBlock(blocks.find(128)->block, |
| MakeBlock(ExtentBlockFields::Type::Make(BlockType::kExtent) | |
| ExtentBlockFields::Order::Make(kNumOrders - 1), |
| "abcdefga")); |
| EXPECT_EQ(0, memcmp(blocks.find(128)->block->payload.data, contents.data(), 2040)); |
| |
| // Property b has the next 2 blocks at the beginning for its value and name, but it claims |
| // another large block for the extent. |
| |
| CompareBlock(blocks.find(3)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kPropertyValue) | |
| ValueBlockFields::NameIndex::Make(4), |
| PropertyBlockPayload::ExtentIndex::Make(256) | |
| PropertyBlockPayload::TotalLength::Make(2040) | |
| PropertyBlockPayload::Flags::Make(PropertyBlockFormat::kBinary))); |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "b\0\0\0\0\0\0\0")); |
| CompareBlock(blocks.find(256)->block, |
| MakeBlock(ExtentBlockFields::Type::Make(BlockType::kExtent) | |
| ExtentBlockFields::Order::Make(kNumOrders - 1), |
| "abcdefga")); |
| EXPECT_EQ(0, memcmp(blocks.find(128)->block->payload.data, contents.data(), 2040)); |
| } |
| |
| TEST(State, CreateMultiExtentProperty) { |
| auto vmo = MakeVmo(2 * 4096); // Need 4 pages to store 12K of properties. |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| char input[] = "abcdefg"; |
| size_t input_size = 7; |
| std::string contents; |
| for (int i = 0; i < 6000; i++) { |
| contents.push_back(input[i % input_size]); |
| } |
| StringProperty a = state->CreateStringProperty("a", 0, contents); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header (1), 1 property (2) with 3 extents (3) |
| EXPECT_EQ(1u + 2u + 3u, allocated_blocks); |
| EXPECT_EQ(6u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(2)); |
| |
| // Property a has the first 2 blocks for its value and name. |
| CompareBlock(blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kPropertyValue) | |
| ValueBlockFields::NameIndex::Make(2), |
| PropertyBlockPayload::ExtentIndex::Make(128) | |
| PropertyBlockPayload::TotalLength::Make(6000))); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| // Extents are threaded between blocks 128, 256, and 384. |
| CompareBlock(blocks.find(128)->block, |
| MakeBlock(ExtentBlockFields::Type::Make(BlockType::kExtent) | |
| ExtentBlockFields::Order::Make(kNumOrders - 1) | |
| ExtentBlockFields::NextExtentIndex::Make(256), |
| "abcdefga")); |
| EXPECT_EQ(0, memcmp(blocks.find(128)->block->payload.data, contents.data(), 2040)); |
| CompareBlock(blocks.find(256)->block, |
| MakeBlock(ExtentBlockFields::Type::Make(BlockType::kExtent) | |
| ExtentBlockFields::Order::Make(kNumOrders - 1) | |
| ExtentBlockFields::NextExtentIndex::Make(384), |
| "defgabcd")); |
| EXPECT_EQ(0, memcmp(blocks.find(256)->block->payload.data, contents.data() + 2040, 2040)); |
| CompareBlock(blocks.find(384)->block, |
| MakeBlock(ExtentBlockFields::Type::Make(BlockType::kExtent) | |
| ExtentBlockFields::Order::Make(kNumOrders - 1), |
| "gabcdefg")); |
| EXPECT_EQ(0, memcmp(blocks.find(384)->block->payload.data, contents.data() + 2 * 2040, |
| 6000 - 2 * 2040)); |
| } |
| |
| TEST(State, SetSmallStringProperty) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| StringProperty a = state->CreateStringProperty("a", 0, "Hello"); |
| |
| a.Set("World"); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header (1), 1 single extent property (3) |
| EXPECT_EQ(1u + 3u, allocated_blocks); |
| EXPECT_EQ(6u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(4)); |
| |
| // Property a fits in the first 3 blocks (value, name, extent). |
| CompareBlock(blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kPropertyValue) | |
| ValueBlockFields::NameIndex::Make(2), |
| PropertyBlockPayload::ExtentIndex::Make(3) | |
| PropertyBlockPayload::TotalLength::Make(5) | |
| PropertyBlockPayload::Flags::Make(PropertyBlockFormat::kUtf8))); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| |
| CompareBlock(blocks.find(3)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kExtent), "World\0\0\0")); |
| } |
| |
| TEST(State, SetSmallBinaryProperty) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| ByteVectorProperty a = state->CreateByteVectorProperty("a", 0, {'a', 'b', 'c', 'd'}); |
| |
| a.Set({'a', 'a', 'a', 'a'}); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header (1), 1 single extent property (3) |
| EXPECT_EQ(1u + 3u, allocated_blocks); |
| EXPECT_EQ(6u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(4)); |
| |
| // Property a fits in the first 3 blocks (value, name, extent). |
| CompareBlock(blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kPropertyValue) | |
| ValueBlockFields::NameIndex::Make(2), |
| PropertyBlockPayload::ExtentIndex::Make(3) | |
| PropertyBlockPayload::TotalLength::Make(4) | |
| PropertyBlockPayload::Flags::Make(PropertyBlockFormat::kBinary))); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| |
| CompareBlock(blocks.find(3)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kExtent), "aaaa\0\0\0\0")); |
| } |
| |
| TEST(State, SetLargeProperty) { |
| auto vmo = MakeVmo(2 * 4096); // Need space for 6K of contents. |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| char input[] = "abcdefg"; |
| size_t input_size = 7; |
| std::string contents; |
| for (int i = 0; i < 6000; i++) { |
| contents.push_back(input[i % input_size]); |
| } |
| |
| StringProperty a = state->CreateStringProperty("a", 0, contents); |
| |
| a.Set("World"); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header (1), 1 single extent property (3) |
| EXPECT_EQ(1u + 3u, allocated_blocks); |
| EXPECT_EQ(8u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(4)); |
| |
| // Property a fits in the first 3 blocks (value, name, extent). |
| CompareBlock(blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kPropertyValue) | |
| ValueBlockFields::NameIndex::Make(2), |
| PropertyBlockPayload::ExtentIndex::Make(3) | |
| PropertyBlockPayload::TotalLength::Make(5))); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| |
| CompareBlock(blocks.find(3)->block, |
| MakeBlock(ExtentBlockFields::Type::Make(BlockType::kExtent), "World\0\0\0")); |
| } |
| |
| TEST(State, SetPropertyOutOfMemory) { |
| auto vmo = MakeVmo(16 * 1024); // Only 16K of space, property will not fit. |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| std::vector<uint8_t> vec; |
| for (int i = 0; i < 65000; i++) { |
| vec.push_back('a'); |
| } |
| |
| ByteVectorProperty a = state->CreateByteVectorProperty("a", 0, vec); |
| EXPECT_FALSE(bool(a)); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header (1) only, property failed to fit. |
| EXPECT_EQ(1u, allocated_blocks); |
| EXPECT_EQ(14u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(2)); |
| } |
| |
| TEST(State, CreateNodeHierarchy) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| Node root = state->CreateNode("objects", 0); |
| auto req = root.CreateChild("requests"); |
| auto network = req.CreateUint("network", 10); |
| auto wifi = req.CreateUint("wifi", 5); |
| |
| auto version = root.CreateString("version", "1.0beta2"); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header (1), root (2), requests (2), 2 metrics (4), small property (3) |
| EXPECT_EQ(1u + 2u + 2u + 4u + 3u, allocated_blocks); |
| EXPECT_EQ(5u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(10)); |
| |
| // Root object is at index 1. |
| // It has 2 references (req and version). |
| CompareBlock( |
| blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::ParentIndex::Make(0) | ValueBlockFields::NameIndex::Make(2), |
| 2)); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(7), |
| "objects\0")); |
| |
| // Requests object is at index 3. |
| // It has 2 references (wifi and network). |
| CompareBlock( |
| blocks.find(3)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::NameIndex::Make(4), |
| 2)); |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(8), |
| "requests")); |
| |
| // Network value |
| CompareBlock( |
| blocks.find(5)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kUintValue) | |
| ValueBlockFields::ParentIndex::Make(3) | ValueBlockFields::NameIndex::Make(6), |
| 10)); |
| CompareBlock(blocks.find(6)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(7), |
| "network\0")); |
| |
| // Wifi value |
| CompareBlock( |
| blocks.find(7)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kUintValue) | |
| ValueBlockFields::ParentIndex::Make(3) | ValueBlockFields::NameIndex::Make(8), |
| 5)); |
| CompareBlock(blocks.find(8)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(4), |
| "wifi\0\0\0\0")); |
| |
| // Version property |
| CompareBlock( |
| blocks.find(9)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kPropertyValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::NameIndex::Make(10), |
| PropertyBlockPayload::ExtentIndex::Make(11) | |
| PropertyBlockPayload::TotalLength::Make(8))); |
| CompareBlock(blocks.find(10)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(7), |
| "version\0")); |
| |
| CompareBlock(blocks.find(11)->block, |
| MakeBlock(ExtentBlockFields::Type::Make(BlockType::kExtent), "1.0beta2")); |
| } |
| |
| TEST(State, TombstoneTest) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| std::unique_ptr<Node> requests; |
| { |
| // Root going out of scope causes a tombstone to be created, |
| // but since requests is referencing it it will not be deleted. |
| Node root = state->CreateNode("objects", 0); |
| requests = std::make_unique<Node>(root.CreateChild("requests")); |
| auto a = root.CreateInt("a", 1); |
| auto b = root.CreateUint("b", 1); |
| auto c = root.CreateDouble("c", 1); |
| } |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header (1), root tombstone (2), requests (2) |
| EXPECT_EQ(1u + 2u + 2u, allocated_blocks); |
| EXPECT_EQ(7u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(18)); |
| |
| // Root object is at index 1, but has been tombstoned. |
| // It has 1 reference (requests) |
| CompareBlock( |
| blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kTombstone) | |
| ValueBlockFields::ParentIndex::Make(0) | ValueBlockFields::NameIndex::Make(2), |
| 1)); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(7), |
| "objects\0")); |
| CompareBlock( |
| blocks.find(3)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::NameIndex::Make(4))); |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(8), |
| "requests")); |
| } |
| |
| TEST(State, TombstoneCleanup) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| IntProperty metric = state->CreateIntProperty("a", 0, 0); |
| |
| Node root = state->CreateNode("root", 0); |
| { |
| Node child1 = state->CreateNode("child1", 0); |
| Node child2 = child1.CreateChild("child2"); |
| |
| { |
| Node child = child1.CreateChild("this_is_a_child"); |
| std::unique_ptr<IntProperty> m; |
| { |
| Node new_child = root.CreateChild("child"); |
| m = std::make_unique<IntProperty>(new_child.CreateInt("value", -1)); |
| } |
| auto temp = child.CreateString("temp", "test"); |
| m.reset(); |
| } |
| } |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // 2 each for: |
| // metric create |
| // root create |
| // child1 create |
| // child2 create |
| // child create |
| // new_child |
| // m create |
| // new_child delete (tombstone) |
| // temp create |
| // m delete |
| // temp delete |
| // child delete |
| // child2 delete |
| // child1 delete |
| CompareBlock(blocks.find(0)->block, MakeHeader(14 * 2)); |
| |
| // Property "a" is at index 1. |
| CompareBlock(blocks.find(1)->block, |
| MakeIntBlock(ValueBlockFields::Type::Make(BlockType::kIntValue) | |
| ValueBlockFields::ParentIndex::Make(0) | |
| ValueBlockFields::NameIndex::Make(2), |
| 0)); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| |
| // Root object is at index 3. |
| // It has 0 references since the children should be removed. |
| CompareBlock( |
| blocks.find(3)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::ParentIndex::Make(0) | ValueBlockFields::NameIndex::Make(4))); |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(4), |
| "root\0\0\0\0")); |
| } |
| |
| TEST(State, LinkTest) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| // root will be at block index 1 |
| Node root = state->CreateNode("root", 0); |
| Link link = state->CreateLink("link", 1u /* root index */, "/test", LinkBlockDisposition::kChild); |
| Link link2 = |
| state->CreateLink("link2", 1u /* root index */, "/test", LinkBlockDisposition::kInline); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header (1), root (2), link (3), link2 (3) |
| EXPECT_EQ(1u + 2u + 3u + 3u, allocated_blocks); |
| EXPECT_EQ(7u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(6)); |
| |
| // Root node has 2 children. |
| CompareBlock( |
| blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::ParentIndex::Make(0) | ValueBlockFields::NameIndex::Make(2), |
| 2)); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(4), |
| "root\0\0\0\0")); |
| CompareBlock( |
| blocks.find(3)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kLinkValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::NameIndex::Make(4), |
| LinkBlockPayload::ContentIndex::Make(5))); |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(4), |
| "link\0\0\0\0")); |
| CompareBlock(blocks.find(5)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(5), |
| "/test\0\0\0")); |
| CompareBlock( |
| blocks.find(6)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kLinkValue) | |
| ValueBlockFields::ParentIndex::Make(1) | ValueBlockFields::NameIndex::Make(7), |
| LinkBlockPayload::ContentIndex::Make(8) | |
| LinkBlockPayload::Flags::Make(LinkBlockDisposition::kInline))); |
| CompareBlock(blocks.find(7)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(5), |
| "link2\0\0\0")); |
| CompareBlock(blocks.find(8)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(5), |
| "/test\0\0\0")); |
| } |
| |
| TEST(State, LinkContentsAllocationFailure) { |
| auto vmo = MakeVmo(4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| // root will be at block index 1 |
| Node root = state->CreateNode("root", 0); |
| std::string name(2000, 'a'); |
| Link link = state->CreateLink(name, 1u /* root index */, name, LinkBlockDisposition::kChild); |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| // Header (1), root (2). |
| EXPECT_EQ(1u + 2u, allocated_blocks); |
| EXPECT_EQ(7u, free_blocks); |
| |
| CompareBlock(blocks.find(0)->block, MakeHeader(4)); |
| |
| // Root node has 0 children. |
| CompareBlock( |
| blocks.find(1)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::ParentIndex::Make(0) | ValueBlockFields::NameIndex::Make(2), |
| "\0\0\0\0\0\0\0\0")); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(4), |
| "root\0\0\0\0")); |
| } |
| |
| constexpr size_t kThreadTimes = 1024 * 10; |
| |
| struct ThreadArgs { |
| IntProperty* metric; |
| uint64_t value; |
| bool add; |
| }; |
| |
| int ValueThread(void* input) { |
| auto* args = reinterpret_cast<ThreadArgs*>(input); |
| for (size_t i = 0; i < kThreadTimes; i++) { |
| if (args->add) { |
| args->metric->Add(args->value); |
| } else { |
| args->metric->Subtract(args->value); |
| } |
| } |
| |
| return 0; |
| } |
| |
| int ChildThread(void* input) { |
| Node* object = reinterpret_cast<Node*>(input); |
| for (size_t i = 0; i < kThreadTimes; i++) { |
| Node child = object->CreateChild("this_is_a_child"); |
| auto temp = child.CreateString("temp", "test"); |
| } |
| return 0; |
| } |
| |
| TEST(State, MultithreadingTest) { |
| auto vmo = MakeVmo(10 * 4096); |
| ASSERT_TRUE(!!vmo); |
| auto heap = std::make_unique<Heap>(std::move(vmo)); |
| auto state = State::Create(std::move(heap)); |
| |
| size_t per_thread_times_operation_count = 0; |
| size_t other_operation_count = 0; |
| |
| other_operation_count += 1; // create a |
| IntProperty metric = state->CreateIntProperty("a", 0, 0); |
| |
| ThreadArgs adder{.metric = &metric, .value = 2, .add = true}; |
| ThreadArgs subtractor{.metric = &metric, .value = 1, .add = false}; |
| |
| thrd_t add_thread, subtract_thread, child_thread_1, child_thread_2; |
| |
| other_operation_count += 1; // create root |
| Node root = state->CreateNode("root", 0); |
| { |
| other_operation_count += 2; // create and delete |
| Node child1 = state->CreateNode("child1", 0); |
| other_operation_count += 2; // create and delete |
| Node child2 = child1.CreateChild("child2"); |
| |
| per_thread_times_operation_count += 1; // add metric |
| thrd_create(&add_thread, ValueThread, &adder); |
| |
| per_thread_times_operation_count += 1; // subtract metric |
| thrd_create(&subtract_thread, ValueThread, &subtractor); |
| |
| per_thread_times_operation_count += 4; // create child, create temp, delete both |
| thrd_create(&child_thread_1, ChildThread, &child1); |
| per_thread_times_operation_count += 4; // create child, create temp, delete both |
| thrd_create(&child_thread_2, ChildThread, &child2); |
| |
| per_thread_times_operation_count += 4; // create child, create m, delete both; |
| for (size_t i = 0; i < kThreadTimes; i++) { |
| Node child = root.CreateChild("child"); |
| IntProperty m = child.CreateInt("value", -1); |
| } |
| thrd_join(add_thread, nullptr); |
| thrd_join(subtract_thread, nullptr); |
| thrd_join(child_thread_1, nullptr); |
| thrd_join(child_thread_2, nullptr); |
| } |
| |
| fbl::WAVLTree<BlockIndex, std::unique_ptr<ScannedBlock>> blocks; |
| size_t free_blocks, allocated_blocks; |
| auto snapshot = SnapshotAndScan(state->GetVmo(), &blocks, &free_blocks, &allocated_blocks); |
| ASSERT_TRUE(snapshot); |
| |
| CompareBlock( |
| blocks.find(0)->block, |
| MakeHeader(kThreadTimes * per_thread_times_operation_count * 2 + other_operation_count * 2)); |
| |
| // Property "a" is at index 1. |
| // Its value should be equal to kThreadTimes since subtraction |
| // should cancel out half of addition. |
| CompareBlock(blocks.find(1)->block, |
| MakeIntBlock(ValueBlockFields::Type::Make(BlockType::kIntValue) | |
| ValueBlockFields::ParentIndex::Make(0) | |
| ValueBlockFields::NameIndex::Make(2), |
| kThreadTimes)); |
| CompareBlock(blocks.find(2)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1), |
| "a\0\0\0\0\0\0\0")); |
| |
| // Root object is at index 3. |
| // It has 0 references since the children should be removed. |
| CompareBlock( |
| blocks.find(3)->block, |
| MakeBlock(ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::ParentIndex::Make(0) | ValueBlockFields::NameIndex::Make(4))); |
| CompareBlock(blocks.find(4)->block, MakeBlock(NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(4), |
| "root\0\0\0\0")); |
| } |
| |
| TEST(State, OutOfOrderDeletion) { |
| // Ensure that deleting properties after their parent does not cause a crash. |
| auto state = State::CreateWithSize(4096); |
| { |
| auto root = state->CreateRootNode(); |
| |
| inspect::StringProperty a, b, c; |
| auto base = root.CreateChild("base"); |
| c = base.CreateString("c", "test"); |
| b = base.CreateString("b", "test"); |
| a = base.CreateString("a", "test"); |
| ASSERT_TRUE(!!base); |
| ASSERT_TRUE(!!c); |
| ASSERT_TRUE(!!b); |
| ASSERT_TRUE(!!a); |
| } |
| } |
| |
| } // namespace |