// balm_integermetric.h                                               -*-C++-*-

// ----------------------------------------------------------------------------
//                                   NOTICE
//
// This component is not up to date with current BDE coding standards, and
// should not be used as an example for new development.
// ----------------------------------------------------------------------------

#ifndef INCLUDED_BALM_INTEGERMETRIC
#define INCLUDED_BALM_INTEGERMETRIC

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide helper classes for recording int metric values.
//
//@CLASSES:
//   balm::IntegerMetric: container for recording integer metric values
//
//@SEE_ALSO: balm_metric, balm_metrics, balm_metricsmanager,
//           balm_defaultmetricsmanager
//
//@DESCRIPTION: This component provides a class, 'balm::IntegerMetric', to
// simplify the process of collecting integer metrics.  A metric 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 associated value represents.  A metric maintains a count of event
// occurrences and the aggregated minimum, maximum, and total of the measured
// metric-event values.
//
// The 'balm::IntegerMetric' class, defined in this component, has in-core
// value semantics.  Each 'balm::IntegerMetric' object holds a pointer to a
// 'balm::IntegerCollector' that collects values for a particular integer
// metric.  The 'balm::IntegerCollector' is either supplied at construction, or
// else obtained from a 'balm::MetricsManager' object's
// 'balm::CollectorRepository'.  If the supplied 'balm::MetricsManager' is 0,
// the metric will use the default metrics manager instance
// ('balm::DefaultMetricsManager::instance()'), if initialized; otherwise the
// metric is placed in the inactive state (i.e., 'isActive()' is 'false') and
// operations that would otherwise update the metric will have no effect.
//
///Alternative Systems for Telemetry
///---------------------------------
// Bloomberg software may alternatively use the GUTS telemetry API, which is
// integrated into Bloomberg infrastructure.
//
///Choosing Between 'balm::IntegerMetric' and Macros
///-------------------------------------------------
// The 'balm::IntegerMetric' class and the macros defined in 'balm_metrics'
// provide the same basic functionality.  Clients may find
// 'balm::IntegerMetric' objects better suited to collecting integer metrics
// associated with a particular instance of a stateful object, while macros are
// better suited to collecting metrics associated with a particular code path
// (rather than an object instance).  In most instances choosing is a matter of
// taste.
//
///Thread Safety
///-------------
// 'balm::IntegerMetric' is fully *thread-safe*, meaning that all non-creator
// operations on a given instance can be safely invoked simultaneously from
// multiple threads.
//
// In addition all the macros defined in this component are fully
// *thread-safe*, meaning that they can be safely invoked simultaneously from
// multiple threads.
//
///Usage
///-----
// The following examples demonstrate how to configure, collect, and publish
// metrics.
//
///Example 1: Metric Collection With 'balm::IntegerMetric'
///- - - - - - - - - - - - - - - - - - - - - - - - - - - -
// We can use 'balm::IntegerMetric' objects to record metric values.  In this
// example we implement a hypothetical event manager object.  We use
// 'balm::IntegerMetric' objects to record metrics for the size of the request,
// the elapsed processing time, and the number of failures.
//..
//  class EventManager {
//
//      // DATA
//      balm::IntegerMetric d_messageSize;
//      balm::IntegerMetric d_elapsedTime;
//      balm::IntegerMetric d_failedRequests;
//
//    public:
//
//      // CREATORS
//      EventManager()
//      : d_messageSize("MyCategory", "EventManager/size")
//      , d_elapsedTime("MyCategory", "EventManager/elapsedTime")
//      , d_failedRequests("MyCategory", "EventManager/failedRequests")
//      {}
//
//      // MANIPULATORS
//      int handleEvent(int eventId, const bsl::string& eventMessage)
//          // Process the event described by the specified 'eventId' and
//          // 'eventMessage' .  Return 0 on success, and a non-zero value
//          // if there was an error handling the event.
//      {
//          (void)eventId;
//
//          int returnCode = 0;
//
//          d_messageSize.update(static_cast<int>(eventMessage.size()));
//
//          bsls::TimeInterval start = bdlt::CurrentTime::now();
//
//          // Process 'data' ('returnCode' may change).
//
//          if (0 != returnCode) {
//              d_failedRequests.increment();
//          }
//
//          bsls::TimeInterval end = bdlt::CurrentTime::now();
//          d_elapsedTime.update(static_cast<int>(
//                                         (end - start).totalMicroseconds()));
//          return returnCode;
//      }
//
//  // ...
//  };
//..
//
///Example 2: Create and Access the Default 'balm::MetricsManager' Instance
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// This example demonstrates how to create the default 'balm::MetricManager'
// instance and perform a trivial configuration.
//
// First we create a 'balm::DefaultMetricsManagerScopedGuard', which manages
// the lifetime of the default metrics manager instance.  At construction, we
// provide the scoped guard an output stream ('stdout') that it will publish
// metrics to.  Note that the default metrics manager is intended to be created
// and destroyed by the *owner* of 'main'.  An instance of the manager should
// be created during the initialization of an application (while the task has a
// single thread) and destroyed just prior to termination (when there is
// similarly a single thread).
//..
//  int main(int argc, char *argv[])
//  {
//      // ...
//
//      balm::DefaultMetricsManagerScopedGuard managerGuard(bsl::cout);
//..
// Once the default instance has been created, it can be accessed using the
// 'instance' operation.
//..
//      balm::MetricsManager *manager =
//                                     balm::DefaultMetricsManager::instance();
//      assert(0 != manager);
//..
// Note that the default metrics manager will be released when 'managerGuard'
// exits this scoped and is destroyed.  Clients that choose to explicitly call
// 'balm::DefaultMetricsManager::create' must also explicitly call
// 'balm::DefaultMetricsManager::release()'.
//
// Now that we have created a 'balm::MetricsManager' instance, we can use the
// instance to publish metrics collected using the event manager described in
// {Example 1}:
//..
//      EventManager eventManager;
//
//      eventManager.handleEvent(0, "ab");
//      eventManager.handleEvent(0, "abc");
//      eventManager.handleEvent(0, "abc");
//      eventManager.handleEvent(0, "abdef");
//
//      manager->publishAll();
//
//      eventManager.handleEvent(0, "ab");
//      eventManager.handleEvent(0, "abc");
//      eventManager.handleEvent(0, "abc");
//      eventManager.handleEvent(0, "abdef");
//
//      eventManager.handleEvent(0, "a");
//      eventManager.handleEvent(0, "abc");
//      eventManager.handleEvent(0, "abc");
//      eventManager.handleEvent(0, "abdefg");
//
//      manager->publishAll();
//  }
//..

