Skip to content

File monitor_statistic.hpp

File List > cubrid > src > monitor > monitor_statistic.hpp

Go to the documentation of this file

/*
 * Copyright 2008 Search Solution Corporation
 * Copyright 2016 CUBRID Corporation
 *
 *  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.
 *
 */

//
// monitor_statistic.hpp - base interface for monitoring statistics
//
//  in this header can be found the most basic statistics definitions
//
//  cubmonitor statistic concept is defined by two functions: fetch and collect.
//
//      collect (change) can vary on type of statistics and is called by active transactions and other engine threads.
//      as a rule, it should be fast and non-intrusive.
//
//      fetch function returns the statistic's value. it is also the function used by monitor to fetch registered
//      statistics.
//
//  Based on type of collections, there are currently four types of statistics:
//
//      1. accumulator - values are added up in stored value.
//      2. gauge - updates stored value.
//      3. max - stores maximum value of all given values.
//      4. min - stores minimum value of all given values.
//
//  Based on statistic's collected data representation, there are three types:
//
//      1. amount (e.g. counters and other types of units).
//      2. floating (e.g. ratio, percentage).
//      3. time (all timers).
//
//  For all statistic types, an atomic way of synchronizing is also defined.
//
//  Templates are used to define each type of collector. Fully specialized statistics will each have one fetch and one
//  collect function. Fully specialized statistic are named:
//
//          valuetype_collectype[_atomic]_statistic.
//
//  So a time accumulator that is shared by many transactions and requires atomic access is called:
//
//          time_accumulator_atomic_statistic.
//
//  How to use:
//
//          // use a statistic to time execution
//          cubmonitor::time_accumulator_atomic_statistic my_timer_stat;
//
//          cubmonitor::time_point start_pt = cubmonitor::clock_type::now ();
//
//          // do some operations
//
//          cubmonitor::time_point end_pt = cubmonitor::clock_type::now ();
//          my_timer_stat.collect (end_pt - start_pt);
//          // fetch and print time value
//          std::cout << "execution time is " << my_timer_stat.fetch () << " microseconds." << std::endl;
//

#if !defined _MONITOR_STATISTIC_HPP_
#define _MONITOR_STATISTIC_HPP_

#include "monitor_definition.hpp"

#include <atomic>
#include <chrono>
#include <limits>

#include <cinttypes>

namespace cubmonitor
{
  // Statistic collected data memory representation

  // aliases for usual memory representation of statistics (and the atomic counterparts):
  // amount type
  using amount_rep = std::uint64_t;
  // floating type
  using floating_rep = double;
  // time type
  using time_rep = duration;

  statistic_value statistic_value_cast (const amount_rep &rep);
  amount_rep amount_rep_cast (statistic_value value);

  statistic_value statistic_value_cast (const floating_rep &rep);
  floating_rep floating_rep_cast (statistic_value value);

  statistic_value statistic_value_cast (const time_rep &rep);
  time_rep time_rep_cast (statistic_value value);

  // Primitive classes for basic statistics.
  //
  // Primitive interface includes fetch-able concept required for monitoring registration:
  //
  //    void fetch (statistic_value *, fetch_mode) const;   // fetch statistics (global, transaction sheets)
  //                                                        // primitives do not actually keep transaction sheets
  //    std::size_t get_statistics_count (void) const;      // statistics count; for primitive is always 1
  //
  // For statistics and collector structures:
  //
  //    Rep get_value (fetch_mode);                         // return current value
  //    void set_value (const Rep& value);                  // replace current value
  //
  // Atomic primitives will also include additional function:
  //    void compare_exchange (Rep& compare_value, const Rep& replace_value);   // atomic compare & exchange
  //
  // Rep template represents the type of data primitive can interact with (get/set type). This can be amount_rep,
  // floating_rep or time_rep.
  //

  template <typename Rep>
  class primitive
  {
    public:
      using rep = Rep;                    // alias for data representation

      primitive (Rep value = Rep ())      // constructor
    : m_value (value)
      {
    //
      }

      primitive &operator= (const primitive &other)
      {
    m_value = other.m_value;
    return *this;
      }

