// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/*
 * Bitmap based in memory allocator.
 * Author: Ramesh Chander, Ramesh.Chander@sandisk.com
 */

#ifndef  CEPH_OS_BLUESTORE_BITALLOCATOR_H
#define CEPH_OS_BLUESTORE_BITALLOCATOR_H


#include <assert.h>
#include <stdint.h>
#include <pthread.h>
#include <mutex>
#include <atomic>
#include <vector>
#include "include/intarith.h"
#include "os/bluestore/bluestore_types.h"

#define alloc_assert assert

#ifdef BIT_ALLOCATOR_DEBUG
#define alloc_dbg_assert(x) assert(x)
#else
#define alloc_dbg_assert(x) (static_cast<void> (0))
#endif


class BitAllocatorStats {
public:
  std::atomic<int64_t> m_total_alloc_calls;
  std::atomic<int64_t> m_total_free_calls;
  std::atomic<int64_t> m_total_allocated;
  std::atomic<int64_t> m_total_freed;
  std::atomic<int64_t> m_total_serial_scans;
  std::atomic<int64_t> m_total_concurrent_scans;
  std::atomic<int64_t> m_total_node_scanned;

  BitAllocatorStats() {
    m_total_alloc_calls = 0;
    m_total_free_calls = 0;
    m_total_allocated = 0;
    m_total_freed = 0;
    m_total_serial_scans = 0;
    m_total_concurrent_scans = 0;
    m_total_node_scanned = 0;
  }

  void add_alloc_calls(int64_t val) {
    std::atomic_fetch_add(&m_total_alloc_calls, val);
  }
  void add_free_calls(int64_t val) {
    std::atomic_fetch_add(&m_total_free_calls, val);
  }
  void add_allocated(int64_t val) {
    std::atomic_fetch_add(&m_total_allocated, val);
  }
  void add_freed(int64_t val) {
    std::atomic_fetch_add(&m_total_freed, val);
  }
  void add_serial_scans(int64_t val) {
    std::atomic_fetch_add(&m_total_serial_scans, val);
  }
  void add_concurrent_scans(int64_t val) {
    std::atomic_fetch_add(&m_total_concurrent_scans, val);
  }
  void add_node_scanned(int64_t val) {
    std::atomic_fetch_add(&m_total_node_scanned, val);
  }
};

template <class BitMapEntity>
class BitMapEntityIter {
  typedef mempool::bluestore_alloc::vector<BitMapEntity> BitMapEntityVector;
  BitMapEntityVector *m_list;
  int64_t m_start_idx;
  int64_t m_cur_idx;
  bool m_wrap;
  bool m_wrapped;
  bool m_end;
public:

  void init(BitMapEntityVector *list, bool wrap, int64_t start_idx) {
    m_list = list;
    m_wrap = wrap;
    m_start_idx = start_idx;
    m_cur_idx = m_start_idx;
    m_wrapped = false;
    m_end = false;
  }

  BitMapEntityIter(BitMapEntityVector *list, int64_t start_idx) {
    init(list, false, start_idx);
  }
  BitMapEntityIter(BitMapEntityVector *list, int64_t start_idx, bool wrap) {
    init(list, wrap, start_idx);
  }

  BitMapEntity *next() {
    int64_t cur_idx = m_cur_idx;

    if (m_wrapped &&
      cur_idx == m_start_idx) {
      /*
       * End of wrap cycle + 1
       */
      if (!m_end) {
        m_end = true;
        return &(*m_list)[cur_idx];
      }
      return NULL;
    }
    m_cur_idx++;

    if (m_cur_idx == (int64_t)m_list->size() &&
        m_wrap) {
      m_cur_idx = 0;
      m_wrapped = true;
    }

    if (cur_idx == (int64_t)m_list->size()) {
      /*
       * End of list
       */
      return NULL;
    }

    alloc_assert(cur_idx < (int64_t)m_list->size());
    return &(*m_list)[cur_idx];
  }

  int64_t index() {
    return m_cur_idx;
  }
};

typedef unsigned long bmap_t;
typedef mempool::bluestore_alloc::vector<bmap_t> bmap_mask_vec_t;

class BmapEntry {
private:
  bmap_t m_bits;

public:
  MEMPOOL_CLASS_HELPERS();
  static bmap_t full_bmask() {
    return (bmap_t) -1;
  }
  static int64_t size() {
    return (sizeof(bmap_t) * 8);
  }
  static bmap_t empty_bmask() {
    return (bmap_t) 0;
  }
  static bmap_t align_mask(int x) {
    return ((x) >= BmapEntry::size()? (bmap_t) -1 : (~(((bmap_t) -1) >> (x))));
  }
  static bmap_t bit_mask(int bit_num) {
    return (bmap_t) 0x1 << ((BmapEntry::size() - 1) - bit_num);
  }
  bmap_t atomic_fetch() {
    return m_bits;
  }
  BmapEntry(CephContext*, bool val);
  BmapEntry(CephContext*) {
    m_bits = 0;
  }
  BmapEntry(const BmapEntry& bmap) {
    m_bits = bmap.m_bits;
  }

