// balm_collector.h                                                   -*-C++-*-
#ifndef INCLUDED_BALM_COLLECTOR
#define INCLUDED_BALM_COLLECTOR

#include <bsls_ident.h>
BSLS_IDENT("$Id: balm_collector.h,v 1.7 2008/04/16 20:00:49 hversche Exp $")

//@PURPOSE: Provide a container for collecting and aggregating metric values.
//
//@CLASSES:
//   balm::Collector: a container for collecting and aggregating metric values
//
//@SEE_ALSO: balm_collectorrepository, balm_metric
//
//@DESCRIPTION: This component provides a class for collecting and aggregating
// the values of a metric.  The collector records the number of times an event
// occurs as well as an associated measurement value.  This component does
// *not* define what constitutes an event or what the value measures.  The
// collector manages, in a thread-safe manner, the count of event occurrences
// and the aggregated minimum, maximum, and total of the measured metric
// value.  This collector class provides operations to update the aggregated
// value, a `load` operator to populate a `balm::MetricRecord` with the current
// state of the collector, a `reset` method to reset the current state of the
// collector, and finally a combined `loadAndReset` method that performs both
// a load and a reset as an atomic operation.  Note that in practice, most
// clients should not need to access a `balm::Collector` directly, but instead
// use it through another type (see `balm_metric`).
//
///Alternative Systems for Telemetry
///---------------------------------
// Bloomberg software may alternatively use the GUTS telemetry API, which is
// integrated into Bloomberg infrastructure.
//
///Thread Safety
///-------------
// `balm::Collector` is fully *thread-safe*, meaning that all non-creator
// operations on a given instance can be safely invoked simultaneously from
// multiple threads.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Basic Usage
/// - - - - - - - - - - -
// The following example creates a `balm::Collector`, modifies its values, then
// collects a `balm::MetricRecord`.
//
// We start by creating a `balm::MetricId` object by hand, but in practice, an
// id should be obtained from a `balm::MetricRegistry` object (such as the one
// owned by a `balm::MetricsManager`):
// ```
// balm::Category           myCategory("MyCategory");
// balm::MetricDescription  description(&myCategory, "MyMetric");
// balm::MetricId           myMetric(&description);
// ```
// Now we create a `balm::Collector` object for `myMetric` and use the `update`
// method to update its collected value:
// ```
// balm::Collector collector(myMetric);
//
// collector.update(1.0);
// collector.update(3.0);
// ```
// The collector accumulated the values 1 and 3.  The result should have a
// count of 2, a total of 4 (3 + 1), a max of 3 (max(3, 1)), and a min of 1
// (min(3, 1)).
// ```
// balm::MetricRecord record;
// collector.loadAndReset(&record);
//
//     assert(myMetric == record.metricId());
//     assert(2        == record.count());
//     assert(4        == record.total());
//     assert(1.0      == record.min());
//     assert(3.0      == record.max());
// ```

#include <balscm_version.h>

#include <balm_metricrecord.h>
#include <balm_metricid.h>

#include <bslmt_mutex.h>
#include <bslmt_lockguard.h>

#include <bsl_algorithm.h>

namespace BloombergLP {


namespace balm {

                              // ===============
                              // class Collector
                              // ===============

/// This class provides a mechanism for collecting and aggregating the
/// value of a metric over a period of time.  The collector contains a
/// `MetricRecord` object that holds the identity of the metric being
/// collected, the number of times an event occurred, and the total,
/// minimum, and maximum aggregates of the associated measurement value.
/// The default value for the count is 0, the default value for the total
/// is 0.0, the default minimum value is `MetricRecord::k_DEFAULT_MIN`, and
/// the default maximum value is `MetricRecord::k_DEFAULT_MAX`.
class Collector {

    // DATA
    MetricRecord         d_record;  // the recorded metric information
    mutable bslmt::Mutex d_lock;    // record synchronization mechanism

    // NOT IMPLEMENTED
    Collector(const Collector&);
    Collector& operator=(const Collector&);

  public:
     // CREATORS

    /// Create a collector for a metric having the specified `metricId`,
    /// and having an initial count of 0, total of 0.0, min of
    /// `MetricRecord::k_DEFAULT_MIN`, and max of
    /// `MetricRecord::k_DEFAULT_MAX`.
    Collector(const MetricId& metricId);

    /// Destroy this object.
    ~Collector();

    // MANIPULATORS

