/*
 * 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.

#ifndef NET_INSTAWEB_UTIL_SHARED_MEM_CACHE_DATA_H_
#define NET_INSTAWEB_UTIL_SHARED_MEM_CACHE_DATA_H_

#include <cstddef>                     // for size_t

#include "base/logging.h"
#include "net/instaweb/util/public/basictypes.h"  // for int64, int32, etc
#include "net/instaweb/util/public/scoped_ptr.h"
#include "net/instaweb/util/public/shared_mem_cache.h"
#include "net/instaweb/util/public/string.h"

namespace net_instaweb {

class AbstractMutex;
class AbstractSharedMem;
class AbstractSharedMemSegment;
class MessageHandler;

namespace SharedMemCacheData {

const BlockNum kInvalidBlock = -1;
const EntryNum kInvalidEntry = -1;
const size_t kHashSize = 16;

struct SectorStats {
  SectorStats();

  // FS operation stats --- updated by SharedMemCache. We do it
  // this way rather than using the normal Statistics interface
  // to avoid having to worry about extra synchronization inside
  // critical sections --- since we already hold sector locks
  // when doing this stuff, it's easy to update per-sector data.
  // TODO(morlovich): Consider periodically pushing these to
  // normal Statistics.
  int64 num_put;
  int64 num_put_update;  // update of the same key
  int64 num_put_replace;  // replacement of different key
  int64 num_put_concurrent_create;
  int64 num_put_concurrent_full_set;
  int64 num_put_spins;  // # of times writers had to sleep behind readers
  int64 num_get;    // # of calls to get
  int64 num_get_hit;

  // Current state stats --- updated by SharedMemCacheData
  int64 used_entries;
  int64 used_blocks;

  // Adds number to this object's. No concurrency control is done.
  void Add(const SectorStats& other);

  // Text dump of the statistics. No concurrency control is done.
  GoogleString Dump(size_t total_entries, size_t total_blocks) const;
};

struct SectorHeader {
  BlockNum free_list_front;
  EntryNum lru_list_front;
  EntryNum lru_list_rear;
  int32 padding;

  SectorStats stats;

  // mutex goes here.
};

struct CacheEntry {
  char hash_bytes[kHashSize];
  int64 last_use_timestamp_ms;
  int32 byte_size;

  // For LRU list, prev/next are kInvalidEntry to denote 'none', which
  // can apply both at endpoints and for entries not in the LRU at all,
  // due to being free.
  EntryNum lru_prev;
  EntryNum lru_next;

  BlockNum first_block;

  // When this is true, someone is trying to overwrite this entry.
  bool creating : 1;

  // Number of readers currently accessing the data.
  uint32 open_count : 31;

  uint32 padding;  // ensures we're 8-aligned.
};

// Helper for operating on a given sector's data structures; helping
// access them, lay them out in memory, and initialize them. It does not
// implement the actual cache operations, however. In particular, its
// methods affect only a single data structure at the time and do not
// do anything to preserve cross-structure invariants.
template<size_t kBlockSize>
class Sector {
 public:
  // Creates a wrapper to help operate on cache sectors in a given region of
  // memory with given geometry.  The sector should have had as much memory
  // allocated for it as returned by a call to RequiredSize with the same
  // arguments.
  //
  // Note that this doesn't do any imperative initialization; you must
  // call Initialize() in the parent process, and Attach() in child processes,
  // and check their results as well. Also, segment is assumed to be owned
  // separately, with lifetime longer than ours.
  Sector(AbstractSharedMemSegment* segment, size_t sector_offset,
         size_t cache_entries, size_t data_blocks);

  // This should be called from child processes to initialize client
  // state for the cache already formatted by a call to Initialize() in
  // the parent.
  //
  // Returns if successful (which it should be if the parent process
  // successfully create the memory and Initialize()'d it).
  bool Attach(MessageHandler* handler);

  // This should be called from the initial/parent process before the children
  // start. It initializes the data structures in this sector, including
  // mutexes. Returns true on success.
  bool Initialize(MessageHandler* handler);

  // Computes how much memory a sector will need for given number of entries.
  // Also makes sure it's padded to proper alignment.
  static size_t RequiredSize(AbstractSharedMem* shmem_runtime,
                             size_t cache_entries, size_t data_blocks);

  // Mutex ops.

  // The sector lock should be held while doing any metadata accesses.
  void Lock();
  void Unlock();

  // Block successor list ops. The should be called if sector lock held.
  // ------------------------------------------------------------
  BlockNum GetBlockSuccessor(BlockNum block) {
    DCHECK_GE(block, 0);
    DCHECK_LT(block, static_cast<BlockNum>(data_blocks_));
    return block_successors_[block];
  }

  void SetBlockSuccessor(BlockNum block, BlockNum next) {
    DCHECK_GE(block, 0);
    DCHECK_LT(block, static_cast<BlockNum>(data_blocks_));

    DCHECK_GE(next, kInvalidBlock);
    DCHECK_LT(next, static_cast<BlockNum>(data_blocks_));

    block_successors_[block] = next;
  }

  // Links blocks in the vector in order, with later blocks being
  // marked as successors of later ones.
  void LinkBlockSuccessors(const BlockVector& blocks) {
    for (size_t pos = 0; pos < blocks.size(); ++pos) {
      if (pos == (blocks.size() - 1)) {
        SetBlockSuccessor(blocks[pos], kInvalidBlock);
      } else {
        SetBlockSuccessor(blocks[pos], blocks[pos + 1]);
      }
    }
  }

  // Freelist ops.
  // ------------------------------------------------------------

  // Allocates as close to the goal blocks from freelist as it can, and
  // appends their numbers to blocks. Returns how much it allocated.
  // Does not adjust block successor lists.
  //
  // Note that this doesn't attempt to free blocks that are in use by some
  // entries.
  //
  // Assumes the lock is held.
  int AllocBlocksFromFreeList(int goal, BlockVector* blocks);

  // Puts all the passed in blocks onto this sector's freelist.
  // Does not read successors for passed in blocks, but does set them
  // for freelist membership.
  // Assumes the lock is held.
  void ReturnBlocksToFreeList(const BlockVector& blocks);

  // Cache directory ops.
  // ------------------------------------------------------------

  // Returns the given # entry.
  CacheEntry* EntryAt(EntryNum slot) {
    return reinterpret_cast<CacheEntry*>(directory_base_) + slot;
  }

  // Inserts the given entry into the LRU, at front.
  // Precondition: must not be in LRU.
  void InsertEntryIntoLRU(EntryNum entry_num);

  // Removes from the LRU. Safe to call if not in the LRU already.
  void UnlinkEntryFromLRU(EntryNum entry_num);

  EntryNum OldestEntryNum() {
    return sector_header_->lru_list_rear;
  }

  // Block ops.
  // ------------------------------------------------------------

  char* BlockBytes(BlockNum block_num) {
    return blocks_base_ + kBlockSize * block_num;
  }

  // Ops for lists of blocks corresponding to each directory entry,
  // and related size computations
  // ------------------------------------------------------------

  // Number of blocks of data needed for size blocks.
  static size_t DataBlocksForSize(size_t size) {
    return NeededPieces(size, kBlockSize);
  }

  // The # of bytes stored in block 'b' out of 'total' blocks for file of size
  // 'total_bytes'.
  // Precondition: 'total' is appropriate for 'total_bytes'.
  static size_t BytesInPortion(size_t total_bytes, size_t b, size_t total);

  // Appends the list of blocks used by the entry to 'blocks'.
  // Returns the number of items appended.
  //
  // Assumes sector lock is held.
  int BlockListForEntry(CacheEntry* entry, BlockVector* out_blocks);

  // Statistics stuff
  // ------------------------------------------------------------

  SectorStats* sector_stats() { return &sector_header_->stats; }

  // Prints out all statistics in the header (some of which are maintained
  // by the higher-level)
  void DumpStats(MessageHandler* handler);

 private:
  // Helper for doing sizing/memory layout computations.
  struct MemLayout;

  // How many piece_size pieces suffice to fit total
  static size_t NeededPieces(size_t total, size_t piece_size) {
    return (total + piece_size - 1) / piece_size;
  }

  // Configured geometry
  size_t cache_entries_;
  size_t data_blocks_;

  // Pointers to where various things are, and our sizes
  AbstractSharedMemSegment* segment_;
  scoped_ptr<AbstractMutex> mutex_;
  SectorHeader* sector_header_;
  BlockNum* block_successors_;
  char* directory_base_;
  char* blocks_base_;
  size_t sector_offset_;  // offset of the sector within the SHM segment

  DISALLOW_COPY_AND_ASSIGN(Sector);
};

}  // namespace SharedMemCacheData

}  // namespace net_instaweb

#endif  // NET_INSTAWEB_UTIL_SHARED_MEM_CACHE_DATA_H_
