blob: 574304955f2d494cc5df2a7fc9f3350f991d8565 [file] [log] [blame]
// Copyright 2011 Google Inc.
//
// 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.
//
// Author: morlovich@google.com (Maksim Orlovich)
#include "pagespeed/kernel/sharedmem/shared_mem_cache_data_test_base.h"
#include <cstddef> // for size_t
#include <set>
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/function.h"
#include "pagespeed/kernel/base/thread_annotations.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/util/platform.h"
namespace net_instaweb {
using SharedMemCacheData::BlockNum;
using SharedMemCacheData::BlockVector;
using SharedMemCacheData::CacheEntry;
using SharedMemCacheData::EntryNum;
using SharedMemCacheData::Sector;
using SharedMemCacheData::kInvalidEntry;
namespace {
const char kSegment[] = "cache";
const size_t kExtra = 8192; // we only allocate one segment,
// so we prepend these many bytes to make
// sure the offset is actually honored.
const int kBlocks = 20;
const int kEntries = 25;
} // namespace
SharedMemCacheDataTestBase::SharedMemCacheDataTestBase(SharedMemTestEnv* env)
: test_env_(env),
shmem_runtime_(env->CreateSharedMemRuntime()),
thread_system_(Platform::CreateThreadSystem()),
handler_(thread_system_->NewMutex()) {
}
bool SharedMemCacheDataTestBase::CreateChild(TestMethod method) {
Function* callback =
new MemberFunction0<SharedMemCacheDataTestBase>(method, this);
return test_env_->CreateChild(callback);
}
void SharedMemCacheDataTestBase::SanityCheckBlockVector(
const SharedMemCacheData::BlockVector& blocks, int min_valid,
int max_valid) {
// Make sure all numbers are in range, unique.
std::set<BlockNum> distinct_count;
for (size_t c = 0; c < blocks.size(); ++c) {
EXPECT_LE(min_valid, blocks[c]);
EXPECT_LE(blocks[c], max_valid);
distinct_count.insert(c);
}
EXPECT_EQ(blocks.size(), distinct_count.size());
}
void SharedMemCacheDataTestBase::ExtractAndSanityCheckLRU(
Sector<SharedMemCacheDataTestBase::kBlockSize>* sector,
std::vector<EntryNum>* out_lru) {
// collect list starting form oldest.
std::vector<EntryNum> backwards_lru;
for (EntryNum e = sector->OldestEntryNum(); e != kInvalidEntry;
e = sector->EntryAt(e)->lru_prev) {
backwards_lru.push_back(e);
}
size_t size = backwards_lru.size();
// Flip it to be from newest.
out_lru->clear();
for (size_t p = 0; p < size; ++p) {
size_t backwards_pos = size - 1 - p;
out_lru->push_back(backwards_lru[backwards_pos]);
}
// Sanity check it.
for (size_t p = 0; p < size; ++p) {
CacheEntry* entry = sector->EntryAt(out_lru->at(p));
if (p == 0) {
EXPECT_EQ(kInvalidEntry, entry->lru_prev);
} else {
EXPECT_EQ(out_lru->at(p - 1), entry->lru_prev);
}
if (p == (size - 1)) {
EXPECT_EQ(kInvalidEntry, entry->lru_next);
} else {
EXPECT_EQ(out_lru->at(p + 1), entry->lru_next);
}
}
EXPECT_EQ(out_lru->size(),
static_cast<size_t>(sector->sector_stats()->used_entries));
}
void SharedMemCacheDataTestBase::TestFreeList() NO_THREAD_SAFETY_ANALYSIS {
AbstractSharedMemSegment* seg_raw_ptr = NULL;
Sector<kBlockSize>* sector_raw_ptr = NULL;
ASSERT_TRUE(ParentInit(&seg_raw_ptr, &sector_raw_ptr));
scoped_ptr<AbstractSharedMemSegment> seg(seg_raw_ptr);
scoped_ptr<Sector<kBlockSize> > sector(sector_raw_ptr);
// Ask for more than the sector has; get exactly as many as it has.
EXPECT_EQ(0, sector->sector_stats()->used_blocks);
BlockVector blocks;
EXPECT_EQ(kBlocks, sector->AllocBlocksFromFreeList(kBlocks * 2, &blocks));
EXPECT_EQ(static_cast<size_t>(kBlocks), blocks.size());
SanityCheckBlockVector(blocks, 0, kBlocks - 1);
EXPECT_EQ(kBlocks, sector->sector_stats()->used_blocks);
// Now asking to allocate more should not return anything.
EXPECT_EQ(0, sector->AllocBlocksFromFreeList(5, &blocks));
// Should not damage previous blocks set, however.
EXPECT_EQ(static_cast<size_t>(kBlocks), blocks.size());
EXPECT_EQ(kBlocks, sector->sector_stats()->used_blocks);
// Now free blocks 0, 1, 2
blocks.clear();
blocks.push_back(0);
blocks.push_back(1);
blocks.push_back(2);
sector->ReturnBlocksToFreeList(blocks);
EXPECT_EQ(kBlocks - 3, sector->sector_stats()->used_blocks);
// Now allocate 2 blocks then 1 blocks. Should succeed, and return 3
// blocks of valid range.
blocks.clear();
EXPECT_EQ(2, sector->AllocBlocksFromFreeList(2, &blocks));
EXPECT_EQ(static_cast<size_t>(2), blocks.size());
EXPECT_EQ(kBlocks - 1, sector->sector_stats()->used_blocks);
SanityCheckBlockVector(blocks, 0, 2);
EXPECT_EQ(1, sector->AllocBlocksFromFreeList(1, &blocks));
EXPECT_EQ(static_cast<size_t>(3), blocks.size());
SanityCheckBlockVector(blocks, 0, 2);
EXPECT_EQ(kBlocks, sector->sector_stats()->used_blocks);
// Run the kid, which will free things other than 0, 1, 2.
CreateChild(&SharedMemCacheDataTestBase::TestFreeListChild);
test_env_->WaitForChildren();
EXPECT_EQ(3, sector->sector_stats()->used_blocks);
// Now try allocating this rest, appending it into blocks.
EXPECT_EQ(kBlocks - 3, sector->AllocBlocksFromFreeList(kBlocks - 3, &blocks));
EXPECT_EQ(static_cast<size_t>(kBlocks), blocks.size());
SanityCheckBlockVector(blocks, 0, kBlocks - 1);
EXPECT_EQ(kBlocks, sector->sector_stats()->used_blocks);
ParentCleanup();
}
void SharedMemCacheDataTestBase::TestFreeListChild() NO_THREAD_SAFETY_ANALYSIS {
AbstractSharedMemSegment* seg_raw_ptr = NULL;
Sector<kBlockSize>* sector_raw_ptr = NULL;
if (!ChildInit(&seg_raw_ptr, &sector_raw_ptr)) {
test_env_->ChildFailed();
}
scoped_ptr<AbstractSharedMemSegment> seg(seg_raw_ptr);
scoped_ptr<Sector<kBlockSize> > sector(sector_raw_ptr);
// Free blocks [3, kBlocks)
BlockVector to_free;
for (BlockNum b = 3; b < kBlocks; ++b) {
to_free.push_back(b);
}
sector->ReturnBlocksToFreeList(to_free);
}
void SharedMemCacheDataTestBase::TestLRU() {
AbstractSharedMemSegment* seg_raw_ptr = NULL;
Sector<kBlockSize>* sector_raw_ptr = NULL;
ASSERT_TRUE(ParentInit(&seg_raw_ptr, &sector_raw_ptr));
scoped_ptr<AbstractSharedMemSegment> seg(seg_raw_ptr);
scoped_ptr<Sector<kBlockSize> > sector(sector_raw_ptr);
// Initially, nothing should be in the LRU.
std::vector<EntryNum> lru;
ExtractAndSanityCheckLRU(sector.get(), &lru);
EXPECT_TRUE(lru.empty());
EXPECT_EQ(0, sector->sector_stats()->used_entries);
// Trying to unlink things again is fine, too.
sector->UnlinkEntryFromLRU(0);
EXPECT_EQ(0, sector->sector_stats()->used_entries);
sector->UnlinkEntryFromLRU(1);
EXPECT_EQ(0, sector->sector_stats()->used_entries);
// Now insert 5 things into the LRU manually, with entry 0 expected
// to be in front.
for (EntryNum cur = 4; cur >= 0; --cur) {
sector->InsertEntryIntoLRU(cur);
}
EXPECT_EQ(5, sector->sector_stats()->used_entries);
ExtractAndSanityCheckLRU(sector.get(), &lru);
ASSERT_EQ(5u, lru.size());
EXPECT_EQ(0, lru[0]);
EXPECT_EQ(1, lru[1]);
EXPECT_EQ(2, lru[2]);
EXPECT_EQ(3, lru[3]);
EXPECT_EQ(4, lru[4]);
// Unlink the middle, and two endpoints.
sector->UnlinkEntryFromLRU(2);
sector->UnlinkEntryFromLRU(0);
sector->UnlinkEntryFromLRU(4);
EXPECT_EQ(2, sector->sector_stats()->used_entries);
ExtractAndSanityCheckLRU(sector.get(), &lru);
ASSERT_EQ(2u, lru.size());
EXPECT_EQ(1, lru[0]);
EXPECT_EQ(3, lru[1]);
ParentCleanup();
}
void SharedMemCacheDataTestBase::TestBlockLists() NO_THREAD_SAFETY_ANALYSIS {
AbstractSharedMemSegment* seg_raw_ptr = NULL;
Sector<kBlockSize>* sector_raw_ptr = NULL;
ASSERT_TRUE(ParentInit(&seg_raw_ptr, &sector_raw_ptr));
scoped_ptr<AbstractSharedMemSegment> seg(seg_raw_ptr);
scoped_ptr<Sector<kBlockSize> > sector(sector_raw_ptr);
// First, let's sanity-check the computation routines
EXPECT_EQ(static_cast<size_t>(0),
Sector<kBlockSize>::DataBlocksForSize(0));
EXPECT_EQ(static_cast<size_t>(1),
Sector<kBlockSize>::DataBlocksForSize(1));
EXPECT_EQ(static_cast<size_t>(1),
Sector<kBlockSize>::DataBlocksForSize(kBlockSize));
EXPECT_EQ(static_cast<size_t>(2),
Sector<kBlockSize>::DataBlocksForSize(kBlockSize + 1));
EXPECT_EQ(static_cast<size_t>(2),
Sector<kBlockSize>::DataBlocksForSize(kBlockSize * 2));
EXPECT_EQ(static_cast<size_t>(3),
Sector<kBlockSize>::DataBlocksForSize(kBlockSize * 2 + 1));
EXPECT_EQ(static_cast<size_t>(1),
Sector<kBlockSize>::BytesInPortion(1, 0, 1));
EXPECT_EQ(static_cast<size_t>(kBlockSize - 1),
Sector<kBlockSize>::BytesInPortion(kBlockSize - 1, 0, 1));
EXPECT_EQ(static_cast<size_t>(kBlockSize),
Sector<kBlockSize>::BytesInPortion(kBlockSize, 0, 1));
EXPECT_EQ(static_cast<size_t>(kBlockSize),
Sector<kBlockSize>::BytesInPortion(kBlockSize + 1, 0, 2));
EXPECT_EQ(static_cast<size_t>(1),
Sector<kBlockSize>::BytesInPortion(kBlockSize + 1, 1, 2));
EXPECT_EQ(static_cast<size_t>(kBlockSize),
Sector<kBlockSize>::BytesInPortion(2 * kBlockSize, 0, 2));
EXPECT_EQ(static_cast<size_t>(kBlockSize),
Sector<kBlockSize>::BytesInPortion(2 * kBlockSize, 1, 2));
// Now, let's allocate some blocks.
const int kTestBlocks = 10;
BlockVector blocks;
ASSERT_EQ(kTestBlocks,
sector->AllocBlocksFromFreeList(kTestBlocks, &blocks));
// Link them together, and add them to a test entry.
sector->LinkBlockSuccessors(blocks);
CacheEntry* entry = sector->EntryAt(0);
entry->byte_size = kTestBlocks * kBlockSize;
entry->first_block = blocks[0];
// Hopefully BlockListForEntry will look things up right.
BlockVector extracted_blocks;
sector->BlockListForEntry(entry, &extracted_blocks);
EXPECT_EQ(blocks, extracted_blocks);
ParentCleanup();
}
bool SharedMemCacheDataTestBase::ParentInit(AbstractSharedMemSegment** out_seg,
Sector<kBlockSize>** out_sector) {
size_t bytes =
Sector<kBlockSize>::RequiredSize(shmem_runtime_.get(), kEntries, kBlocks);
AbstractSharedMemSegment* seg =
shmem_runtime_->CreateSegment(kSegment, bytes + kExtra, &handler_);
if (seg == NULL) {
return false;
}
Sector<kBlockSize>* sector =
new Sector<kBlockSize>(seg, kExtra, kEntries, kBlocks);
*out_seg = seg;
*out_sector = sector;
return sector->Initialize(&handler_);
}
bool SharedMemCacheDataTestBase::ChildInit(AbstractSharedMemSegment** out_seg,
Sector<kBlockSize>** out_sector) {
size_t bytes =
Sector<kBlockSize>::RequiredSize(shmem_runtime_.get(), kEntries, kBlocks);
AbstractSharedMemSegment* seg =
shmem_runtime_->AttachToSegment(kSegment, bytes + kExtra, &handler_);
if (seg == NULL) {
return false;
}
Sector<kBlockSize>* sector =
new Sector<kBlockSize>(seg, kExtra, kEntries, kBlocks);
*out_seg = seg;
*out_sector = sector;
return sector->Attach(&handler_);
}
void SharedMemCacheDataTestBase::ParentCleanup() {
test_env_->WaitForChildren();
shmem_runtime_->DestroySegment(kSegment, &handler_);
}
} // namespace net_instaweb