  void clear_bit(int bit);
  void clear_bits(int offset, int num_bits);
  void set_bits(int offset, int num_bits);
  bool check_n_set_bit(int bit);
  bool check_bit(int bit);
  bool is_allocated(int64_t start_bit, int64_t num_bits);

  int find_n_cont_bits(int start_offset, int64_t num_bits);
  int find_n_free_bits(int start_idx, int64_t max_bits,
           int *free_bit, int *end_idx);
  int find_first_set_bits(int64_t required_blocks, int bit_offset,
          int *start_offset, int64_t *scanned);

  void dump_state(CephContext* cct, const int& count);
  ~BmapEntry();

};

class BitMapArea {
protected:
  int16_t m_area_index;

public:
  MEMPOOL_CLASS_HELPERS();
  static int64_t get_zone_size(CephContext* cct);
  static int64_t get_span_size(CephContext* cct);
  static int get_level(CephContext* cct, int64_t total_blocks);
  static int64_t get_level_factor(CephContext* cct, int level);
  virtual bool is_allocated(int64_t start_block, int64_t num_blocks) = 0;
  virtual bool is_exhausted() = 0;
  virtual bool child_check_n_lock(BitMapArea *child, int64_t required) {
      ceph_abort();
      return true;
  }
  virtual bool child_check_n_lock(BitMapArea *child, int64_t required, bool lock) {
      ceph_abort();
      return true;
  }
  virtual void child_unlock(BitMapArea *child) {
    ceph_abort();
  }

  virtual void lock_excl() = 0;
  virtual bool lock_excl_try() {
    ceph_abort();
    return false;
  }
  virtual void lock_shared() {
    ceph_abort();
    return;
  }
  virtual void unlock() = 0;

  virtual int64_t sub_used_blocks(int64_t num_blocks) = 0;
  virtual int64_t add_used_blocks(int64_t num_blocks) = 0;
  virtual bool reserve_blocks(int64_t num_blocks) = 0;
  virtual void unreserve(int64_t num_blocks, int64_t allocated) = 0;
  virtual int64_t get_reserved_blocks() = 0;
  virtual int64_t get_used_blocks() = 0;

  virtual void shutdown() = 0;

  virtual int64_t alloc_blocks_dis(int64_t num_blocks, int64_t min_alloc,
             int64_t hint, int64_t blk_off, ExtentList *block_list) {
    ceph_abort();
    return 0;
  }

  virtual void set_blocks_used(int64_t start_block, int64_t num_blocks) = 0;
  virtual void free_blocks(int64_t start_block, int64_t num_blocks) = 0;
  virtual int64_t size() = 0;

  int64_t child_count();
  int64_t get_index();
  int64_t get_level();
  virtual void dump_state(CephContext* cct, int& count) = 0;
  BitMapArea(CephContext*) { }
  virtual ~BitMapArea() { }
};

class BitMapAreaList {

private:
  std::vector<BitMapArea*> m_items;

public:
  /* Must be DefaultConstructible as BitMapAreaIN and derivates employ
   * a deferred init, sorry. */
  BitMapAreaList() = default;

  BitMapAreaList(std::vector<BitMapArea*>&& m_items)
    : m_items(std::move(m_items)) {
  }

  BitMapArea *get_nth_item(const int64_t idx) {
    return m_items[idx];
  }

  /* FIXME: we really should use size_t. */
  int64_t size() const {
    return m_items.size();
  }
};

/* Intensionally inlined for the sake of BitMapAreaLeaf::alloc_blocks_dis_int. */
class BmapEntityListIter {
  BitMapAreaList* m_list;
  int64_t m_start_idx;
  int64_t m_cur_idx;
  bool m_wrap;
  bool m_wrapped;
  bool m_end;

public:
  BmapEntityListIter(BitMapAreaList* const list,
                     const int64_t start_idx,
                     const bool wrap = false)
    : m_list(list),
      m_start_idx(start_idx),
      m_cur_idx(start_idx),
      m_wrap(wrap),
      m_wrapped(false),
      m_end(false) {
  }

  BitMapArea* next() {
    int64_t cur_idx = m_cur_idx;

    if (m_wrapped &&
      cur_idx == m_start_idx) {
      /*
       * End of wrap cycle + 1
       */
      if (!m_end) {
        m_end = true;
        return m_list->get_nth_item(cur_idx);
      }
      return NULL;
    }
    m_cur_idx++;

    if (m_cur_idx == m_list->size() &&
        m_wrap) {
      m_cur_idx = 0;
      m_wrapped = true;
    }
    if (cur_idx == m_list->size()) {
      /*
       * End of list
       */
      return NULL;
    }

    /* This method should be *really* fast as it's being executed over
     * and over during traversal of allocators indexes. */
    alloc_dbg_assert(cur_idx < m_list->size());
    return m_list->get_nth_item(cur_idx);
  }

