| /* |
| * 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) |
| |
| // Data structure operation helpers for SharedMemCache. See the top of |
| // shared_mem_cache.cc for data format descriptions. |
| |
| #include "pagespeed/kernel/sharedmem/shared_mem_cache_data.h" |
| |
| #include "base/logging.h" |
| #include "pagespeed/kernel/base/abstract_mutex.h" |
| #include "pagespeed/kernel/base/abstract_shared_mem.h" |
| #include "pagespeed/kernel/base/message_handler.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| |
| namespace net_instaweb { |
| |
| namespace SharedMemCacheData { |
| |
| namespace { |
| // Assumes align is power-of-2 |
| inline size_t AlignTo(size_t alignment, size_t in) { |
| return (in + (alignment - 1)) & ~(alignment - 1); |
| } |
| |
| double percent(int64 portion, int64 total) { |
| return static_cast<double>(portion) / static_cast<double>(total) * 100.0; |
| } |
| |
| } // namespace |
| |
| template<size_t kBlockSize> |
| struct Sector<kBlockSize>::MemLayout { |
| MemLayout(size_t mutex_size, size_t cache_entries, size_t data_blocks) { |
| // Check out alignment assumptions -- everything must be of a size |
| // that's multiple of 8. The exact sizes don't matter too much, but |
| // we check it anyway to avoid surprises. |
| CHECK_EQ(96u, sizeof(SectorHeader)); |
| CHECK_EQ(48u, sizeof(CacheEntry)); |
| |
| header_bytes = AlignTo(8, sizeof(SectorHeader) + mutex_size); |
| block_successor_list_bytes = |
| AlignTo(8, sizeof(BlockNum) * data_blocks); |
| size_t directory_size = sizeof(CacheEntry) * cache_entries; |
| metadata_bytes = |
| AlignTo(kBlockSize, |
| header_bytes + directory_size + block_successor_list_bytes); |
| } |
| |
| size_t header_bytes; // also offset to the block successor list. |
| size_t block_successor_list_bytes; |
| size_t metadata_bytes; // e.g. offset to the blocks. |
| }; |
| |
| template<size_t kBlockSize> |
| Sector<kBlockSize>::Sector(AbstractSharedMemSegment* segment, |
| size_t sector_offset, size_t cache_entries, |
| size_t data_blocks) |
| : cache_entries_(cache_entries), |
| data_blocks_(data_blocks), |
| segment_(segment), |
| sector_offset_(sector_offset) { |
| MemLayout layout(segment->SharedMutexSize(), cache_entries, data_blocks); |
| char* base = const_cast<char*>(segment->Base()) + sector_offset; |
| sector_header_ = reinterpret_cast<SectorHeader*>(base); |
| block_successors_ = reinterpret_cast<BlockNum*>(base + layout.header_bytes); |
| directory_base_ = |
| base + layout.header_bytes + layout.block_successor_list_bytes; |
| blocks_base_ = base + layout.metadata_bytes; |
| } |
| |
| template<size_t kBlockSize> |
| Sector<kBlockSize>::~Sector() { |
| } |
| |
| template<size_t kBlockSize> |
| bool Sector<kBlockSize>::Attach(MessageHandler* handler) { |
| mutex_.reset( |
| segment_->AttachToSharedMutex(sector_offset_ + sizeof(SectorHeader))); |
| return (mutex_.get() != NULL); |
| } |
| |
| template<size_t kBlockSize> |
| bool Sector<kBlockSize>::Initialize(MessageHandler* handler) |
| NO_THREAD_SAFETY_ANALYSIS { |
| if (!segment_->InitializeSharedMutex(sector_offset_ + sizeof(SectorHeader), |
| handler)) { |
| return false; |
| } |
| |
| // Connect to the mutex, now it's created. |
| if (!Attach(handler)) { |
| return false; |
| } |
| |
| // Initialize the LRU and the cache entry. |
| sector_header_->lru_list_front = kInvalidEntry; |
| sector_header_->lru_list_rear = kInvalidEntry; |
| for (size_t c = 0; c < cache_entries_; ++c) { |
| CacheEntry* entry = EntryAt(c); |
| entry->lru_prev = kInvalidEntry; |
| entry->lru_next = kInvalidEntry; |
| entry->first_block = kInvalidBlock; |
| } |
| |
| // Initialize the freelist and block successor list. |
| sector_header_->free_list_front = kInvalidBlock; |
| BlockVector all_blocks; |
| for (size_t c = 0; c < data_blocks_; ++c) { |
| all_blocks.push_back(static_cast<BlockNum>(c)); |
| } |
| ReturnBlocksToFreeList(all_blocks); |
| sector_header_->stats.used_blocks = 0; |
| |
| return true; |
| } |
| |
| template<size_t kBlockSize> |
| size_t Sector<kBlockSize>::RequiredSize(AbstractSharedMem* shmem_runtime, |
| size_t cache_entries, |
| size_t data_blocks) { |
| MemLayout layout(shmem_runtime->SharedMutexSize(), cache_entries, |
| data_blocks); |
| return layout.metadata_bytes + data_blocks * kBlockSize; |
| } |
| |
| template<size_t kBlockSize> |
| int Sector<kBlockSize>::AllocBlocksFromFreeList(int goal, |
| BlockVector* blocks) { |
| int allocated = 0; |
| while ((allocated < goal) && |
| (sector_header_->free_list_front != kInvalidBlock)) { |
| int block_num = sector_header_->free_list_front; |
| sector_header_->free_list_front = GetBlockSuccessor(block_num); |
| blocks->push_back(block_num); |
| ++allocated; |
| } |
| sector_header_->stats.used_blocks += allocated; |
| return allocated; |
| } |
| |
| template<size_t kBlockSize> |
| void Sector<kBlockSize>::ReturnBlocksToFreeList(const BlockVector& blocks) { |
| for (size_t c = 0; c < blocks.size(); ++c) { |
| BlockNum block_num = blocks[c]; |
| SetBlockSuccessor(block_num, sector_header_->free_list_front); |
| sector_header_->free_list_front = block_num; |
| } |
| sector_header_->stats.used_blocks -= blocks.size(); |
| } |
| |
| template<size_t kBlockSize> |
| void Sector<kBlockSize>::InsertEntryIntoLRU(EntryNum entry_num) { |
| CacheEntry* entry = EntryAt(entry_num); |
| CHECK((entry->lru_prev == kInvalidEntry) && |
| (entry->lru_next == kInvalidEntry)); |
| ++sector_header_->stats.used_entries; |
| entry->lru_next = sector_header_->lru_list_front; |
| if (entry->lru_next == kInvalidEntry) { |
| sector_header_->lru_list_rear = entry_num; |
| } else { |
| EntryAt(entry->lru_next)->lru_prev = entry_num; |
| } |
| sector_header_->lru_list_front = entry_num; |
| } |
| |
| template<size_t kBlockSize> |
| void Sector<kBlockSize>::UnlinkEntryFromLRU(int entry_num) { |
| CacheEntry* entry = EntryAt(entry_num); |
| |
| // TODO(morlovich): again, perhaps a bit too much work for stats. |
| if (entry->lru_next != kInvalidEntry || |
| entry->lru_prev != kInvalidEntry || |
| sector_header_->lru_list_front == entry_num) { |
| --sector_header_->stats.used_entries; |
| } |
| |
| // Update successor or rear pointer. |
| if (entry->lru_next == kInvalidEntry) { |
| // Either at end or not linked-in at all. |
| if (entry_num == sector_header_->lru_list_rear) { |
| sector_header_->lru_list_rear = entry->lru_prev; |
| } |
| } else { |
| EntryAt(entry->lru_next)->lru_prev = entry->lru_prev; |
| } |
| |
| // Update predecessor or front pointer. |
| if (entry->lru_prev == kInvalidEntry) { |
| // Front or not linked-in at all. |
| if (entry_num == sector_header_->lru_list_front) { |
| sector_header_->lru_list_front = entry->lru_next; |
| } |
| } else { |
| EntryAt(entry->lru_prev)->lru_next = entry->lru_next; |
| } |
| |
| // clear own links. |
| entry->lru_prev = kInvalidEntry; |
| entry->lru_next = kInvalidEntry; |
| } |
| |
| template<size_t kBlockSize> |
| size_t Sector<kBlockSize>::BytesInPortion(size_t total_bytes, size_t b, |
| size_t total) { |
| if (b != (total -1)) { |
| return kBlockSize; |
| } else { |
| size_t rem = total_bytes % kBlockSize; |
| return (rem == 0) ? kBlockSize : rem; |
| } |
| } |
| |
| template<size_t kBlockSize> |
| int Sector<kBlockSize>::BlockListForEntry(CacheEntry* entry, |
| BlockVector* out_blocks) { |
| int data_blocks = DataBlocksForSize(entry->byte_size); |
| |
| BlockNum block = entry->first_block; |
| for (int d = 0; d < data_blocks; ++d) { |
| DCHECK_LE(0, block); |
| DCHECK_LT(block, static_cast<BlockNum>(data_blocks_)); |
| out_blocks->push_back(block); |
| block = GetBlockSuccessor(block); |
| } |
| DCHECK_EQ(block, kInvalidBlock); |
| |
| return data_blocks; |
| } |
| |
| SectorStats::SectorStats() |
| : num_put(0), |
| num_put_update(0), |
| num_put_replace(0), |
| num_put_concurrent_create(0), |
| num_put_concurrent_full_set(0), |
| num_put_spins(0), |
| num_get(0), |
| num_get_hit(0), |
| used_entries(0), |
| used_blocks(0) { |
| } |
| |
| void SectorStats::Add(const SectorStats& other) { |
| num_put += other.num_put; |
| num_put_update += other.num_put_update; |
| num_put_replace += other.num_put_replace; |
| num_put_concurrent_create += other.num_put_concurrent_create; |
| num_put_concurrent_full_set += other.num_put_concurrent_full_set; |
| num_put_spins += other.num_put_spins; |
| num_get += other.num_get; |
| num_get_hit += other.num_get_hit; |
| used_entries += other.used_entries; |
| used_blocks += other.used_blocks; |
| } |
| |
| GoogleString SectorStats::Dump(size_t total_entries, |
| size_t total_blocks) const { |
| GoogleString out; |
| StringAppendF(&out, "Total put operations: %s\n", |
| Integer64ToString(num_put).c_str()); |
| StringAppendF(&out, " updating an existing key: %s\n", |
| Integer64ToString(num_put_update).c_str()); |
| StringAppendF(&out, " replace/conflict miss: %s\n", |
| Integer64ToString(num_put_replace).c_str()); |
| StringAppendF( |
| &out, " simultaneous same-key insert: %s\n", |
| Integer64ToString(num_put_concurrent_create).c_str()); |
| StringAppendF( |
| &out, " dropped since all of associativity set locked: %s\n", |
| Integer64ToString(num_put_concurrent_full_set).c_str()); |
| StringAppendF( |
| &out, " spinning sleeps performed by writers: %s\n", |
| Integer64ToString(num_put_spins).c_str()); |
| |
| StringAppendF(&out, "Total get operations: %s\n", |
| Integer64ToString(num_get).c_str()); |
| StringAppendF(&out, " hits: %s (%.2f%%)\n", |
| Integer64ToString(num_get_hit).c_str(), |
| percent(num_get_hit, num_get)); |
| |
| StringAppendF(&out, "Entries used: %s (%.2f%%)\n", |
| Integer64ToString(used_entries).c_str(), |
| percent(used_entries, total_entries)); |
| StringAppendF(&out, "Blocks used: %s (%.2f%%)\n", |
| Integer64ToString(used_blocks).c_str(), |
| percent(used_blocks, total_blocks)); |
| return out; |
| } |
| |
| template<size_t kBlockSize> |
| void Sector<kBlockSize>::DumpStats(MessageHandler* handler) { |
| mutex()->Lock(); |
| GoogleString dump = sector_stats()->Dump(cache_entries_, data_blocks_); |
| mutex()->Unlock(); |
| handler->MessageS(kError, dump); |
| } |
| |
| template class Sector<64>; |
| template class Sector<512>; |
| template class Sector<4096>; |
| |
| } // namespace SharedMemCacheData |
| |
| } // namespace net_instaweb |