#include <balscm_version.h>

#include <balm_collectorrepository.h>
#include <balm_defaultmetricsmanager.h>
#include <balm_integercollector.h>
#include <balm_metricid.h>
#include <balm_metricsmanager.h>
#include <balm_publicationtype.h>

#include <bsls_atomic.h>

namespace BloombergLP {

namespace balm {
                            // ===================
                            // class IntegerMetric
                            // ===================

class IntegerMetric {
    // This class provides an in-core value semantic type for recording and
    // aggregating the values of an integer metric.  The value of a
    // 'IntegerMetric' object is characterized by the 'IntegerCollector' object
    // it uses to collect metric-event values.  Each instance of this class
    // establishes (at construction) an association to an 'IntegerCollector'
    // object to which the metric delegates.  A 'IntegerMetric' value is
    // constant after construction (i.e., it does not support assignment or
    // provide manipulators that modify its collector value), so that
    // synchronization primitives are not required to protect its data members.
    // Note that if a collector or metrics manager is not supplied at
    // construction, and if the default metrics manager has not been
    // instantiated, then the metric will be inactive (i.e., 'isActive()' is
    // 'false') and the manipulator methods of the integer metric object will
    // have no effect.

    // DATA
    IntegerCollector      *d_collector_p;  // collected metric data (held, not
                                           // owned); may be 0, but cannot be
                                           // invalid

    const bsls::AtomicInt *d_isEnabled_p;  // memo for isActive()

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

  public:
    // CLASS METHODS
    static IntegerCollector *lookupCollector(const char     *category,
                                             const char     *name,
                                             MetricsManager *manager = 0);
        // Return an integer collector corresponding to the specified metric
        // 'category' and 'name'.  Optionally specify a metrics 'manager' used
        // to provide the collector.  If 'manager' is 0, use the default
        // metrics manager if initialized; if 'manager' is 0 and the default
        // metrics manager has not been initialized, return 0.  The behavior
        // is undefined unless 'category' and 'name' are null-terminated.

    static IntegerCollector *lookupCollector(const MetricId&  metricId,
                                             MetricsManager  *manager = 0);
        // Return an integer collector for the specified 'metricId'.
        // Optionally specify a metrics 'manager' used to provide the
        // collector.  If 'manager' is 0, use the default metrics manager, if
        // initialized; if 'manager' is 0 and the default metrics manager has
        // not been initialized, return 0.  The behavior is undefined unless
        // 'metricId' is a valid metric id supplied by the 'MetricsRegistry'
        // of the indicated metrics manager.