  int64_t index();
};

typedef mempool::bluestore_alloc::vector<BmapEntry> BmapEntryVector;

class BitMapZone: public BitMapArea {

private:
  std::atomic<int32_t> m_used_blocks;
  BmapEntryVector m_bmap_vec;
  std::mutex m_lock;

public:
  MEMPOOL_CLASS_HELPERS();
  static int64_t count;
  static int64_t total_blocks;
  static void incr_count() { count++;}
  static int64_t get_total_blocks() {return total_blocks;}
  bool is_allocated(int64_t start_block, int64_t num_blocks) override;
  bool is_exhausted() override final;
  void reset_marker();

  int64_t sub_used_blocks(int64_t num_blocks) override;
  int64_t add_used_blocks(int64_t num_blocks) override;
  bool reserve_blocks(int64_t num_blocks) override;
  void unreserve(int64_t num_blocks, int64_t allocated) override;
  int64_t get_reserved_blocks() override;
  int64_t get_used_blocks() override final;
  int64_t size() override final {
    return get_total_blocks();
  }

  void lock_excl() override;
  bool lock_excl_try() override;
  void unlock() override;
  bool check_locked();

  void free_blocks_int(int64_t start_block, int64_t num_blocks);
  void init(CephContext* cct, int64_t zone_num, int64_t total_blocks, bool def);

  BitMapZone(CephContext* cct, int64_t total_blocks, int64_t zone_num);
  BitMapZone(CephContext* cct, int64_t total_blocks, int64_t zone_num, bool def);

  ~BitMapZone() override;
  void shutdown() override;
  int64_t alloc_blocks_dis(int64_t num_blocks, int64_t min_alloc, int64_t hint,
        int64_t blk_off, ExtentList *block_list) override;  
  void set_blocks_used(int64_t start_block, int64_t num_blocks) override;

  void free_blocks(int64_t start_block, int64_t num_blocks) override;
  void dump_state(CephContext* cct, int& count) override;
};

class BitMapAreaIN: public BitMapArea{

protected:
  int64_t m_child_size_blocks;
  int64_t m_total_blocks;
  int16_t m_level;

  int64_t m_used_blocks;
  int64_t m_reserved_blocks;
  std::mutex m_blocks_lock;
  BitMapAreaList m_child_list;

  bool is_allocated(int64_t start_block, int64_t num_blocks) override;
  bool is_exhausted() override;

  bool child_check_n_lock(BitMapArea *child, int64_t required, bool lock) override {
    ceph_abort();
    return false;
  }

  bool child_check_n_lock(BitMapArea *child, int64_t required) override;
  void child_unlock(BitMapArea *child) override;

  void lock_excl() override {
    return;
  }
  void lock_shared() override {
    return;
  }
  void unlock() override {
    return;
  }

  void init(CephContext* cct, int64_t total_blocks, int64_t zone_size_block, bool def);
  void init_common(CephContext* cct,
                   int64_t total_blocks,
                   int64_t zone_size_block,
                   bool def);
  int64_t alloc_blocks_dis_int_work(bool wrap, int64_t num_blocks, int64_t min_alloc, int64_t hint,
        int64_t blk_off, ExtentList *block_list);  

  int64_t alloc_blocks_int_work(bool wait, bool wrap,
                         int64_t num_blocks, int64_t hint, int64_t *start_block);

public:
  MEMPOOL_CLASS_HELPERS();
  BitMapAreaIN(CephContext* cct);
  BitMapAreaIN(CephContext* cct, int64_t zone_num, int64_t total_blocks);
  BitMapAreaIN(CephContext* cct, int64_t zone_num, int64_t total_blocks,
	       bool def);

  ~BitMapAreaIN() override;
  void shutdown() override;
  int64_t sub_used_blocks(int64_t num_blocks) override;
  int64_t add_used_blocks(int64_t num_blocks) override;
  bool reserve_blocks(int64_t num_blocks) override;
  void unreserve(int64_t num_blocks, int64_t allocated) override;
  int64_t get_reserved_blocks() override;
  int64_t get_used_blocks() override;
  virtual int64_t get_used_blocks_adj();
  int64_t size() override {
    return m_total_blocks;
  }
  using BitMapArea::alloc_blocks_dis; //non-wait version