      // fetch interface for monitor registration - statistics count (always 1 for primitives) and fetch value function
      inline void fetch (statistic_value *destination, fetch_mode mode = FETCH_GLOBAL) const;
      std::size_t get_statistics_count (void) const
      {
    return 1;
      }

      // get current value
      // NOTE: this function is very expensive for some reason
      Rep get_value (fetch_mode mode = FETCH_GLOBAL) const
      {
    return m_value;
      }

    protected:
      // set new value
      void set_value (const Rep &value)
      {
    m_value = value;
      }

      Rep m_value;                    // stored value
  };

  template <typename Rep>
  class atomic_primitive
  {
    public:
      using rep = Rep;                    // alias for data representation

      atomic_primitive (Rep value = Rep ())      // constructor
    : m_value (value)
      {
    //
      }

      atomic_primitive &operator= (const atomic_primitive &other)
      {
    m_value.store (other.m_value.load ());
    return *this;
      }

      // fetch interface for monitor registration - statistics count (always 1 for primitives) and fetch value function
      inline void fetch (statistic_value *destination, fetch_mode mode = FETCH_GLOBAL) const;
      std::size_t get_statistics_count (void) const
      {
    return 1;
      }

      // get current value
      Rep get_value (fetch_mode mode = FETCH_GLOBAL) const
      {
    return m_value;
      }

    protected:
      // set new value
      void set_value (const Rep &value)
      {
    m_value = value;
      }

      void fetch_add (const Rep &value);

      // atomic compare & exchange
      bool compare_exchange (Rep &compare_value, const Rep &replace_value)
      {
    return m_value.compare_exchange_strong (compare_value, replace_value);
      }

      std::atomic<Rep> m_value;                    // stored value
  };

  // different specialization for time_rep because there is no such thing as atomic duration;
  // we have to store std::atomic<time_rep::rep>
  template <>
  class atomic_primitive<time_rep>
  {
    public:
      using rep = time_rep;                    // alias for data representation

      atomic_primitive (time_rep value = time_rep ())      // constructor
    : m_value (value.count ())
      {
    //
      }

      atomic_primitive &operator= (const atomic_primitive &other)
      {
    m_value.store (other.m_value.load ());
    return *this;
      }

      // fetch interface for monitor registration - statistics count (always 1 for primitives) and fetch value function
      inline void fetch (statistic_value *destination, fetch_mode mode = FETCH_GLOBAL) const;
      std::size_t get_statistics_count (void) const
      {
    return 1;
      }

      // get current value
      time_rep get_value (fetch_mode mode = FETCH_GLOBAL) const
      {
    if (mode == FETCH_GLOBAL)
      {
        return time_rep (m_value.load ());
      }
    else
      {
        return time_rep ();
      }
      }

    protected:
      // set new value
      void set_value (const time_rep &value)
      {
    m_value = value.count ();
      }

      void fetch_add (const time_rep &value)
      {
    (void) m_value.fetch_add (value.count ());
      }

      // atomic compare & exchange
      bool compare_exchange (time_rep &compare_value, const time_rep &replace_value)
      {
    time_rep::rep compare_count = compare_value.count ();
    return m_value.compare_exchange_strong (compare_count, replace_value.count ());
      }

      std::atomic<time_rep::rep> m_value;                    // stored value
  };

  // specialize
  template class primitive<amount_rep>;
  template class atomic_primitive<amount_rep>;
  template class primitive<floating_rep>;
#if defined (MONITOR_ENABLE_ATOMIC_FLOATING_REP)
  template class atomic_primitive<floating_rep>;
#endif // MONITOR_ENABLE_ATOMIC_FLOATING_REP
  template class primitive<time_rep>;
  // template class atomic_primitive<time_rep>; // differentely specialized, see above

  // Accumulator statistics
  // accumulator statistic - add change to existing value
  template<class Rep>
  class accumulator_statistic : public primitive<Rep>
  {
    public:
      using rep = Rep;                            // collected data representation