    // CREATORS
    IntegerMetric(const char     *category,
                  const char     *name,
                  MetricsManager *manager = 0);
        // Create an integer metric object to collect values for the metric
        // identified by the specified 'category' and 'name'.  Optionally
        // specify a metrics 'manager' used to provide a collector for the
        // indicated metric.  If 'manager' is 0, use the default metrics
        // manager, if initialized; if 'manager' is 0 and the default metrics
        // manager has not been initialized, place this metric object in the
        // inactive state (i.e., 'isActive()' is 'false') in which case
        // instance methods that would otherwise update the metric will have no
        // effect.  The behavior is undefined unless 'category' and 'name' are
        // null-terminated.

    explicit IntegerMetric(const MetricId&  metricId,
                           MetricsManager  *manager = 0);
        // Create an integer metric object to collect values for the specified
        // 'metricId'.  Optionally specify a metrics 'manager' used to provide
        // a collector for 'metricId'.  If 'manager' is 0, use the default
        // metrics manager, if initialized; if 'manager' is 0 and the default
        // metrics manager has not been initialized, place this metric object
        // in the inactive state (i.e., 'isActive()' is 'false') in which case
        // instance methods that would otherwise update the metric will have
        // no effect.  The behavior is undefined unless 'metricId' is a valid
        // id returned by the 'MetricRepository' object owned by the indicated
        // metrics manager.

    explicit IntegerMetric(IntegerCollector *collector);
        // Create an integer metric object to collect values for the metric
        // implied by the specified 'collector' (i.e.,
        // 'collector->metricId()').  The behavior is undefined unless
        // 'collector' is a valid address of a 'IntegerCollector' object and
        // the collector object supplied has a valid id (i.e.,
        // 'collector->metricId().isValid()').

    IntegerMetric(const IntegerMetric& original);
        // Create an integer metric object that will record values for the same
        // metric (i.e., using the same 'IntegerCollector' object) as the
        // specified 'original' integer metric.  If the 'original' metric is
        // inactive (i.e., 'isActive() == false'), then this metric will
        // similarly be inactive.

    // ~IntegerMetric();
        // Destroy this integer metric.  Note that this trivial destructor is
        // generated by the compiler.

    // MANIPULATORS
    void increment();
        // Increase the count and total of this integer metric by 1; if 1 is
        // less than the current minimum recorded value of the metric, set the
        // new minimum value to be 1; if 1 is greater than the current maximum
        // recorded value, set the new maximum value to be 1.  If, however,
        // this integer metric is not active (i.e., 'isActive()' is 'false'),
        // then this method has no effect.  Note that this method is
        // functionally equivalent to 'update(1).'

    void update(int value);
        // Increase the event count by 1 and add the specified 'value' to the
        // total recorded value; if 'value' is less than the current minimum
        // recorded value of the metric, set the new minimum value to be
        // 'value'; if 'value' is greater than the current maximum recorded
        // value, set the new maximum value to be 'value'.  If, however, this
        // integer metric is inactive (i.e., 'isActive()' is 'false'), then
        // this method has no effect.

    void accumulateCountTotalMinMax(int count, int total, int min, int max);
        // Increase the event count by the specified 'count' and add the
        // specified 'total' to the accumulated total; if the specified 'min'
        // is less than the current minimum recorded value of the metric, set
        // the new minimum value to be 'min'; if the specified 'max' is
        // greater than the current maximum recorded value, set the new
        // maximum value to be 'max'.  If, however, this integer metric is
        // inactive (i.e., 'isActive()' is 'false'), then this method has no
        // effect.

    IntegerCollector *collector();
        // Return the address of the modifiable integer collector for this
        // integer metric.

    // ACCESSORS
    const IntegerCollector *collector() const;
        // Return the address of the non-modifiable integer collector for this
        // integer metric.

    MetricId metricId() const;
        // Return a 'MetricId' object identifying this integer metric.  If
        // this metric was not supplied a valid integer collector at
        // construction then the returned id will be invalid (i.e.,
        // 'metricId().isValid()' is 'false').

