histogram.h 10.7 KB
/* ****************************************************************************
 * Copyright 2019 Open Systems Development BV                                 *
 *                                                                            *
 * Permission is hereby granted, free of charge, to any person obtaining a    *
 * copy of this software and associated documentation files (the "Software"), *
 * to deal in the Software without restriction, including without limitation  *
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,   *
 * and/or sell copies of the Software, and to permit persons to whom the      *
 * Software is furnished to do so, subject to the following conditions:       *
 *                                                                            *
 * The above copyright notice and this permission notice shall be included in *
 * all copies or substantial portions of the Software.                        *
 *                                                                            *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    *
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    *
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER        *
 * DEALINGS IN THE SOFTWARE.                                                  *
 * ***************************************************************************/
#ifndef OSDEV_COMPONENTS_MQTT_MEASUREMENT_HISTOGRAM_H
#define OSDEV_COMPONENTS_MQTT_MEASUREMENT_HISTOGRAM_H

#include <atomic>
#include <cmath>
#include <limits>
#include <mutex>
#include <string>
#include <vector>

#include "utils.h"

#include "ihistogram.h"

namespace osdev {
namespace components {
namespace mqtt {
namespace measurement {

template <typename T>
double fabs(T val)
{
    return std::fabs(val);
}

template <typename Rep, typename Dur>
double fabs(const std::chrono::duration<Rep, Dur>& dur)
{
    return std::fabs(dur.count());
}

template <typename T>
double to_double(T val)
{
    return static_cast<double>(val);
}

template <typename Rep, typename Dur>
double to_double(const std::chrono::duration<Rep, Dur>& dur)
{
    return static_cast<double>(dur.count());
}

template <typename T>
std::string to_string(T val)
{
    return std::to_string(val);
}

template <typename Rep, typename Dur>
std::string to_string(const std::chrono::duration<Rep, Dur>& dur)
{
    return std::to_string(dur.count());
}

template <typename T>
T min(T)
{
    return std::numeric_limits<T>::min();
}

template <typename Rep, typename Dur>
std::chrono::duration<Rep, Dur> min(const std::chrono::duration<Rep, Dur>&)
{
    return std::chrono::duration<Rep, Dur>::min();
}

template <typename T>
T max(T)
{
    return std::numeric_limits<T>::max();
}

template <typename Rep, typename Dur>
std::chrono::duration<Rep, Dur> max(const std::chrono::duration<Rep, Dur>&)
{
    return std::chrono::duration<Rep, Dur>::max();
}

/**
 * @brief This class implements a histogram for typed values.
 * Outlier values to the left are counted on the first bin and to the right on the last bin.
 * @tparam T The value type. T must be default constructable.
 * @tparam Buckets The number of buckets to use for this histogram. Default is 10 buckets.
 */
template <typename T, std::size_t Buckets = 10>
class Histogram : public IHistogram
{
public:
    /**
     * @brief Construct a histogram.
     * @param id The histogram identification
     * @param minValue The minimum value of the histogram.
     * @param maxValue The maxmim value of the histogram.
     * @param unit The unit of the values that are collected in this histogram.
     */
    Histogram(const std::string& id, T minValue, T maxValue, const char* unit);

    /**
     * @brief Set a value in the histogram.
     */
    void setValue(T value);

    /**
     * @return true when values are added since the last time a snapshot was taken of the histogram or a reset was performed, false otherwise.
     */
    bool isUpdated() const;

    /**
     * @brief Reset the histogram.
     * All counts are made zero and the smallest and largest recorded values are set to default.
     */
    void reset();

    // IHistogram implementation
    virtual const std::string& id() const override;
    virtual std::size_t numBuckets() const override;
    virtual double bucketWidth() const override;
    virtual std::string minValueString() const override;
    virtual std::string maxValueString() const override;
    virtual const std::string& unit() const override;
    virtual HistogramData histogramData() const override;
    virtual std::size_t numberOfValuesSinceLastClear() const override;
    virtual std::string smallestValueSinceLastClear() const override;
    virtual std::string largestValueSinceLastClear() const override;
    virtual std::string averageValueSinceLastClear() const override;
    virtual void clearRunningValues() override;
    virtual std::string toString() const override;

private:
    /**
     * @brief Get the index on which to count the given value.
     * The outliers to the left are counted on index 0 and to the right on index size() - 1.
     * @param value The value to get the index for.
     * @return the index in the histogram on which to count the given value.
     */
    inline std::size_t index(T value)
    {
        if (value > m_maxValue) {
            return m_histogram.size() - 1;
        }
        if (value < m_minValue) {
            return 0;
        }

        return 1 + static_cast<std::size_t>(to_double((value - m_minValue) / m_bucketWidth));
    }