    /// Reset the count, total, minimum, and maximum values of the metric
    /// being collected to their default states.  After this operation, the
    /// count and total values will be 0, the minimum value will be
    /// `MetricRecord::k_DEFAULT_MIN`, and the maximum value will be
    /// `MetricRecord::k_DEFAULT_MAX`.
    void reset();

    /// Load into the specified `record` the id of the metric being
    /// collected as well as the current count, total, minimum, and maximum
    /// aggregated values for that metric; then reset the count, total,
    /// minimum, and maximum values to their default states.  After this
    /// operation, the count and total values will be 0, the minimum value
    /// will be `MetricRecord::k_DEFAULT_MIN`, and the maximum value will be
    /// `MetricRecord::k_DEFAULT_MAX`.  Note that this operation is
    /// logically equivalent to calling the `load` and then the `reset`
    /// methods except that it is performed as a single atomic operation.
    void loadAndReset(MetricRecord *record);

    /// Increment the event count by 1, add the specified `value` to the
    /// total, if `value` is less than the minimum value, set `value` to be
    /// the minimum value, and if `value` is greater than the maximum
    /// value, set `value` to be the maximum value.
    void update(double value);

    /// Increment the event count by the specified `count`, add the
    /// specified `total` to the accumulated total, if specified `min` is
    /// less than the minimum value, set `min` to be the minimum value, and
    /// if specified `max` is greater than the maximum value, set `max` to
    /// be the maximum value.
    void accumulateCountTotalMinMax(int    count,
                                    double total,
                                    double min,
                                    double max);

    /// Set the event count to the specified `count`, the total aggregate to
    /// the specified `total`, the minimum aggregate to the specified `min`
    /// and the maximum aggregate to the specified `max`.
    void setCountTotalMinMax(int count, double total, double min, double max);

    // ACCESSORS

    /// Return a reference to the non-modifiable `MetricId` object
    /// identifying the metric for which this object collects values.
    const MetricId& metricId() const;

    /// Load into the specified `record` the id of the metric being
    /// collected, as well as the current count, total, minimum, and
    /// maximum aggregated values for the metric.
    void load(MetricRecord *record) const;
};

// ============================================================================
//                            INLINE DEFINITIONS
// ============================================================================

                              // ---------------
                              // class Collector
                              // ---------------

// CREATORS
inline
Collector::Collector(const MetricId& metricId)
: d_record(metricId)
, d_lock()
{
}

inline
Collector::~Collector()
{
}

// MANIPULATORS
inline
void Collector::reset()
{
    bslmt::LockGuard<bslmt::Mutex> guard(&d_lock);
    d_record.count() = 0;
    d_record.total() = 0.0;
    d_record.min()   = MetricRecord::k_DEFAULT_MIN;
    d_record.max()   = MetricRecord::k_DEFAULT_MAX;
}

inline
void Collector::loadAndReset(MetricRecord *record)
{
    bslmt::LockGuard<bslmt::Mutex> guard(&d_lock);
    *record          = d_record;
    d_record.count() = 0;
    d_record.total() = 0.0;
    d_record.min()   = MetricRecord::k_DEFAULT_MIN;
    d_record.max()   = MetricRecord::k_DEFAULT_MAX;
}

inline
void Collector::update(double value)
{
    bslmt::LockGuard<bslmt::Mutex> guard(&d_lock);
    ++d_record.count();
    d_record.total() += value;
    d_record.min()   =  bsl::min(d_record.min(), value);
    d_record.max()   =  bsl::max(d_record.max(), value);
}

inline
void Collector::accumulateCountTotalMinMax(int    count,
                                           double total,
                                           double min,
                                           double max)
{
    bslmt::LockGuard<bslmt::Mutex> guard(&d_lock);
    d_record.count() += count;
    d_record.total() += total;
    d_record.min()   =  bsl::min(d_record.min(), min);
    d_record.max()   =  bsl::max(d_record.max(), max);
}

inline
void Collector::setCountTotalMinMax(int    count,
                                    double total,
                                    double min,
                                    double max)
{
    bslmt::LockGuard<bslmt::Mutex> guard(&d_lock);
    d_record.count() = count;
    d_record.total() = total;
    d_record.min()   = min;
    d_record.max()   = max;
}

// ACCESSORS
inline
const MetricId& Collector::metricId() const
{
    return d_record.metricId();
}

inline
void Collector::load(MetricRecord *record) const
{
    bslmt::LockGuard<bslmt::Mutex> guard(&d_lock);
    *record = d_record;
}
}  // close package namespace

}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2015 Bloomberg Finance L.P.
//
// 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.
// ----------------------------- END-OF-FILE ----------------------------------