    bool isActive() const;
        // Return 'true' if this integer metric will actively record metrics,
        // and 'false' otherwise.  If the returned value is 'false', the
        // manipulator operations will have no effect.  An integer metric will
        // be inactive if either (1) it was not initialized with a valid metric
        // identifier or (2) the associated metric category has been disabled
        // (see the 'MetricsManager' method 'setCategoryEnabled').  Note that
        // invoking this method is logically equivalent to the expression
        // '0 != collector() && metricId().category()->enabled()'.
};

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

// FREE OPERATORS
inline
bool operator==(const IntegerMetric& lhs, const IntegerMetric& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' integer metrics have the
    // same value and 'false' otherwise.  Two integer metrics have the same
    // value if they record measurements using the same integer collector
    // object or if they both have null collectors (i.e., 'collector()' is 0).

inline
bool operator!=(const IntegerMetric& lhs, const IntegerMetric& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' integer metrics do not
    // have the same value and 'false' otherwise.  Two integer metrics do not
    // have the same value if they record measurements using different integer
    // collector objects or if one, but not both, have a null collector (i.e.,
    // 'collector()' is 0).

                        // ============================
                        // class IntegerMetric_MacroImp
                        // ============================

struct IntegerMetric_MacroImp {
    // This structure provides a namespace for functions used to implement the
    // macros defined by this component.
    //
    // This is an implementation type of this component and *must* *not* be
    // used by clients of the 'balm' package.

    // CLASS METHODS
    static void getCollector(IntegerCollector **collector,
                             CategoryHolder    *holder,
                             const char        *category,
                             const char        *metric);
        // Load the specified 'collector' with the address of the default
        // integer collector (from the default metrics manager) for the
        // specified 'metric' identified by the specified 'category' and
        // 'name', and register the specified 'holder' for 'category'.   Note
        // that '*collector' must be assigned *before* registering 'holder' to
        // ensure that the macros always have a valid 'collector' when
        // 'holder->enabled()' is 'true'.

    static void getCollector(
                       IntegerCollector       **collector,
                       CategoryHolder          *holder,
                       const char              *category,
                       const char              *metric,
                       PublicationType::Value   preferredPublicationType);
        // Load the specified 'collector' with the address of the default
        // integer collector (from the default metrics manager) for the
        // specified 'metric' identified by the specified 'category' and
        // 'name', register the specified 'holder' for 'category', and set the
        // identified metric's preferred publication type to the specified
        // 'preferredPublicationType'.  Note that '*collector' must be
        // assigned before 'holder' to ensure that the macros always have a
        // valid 'collector' when 'holder->enabled()' is 'true'.
};

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

                            // -------------------
                            // class IntegerMetric
                            // -------------------

// CLASS METHODS
inline
IntegerCollector *IntegerMetric::lookupCollector(const char     *category,
                                                 const char     *name,
                                                 MetricsManager *manager)
{
    manager = DefaultMetricsManager::manager(manager);
    return manager
         ? manager->
               collectorRepository().getDefaultIntegerCollector(category, name)
         : 0;
}

inline
IntegerCollector *IntegerMetric::lookupCollector(const MetricId&  metricId,
                                                 MetricsManager  *manager)
{
    manager = DefaultMetricsManager::manager(manager);
    return manager
         ? manager->collectorRepository().getDefaultIntegerCollector(metricId)
         : 0;
}

// CREATORS
inline
IntegerMetric::IntegerMetric(const char     *category,
                             const char     *name,
                             MetricsManager *manager)
: d_collector_p(lookupCollector(category, name, manager))
{
    d_isEnabled_p = (d_collector_p
                  ? &d_collector_p->metricId().category()->isEnabledRaw() : 0);
}

inline
IntegerMetric::IntegerMetric(const MetricId&  metricId,
                             MetricsManager  *manager)
: d_collector_p(lookupCollector(metricId, manager))
{
    d_isEnabled_p = (d_collector_p
                  ? &d_collector_p->metricId().category()->isEnabledRaw() : 0);
}

inline
IntegerMetric::IntegerMetric(IntegerCollector *collector)
: d_collector_p(collector)
{
    d_isEnabled_p = &d_collector_p->metricId().category()->isEnabledRaw();
}

inline
IntegerMetric::IntegerMetric(const IntegerMetric& original)
: d_collector_p(original.d_collector_p)
, d_isEnabled_p(original.d_isEnabled_p)
{
}

// MANIPULATORS
inline
void IntegerMetric::increment()
{
    if (this->isActive()) {
        d_collector_p->update(1);
    }
}

inline
void IntegerMetric::update(int value)
{
    if (this->isActive()) {
        d_collector_p->update(value);
    }
}

inline
void IntegerMetric::accumulateCountTotalMinMax(int count,
                                               int total,
                                               int min,
                                               int max)
{
    if (this->isActive()) {
        d_collector_p->accumulateCountTotalMinMax(count, total, min, max);
    }
}

inline
IntegerCollector *IntegerMetric::collector()
{
    return d_collector_p;
}

// ACCESSORS
inline
const IntegerCollector *IntegerMetric::collector() const
{
    return d_collector_p;
}

inline
MetricId IntegerMetric::metricId() const
{
    return d_collector_p ? d_collector_p->metricId() : MetricId();
}

inline
bool IntegerMetric::isActive() const
{
    return d_isEnabled_p && d_isEnabled_p->loadRelaxed();
}

}  // close package namespace

// FREE OPERATORS
inline
bool balm::operator==(const IntegerMetric& lhs, const IntegerMetric& rhs)
{
    return lhs.collector() == rhs.collector();
}

inline
bool balm::operator!=(const IntegerMetric& lhs, const IntegerMetric& rhs)
{
    return !(lhs == rhs);
}

}  // 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 ----------------------------------