    const std::string m_id;
    const std::string m_unit;
    const T m_minValue;
    const T m_maxValue;
    const double m_bucketWidth;
    T m_smallestValue;
    T m_largestValue;
    T m_smallestValueSinceLastClear;
    T m_largestValueSinceLastClear;
    T m_summedValueSinceLastClear;
    std::size_t m_numberOfValuesSinceLastClear;
    mutable std::mutex m_mutex;
    std::vector<std::size_t> m_histogram;
    mutable std::atomic_bool m_isUpdated;
};

template <typename T, std::size_t Buckets>
Histogram<T, Buckets>::Histogram(const std::string& _id, T minValue, T maxValue, const char* _unit)
    : m_id(_id)
    , m_unit(_unit)
    , m_minValue(minValue)
    , m_maxValue(maxValue)
    , m_bucketWidth(fabs((m_maxValue - m_minValue)) / Buckets)
    , m_smallestValue{ max(maxValue) }
    , m_largestValue{ min(maxValue) }
    , m_smallestValueSinceLastClear{ max(maxValue) }
    , m_largestValueSinceLastClear{ min(maxValue) }
    , m_summedValueSinceLastClear{}
    , m_numberOfValuesSinceLastClear{}
    , m_mutex()
    , m_histogram(Buckets + 2) // + 2 for the out of bound buckets
    , m_isUpdated(false)
{
}

template <typename T, std::size_t Buckets>
void Histogram<T, Buckets>::setValue(T value)
{
    std::lock_guard<std::mutex> lg(m_mutex);
    apply_unused_parameters(lg);

    if (value > m_largestValueSinceLastClear) {
        m_largestValueSinceLastClear = value;
    }

    if (value < m_smallestValueSinceLastClear) {
        m_smallestValueSinceLastClear = value;
    }

    if (value > m_largestValue) {
        m_largestValue = value;
    }

    if (value < m_smallestValue) {
        m_smallestValue = value;
    }

    m_summedValueSinceLastClear += value;
    ++m_numberOfValuesSinceLastClear;

    m_histogram[this->index(value)]++;
    m_isUpdated = true;
}

template <typename T, std::size_t Buckets>
bool Histogram<T, Buckets>::isUpdated() const
{
    return m_isUpdated;
}

template <typename T, std::size_t Buckets>
void Histogram<T, Buckets>::reset()
{
    std::lock_guard<std::mutex> lg(m_mutex);
    apply_unused_parameters(lg);

    m_smallestValue = T{ max(m_maxValue) };
    m_largestValue = T{ min(m_maxValue) };
    m_smallestValueSinceLastClear = T{ max(m_maxValue) };
    m_largestValueSinceLastClear = T{ min(m_maxValue) };
    m_summedValueSinceLastClear = T{};
    m_numberOfValuesSinceLastClear = 0;

    std::fill(m_histogram.begin(), m_histogram.end(), 0);
    m_isUpdated = false;
}

template <typename T, std::size_t Buckets>
const std::string& Histogram<T, Buckets>::id() const
{
    return m_id;
}

template <typename T, std::size_t Buckets>
std::size_t Histogram<T, Buckets>::numBuckets() const
{
    return Buckets;
}

template <typename T, std::size_t Buckets>
double Histogram<T, Buckets>::bucketWidth() const
{
    return m_bucketWidth;
}

template <typename T, std::size_t Buckets>
std::string Histogram<T, Buckets>::minValueString() const
{
    return to_string(m_minValue);
}

template <typename T, std::size_t Buckets>
std::string Histogram<T, Buckets>::maxValueString() const
{
    return to_string(m_maxValue);
}

template <typename T, std::size_t Buckets>
const std::string& Histogram<T, Buckets>::unit() const
{
    return m_unit;
}

template <typename T, std::size_t Buckets>
HistogramData Histogram<T, Buckets>::histogramData() const
{
    std::lock_guard<std::mutex> lg(m_mutex);
    apply_unused_parameters(lg);

    m_isUpdated = false;
    return HistogramData(to_string(m_smallestValue), to_string(m_largestValue),
        to_string(m_smallestValueSinceLastClear), to_string(m_largestValueSinceLastClear), averageValueSinceLastClear(),
        m_numberOfValuesSinceLastClear, m_histogram);
}

template <typename T, std::size_t Buckets>
std::size_t Histogram<T, Buckets>::numberOfValuesSinceLastClear() const
{
    return m_numberOfValuesSinceLastClear;
}

template <typename T, std::size_t Buckets>
std::string Histogram<T, Buckets>::smallestValueSinceLastClear() const
{
    return to_string(m_smallestValueSinceLastClear);
}

template <typename T, std::size_t Buckets>
std::string Histogram<T, Buckets>::largestValueSinceLastClear() const
{
    return to_string(m_largestValueSinceLastClear);
}

template <typename T, std::size_t Buckets>
std::string Histogram<T, Buckets>::averageValueSinceLastClear() const
{
    if (0 == m_numberOfValuesSinceLastClear) {
        return "undefined";
    }
    return to_string(to_double(m_summedValueSinceLastClear) / m_numberOfValuesSinceLastClear);
}

template <typename T, std::size_t Buckets>
void Histogram<T, Buckets>::clearRunningValues()
{
    std::lock_guard<std::mutex> lg(m_mutex);
    apply_unused_parameters(lg);

    m_smallestValueSinceLastClear = T{ max(m_maxValue) };
    m_largestValueSinceLastClear = T{ min(m_maxValue) };
    m_summedValueSinceLastClear = T{};
    m_numberOfValuesSinceLastClear = 0;
}

template <typename T, std::size_t Buckets>
std::string Histogram<T, Buckets>::toString() const
{
    return visualizeHistogram(*this);
}

}       // End namespace measurement
}       // End namespace mqtt
}       // End namespace components
}       // End namespace osdev

#endif  // OSDEV_COMPONENTS_MQTT_MEASUREMENT_HISTOGRAM_H