      void collect (const Rep &value);            // collect value
  };
  // accumulator atomic statistic - atomic add change to existing value
  template<class Rep>
  class accumulator_atomic_statistic : public atomic_primitive<Rep>
  {
    public:
      using rep = Rep;                            // collected data representation

      void collect (const Rep &value);            // collect value
  };

  // Gauge statistics
  // gauge statistic - replace current value with change
  template<class Rep>
  class gauge_statistic : public primitive<Rep>
  {
    public:
      using rep = Rep;                            // collected data representation

      void collect (const Rep &value);            // collect value
  };
  // gauge atomic statistic - test and set current value
  template<class Rep>
  class gauge_atomic_statistic : public atomic_primitive<Rep>
  {
    public:
      using rep = Rep;                            // collected data representation

      void collect (const Rep &value);            // collect value
  };

  // Max statistics
  // max statistic - compare with current value and set if change is bigger
  template<class Rep>
  class max_statistic : public primitive<Rep>
  {
    public:
      using rep = Rep;                            // collected data representation

      max_statistic (void);                       // constructor
      void collect (const Rep &value);            // collect value
  };
  // max atomic statistic - compare and exchange with current value if change is bigger
  template<class Rep>
  class max_atomic_statistic : public atomic_primitive<Rep>
  {
    public:
      using rep = Rep;                            // collected data representation

      max_atomic_statistic (void);                // constructor
      void collect (const Rep &value);            // collect value
  };

  // Min statistics

  // min statistic - compare with current value and set if change is smaller
  template<class Rep>
  class min_statistic : public primitive<Rep>
  {
    public:
      using rep = Rep;                            // collected data representation

      min_statistic (void);                       // constructor
      void collect (const Rep &value);
  };
  // min atomic statistic - compare and exchange with current value if change is smaller
  template<class Rep>
  class min_atomic_statistic : public atomic_primitive<Rep>
  {
    public:
      using rep = Rep;                            // collected data representation

      min_atomic_statistic (void);                // constructor
      void collect (const Rep &value);            // collect value
  };

  // specializations

  // no synchronization specializations
  using amount_accumulator_statistic = accumulator_statistic<amount_rep>;
  using floating_accumulator_statistic = accumulator_statistic<floating_rep>;
  using time_accumulator_statistic = accumulator_statistic<time_rep>;

  using amount_gauge_statistic = gauge_statistic<amount_rep>;
  using floating_gauge_statistic = gauge_statistic<floating_rep>;
  using time_gauge_statistic = gauge_statistic<time_rep>;

  using amount_max_statistic = max_statistic<amount_rep>;
  using floating_max_statistic = max_statistic<floating_rep>;
  using time_max_statistic = max_statistic<time_rep>;

  using amount_min_statistic = min_statistic<amount_rep>;
  using floating_min_statistic = min_statistic<floating_rep>;
  using time_min_statistic = min_statistic<time_rep>;

  // atomic synchronization specializations
  using amount_accumulator_atomic_statistic = accumulator_atomic_statistic<amount_rep>;
#if defined (MONITOR_ENABLE_ATOMIC_FLOATING_REP)
  using floating_accumulator_atomic_statistic = accumulator_atomic_statistic<floating_rep>;
#endif // MONITOR_ENABLE_ATOMIC_FLOATING_REP
  using time_accumulator_atomic_statistic = accumulator_atomic_statistic<time_rep>;

  using amount_gauge_atomic_statistic = gauge_atomic_statistic<amount_rep>;
#if defined (MONITOR_ENABLE_ATOMIC_FLOATING_REP)
  using floating_gauge_atomic_statistic = gauge_atomic_statistic<floating_rep>;
#endif // MONITOR_ENABLE_ATOMIC_FLOATING_REP
  using time_gauge_atomic_statistic = gauge_atomic_statistic<time_rep>;

  using amount_max_atomic_statistic = max_atomic_statistic<amount_rep>;
#if defined (MONITOR_ENABLE_ATOMIC_FLOATING_REP)
  using floating_max_atomic_statistic = max_atomic_statistic<floating_rep>;
#endif // MONITOR_ENABLE_ATOMIC_FLOATING_REP
  using time_max_atomic_statistic = max_atomic_statistic<time_rep>;

