/*
 * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2020, 2022 SAP SE. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

#ifndef SHARE_MEMORY_METASPACE_COUNTERS_HPP
#define SHARE_MEMORY_METASPACE_COUNTERS_HPP

#include "runtime/atomic.hpp"
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"

#include <type_traits>

namespace metaspace {

// We seem to be counting a lot of things which makes it worthwhile to
// make helper classes for all that boilerplate coding.

// AbstractCounter counts something and asserts overflow and underflow.
template <class T>
class AbstractCounter {

  T _c;

  // Only allow unsigned values for now
  STATIC_ASSERT(std::is_signed<T>::value == false);

public:

  AbstractCounter() : _c(0) {}

  T get() const           { return _c; }

  void increment() { increment_by(1); }
  void decrement() { decrement_by(1); }

  void increment_by(T v) {
#ifdef ASSERT
    T old = _c;
    assert(old + v >= old,
        "overflow (" UINT64_FORMAT "+" UINT64_FORMAT ")", (uint64_t)old, (uint64_t)v);
#endif
    _c += v;
  }

  void decrement_by(T v) {
    assert(_c >= v,
           "underflow (" UINT64_FORMAT "-" UINT64_FORMAT ")",
           (uint64_t)_c, (uint64_t)v);
    _c -= v;
  }

#ifdef ASSERT
  void check(T expected) const {
    assert(_c == expected, "Counter mismatch: %d, expected: %d.",
           (int)_c, (int)expected);
    }
#endif

};

// Atomic variant of AbstractCounter.
template <class T>
class AbstractAtomicCounter {

  volatile T _c;

  // Only allow unsigned values for now
  STATIC_ASSERT(std::is_signed<T>::value == false);

public:

  AbstractAtomicCounter() : _c(0) {}

  T get() const               { return Atomic::load(&_c); }

  void increment() {
    Atomic::inc(&_c, memory_order_relaxed);
  }

  void decrement() {
    Atomic::dec(&_c, memory_order_relaxed);
  }

  void increment_by(T v) {
    Atomic::add(&_c, v, memory_order_relaxed);
  }

  void decrement_by(T v) {
    Atomic::sub(&_c, v, memory_order_relaxed);
  }

#ifdef ASSERT
  void check(T expected) const {
    assert(_c == expected, "Counter mismatch: %d, expected: %d.",
           (int)_c, (int)expected);
    }
#endif

};

typedef AbstractCounter<size_t>   SizeCounter;
typedef AbstractCounter<unsigned> IntCounter;

typedef AbstractAtomicCounter<size_t> SizeAtomicCounter;

// We often count memory ranges (blocks, chunks etc).
// Make a helper class for that.
template <class T_num, class T_size>
class AbstractMemoryRangeCounter {

  AbstractCounter<T_num>  _count;
  AbstractCounter<T_size> _total_size;

public:

  void add(T_size s) {
    if(s > 0) {
      _count.increment();
      _total_size.increment_by(s);
    }
  }

  void sub(T_size s) {
    if(s > 0) {
      _count.decrement();
      _total_size.decrement_by(s);
    }
  }

  T_num count() const       { return _count.get(); }
  T_size total_size() const { return _total_size.get(); }

#ifdef ASSERT
  void check(T_num expected_count, T_size expected_size) const {
    _count.check(expected_count);
    _total_size.check(expected_size);
  }
  void check(const AbstractMemoryRangeCounter<T_num, T_size>& other) const {
    check(other.count(), other.total_size());
  }
#endif

};

typedef AbstractMemoryRangeCounter<unsigned, size_t> MemRangeCounter;

} // namespace metaspace

#endif // SHARE_MEMORY_METASPACE_COUNTERS_HPP