  virtual int64_t alloc_blocks_dis_int(int64_t num_blocks, int64_t min_alloc, int64_t hint,
                                       int64_t blk_off, ExtentList *block_list);  
  int64_t alloc_blocks_dis(int64_t num_blocks, int64_t min_alloc, int64_t hint,
                           int64_t blk_off, ExtentList *block_list) override;  
  virtual void set_blocks_used_int(int64_t start_block, int64_t num_blocks);
  void set_blocks_used(int64_t start_block, int64_t num_blocks) override;

  virtual void free_blocks_int(int64_t start_block, int64_t num_blocks);
  void free_blocks(int64_t start_block, int64_t num_blocks) override;
  void dump_state(CephContext* cct, int& count) override;
};

class BitMapAreaLeaf: public BitMapAreaIN{

private:
  void init(CephContext* cct, int64_t total_blocks, int64_t zone_size_block,
            bool def);

public:
  MEMPOOL_CLASS_HELPERS();
  static int64_t count;
  static void incr_count() { count++;}
  BitMapAreaLeaf(CephContext* cct) : BitMapAreaIN(cct) { }
  BitMapAreaLeaf(CephContext* cct, int64_t zone_num, int64_t total_blocks);
  BitMapAreaLeaf(CephContext* cct, int64_t zone_num, int64_t total_blocks,
		 bool def);

  using BitMapAreaIN::child_check_n_lock;
  bool child_check_n_lock(BitMapArea *child, int64_t required) override {
    ceph_abort();
    return false;
  }

  bool child_check_n_lock(BitMapZone* child, int64_t required, bool lock);

  int64_t alloc_blocks_int(int64_t num_blocks, int64_t hint, int64_t *start_block);
  int64_t alloc_blocks_dis_int(int64_t num_blocks, int64_t min_alloc, int64_t hint,
        int64_t blk_off, ExtentList *block_list) override;  
  void free_blocks_int(int64_t start_block, int64_t num_blocks) override;

  ~BitMapAreaLeaf() override;
};


typedef enum bmap_alloc_mode {
  SERIAL = 1,
  CONCURRENT = 2,
} bmap_alloc_mode_t;

class BitAllocator:public BitMapAreaIN{
private:
  CephContext* const cct;
  bmap_alloc_mode_t m_alloc_mode;
  std::mutex m_serial_mutex;
  pthread_rwlock_t m_rw_lock;
  BitAllocatorStats *m_stats;
  bool m_is_stats_on;
  int64_t m_extra_blocks;

  bool is_stats_on() {
    return m_is_stats_on;
  }

  using BitMapArea::child_check_n_lock;
  bool child_check_n_lock(BitMapArea *child, int64_t required) override;
  void child_unlock(BitMapArea *child) override;

  void serial_lock();
  bool try_serial_lock();
  void serial_unlock();
  void lock_excl() override;
  void lock_shared() override;
  bool try_lock();
  void unlock() override;

  bool check_input(int64_t num_blocks);
  bool check_input_dis(int64_t num_blocks);
  void init_check(int64_t total_blocks, int64_t zone_size_block,
                 bmap_alloc_mode_t mode, bool def, bool stats_on);
  int64_t alloc_blocks_dis_work(int64_t num_blocks, int64_t min_alloc, int64_t hint, ExtentList *block_list, bool reserved);

  int64_t alloc_blocks_dis_int(int64_t num_blocks, int64_t min_alloc, 
           int64_t hint, int64_t area_blk_off, ExtentList *block_list) override;

public:
  MEMPOOL_CLASS_HELPERS();

  BitAllocator(CephContext* cct, int64_t total_blocks,
	       int64_t zone_size_block, bmap_alloc_mode_t mode);
  BitAllocator(CephContext* cct, int64_t total_blocks, int64_t zone_size_block,
	       bmap_alloc_mode_t mode, bool def);
  BitAllocator(CephContext* cct, int64_t total_blocks, int64_t zone_size_block,
               bmap_alloc_mode_t mode, bool def, bool stats_on);
  ~BitAllocator() override;
  void shutdown() override;
  using BitMapAreaIN::alloc_blocks_dis; //Wait version

  void free_blocks(int64_t start_block, int64_t num_blocks) override;
  void set_blocks_used(int64_t start_block, int64_t num_blocks) override;
  void unreserve_blocks(int64_t blocks);

  int64_t alloc_blocks_dis_res(int64_t num_blocks, int64_t min_alloc, int64_t hint, ExtentList *block_list);

  void free_blocks_dis(int64_t num_blocks, ExtentList *block_list);
  bool is_allocated_dis(ExtentList *blocks, int64_t num_blocks);

  int64_t total_blocks() const {
    return m_total_blocks - m_extra_blocks;
  }
  int64_t get_used_blocks() override {
    return (BitMapAreaIN::get_used_blocks_adj() - m_extra_blocks);
  }

  BitAllocatorStats *get_stats() {
      return m_stats;
  }
  void dump();
};

#endif //End of file