  using amount_min_atomic_statistic = min_atomic_statistic<amount_rep>;
#if defined (MONITOR_ENABLE_ATOMIC_FLOATING_REP)
  using floating_min_atomic_statistic = min_atomic_statistic<floating_rep>;
#endif // MONITOR_ENABLE_ATOMIC_FLOATING_REP
  using time_min_atomic_statistic = min_atomic_statistic<time_rep>;

  // template/inline implementation

  // class primitive

  template <typename Rep>
  void
  primitive<Rep>::fetch (statistic_value *destination, fetch_mode mode /* = FETCH_GLOBAL */) const
  {
    if (mode == FETCH_TRANSACTION_SHEET)
      {
    // no transaction sheet
    return;
      }
    *destination = statistic_value_cast (m_value);
  }

  template <typename Rep>
  void
  atomic_primitive<Rep>::fetch (statistic_value *destination, fetch_mode mode /* = FETCH_GLOBAL */) const
  {
    if (mode == FETCH_TRANSACTION_SHEET)
      {
    // no transaction sheet
    return;
      }
    *destination = statistic_value_cast (get_value ());
  }

#if defined (MONITOR_ENABLE_ATOMIC_FLOATING_REP)
  template <>
  void
  atomic_primitive<floating_rep>::fetch_add (const floating_rep &value)
  {
    // fetch_add does not work for floating_rep
    floating_rep crt_value;
    do
      {
    crt_value = m_value;
      }
    while (!compare_exchange (crt_value, crt_value + value));
  }
#endif // MONITOR_ENABLE_ATOMIC_FLOATING_REP

  template <typename Rep>
  void
  atomic_primitive<Rep>::fetch_add (const Rep &value)
  {
    m_value.fetch_add (value);
  }

  void
  atomic_primitive<time_rep>::fetch (statistic_value *destination, fetch_mode mode /* = FETCH_GLOBAL */) const
  {
    if (mode == FETCH_TRANSACTION_SHEET)
      {
    // no transaction sheet
    return;
      }
    *destination = statistic_value_cast (get_value ());
  }

  template <typename Rep>
  void
  accumulator_statistic<Rep>::collect (const Rep &value)
  {
    this->m_value += value;
  }

  template <>
  void
  accumulator_atomic_statistic<time_rep>::collect (const time_rep &value);

  template <typename Rep>
  void
  accumulator_atomic_statistic<Rep>::collect (const Rep &value)
  {
    this->m_value.fetch_add (value);
  }

  template <typename Rep>
  void
  gauge_statistic<Rep>::collect (const Rep &value)
  {
    this->set_value (value);
  }

  template <typename Rep>
  void
  gauge_atomic_statistic<Rep>::collect (const Rep &value)
  {
    this->set_value (value);
  }

  template <typename Rep>
  void
  max_statistic<Rep>::collect (const Rep &value)
  {
    if (value > this->get_value ())
      {
    this->set_value (value);
      }
  }

  template <typename Rep>
  void
  max_atomic_statistic<Rep>::collect (const Rep &value)
  {
    Rep loaded;

    // loop until either:
    // 1. current value is better
    // 2. successfully replaced value

    do
      {
    loaded = this->get_value ();
    if (loaded >= value)
      {
        // not bigger
        return;
      }
    // exchange
      }
    while (!this->compare_exchange (loaded, value));
  }

  template <typename Rep>
  void
  min_statistic<Rep>::collect (const Rep &value)
  {
    if (value < this->get_value ())
      {
    this->set_value (value);
      }
  }

  template <typename Rep>
  void
  min_atomic_statistic<Rep>::collect (const Rep &value)
  {
    Rep loaded;

    // loop until either:
    // 1. current value is better
    // 2. successfully replaced value

    do
      {
    loaded = this->get_value ();
    if (loaded <= value)
      {
        // not smaller
        return;
      }
    // try exchange
      }
    while (!compare_exchange (loaded, value));
    // exchange successful
  }

} // namespace cubmonitor

#endif // _MONITOR_STATISTIC_HPP_