// balm_stopwatchscopedguard.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_STOPWATCHSCOPEDGUARD
#define INCLUDED_BALM_STOPWATCHSCOPEDGUARD

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

//@PURPOSE: Provide a scoped guard for recording elapsed time.
//
//@CLASSES:
// balm::StopwatchScopedGuard: guard for recording a metric for elapsed time
//
//@SEE_ALSO: balm_metricsmanager, balm_defaultmetricsmanager, balm_metric
//
//@DESCRIPTION: This component provides a scoped guard class intended to
// simplify the task of recording (to a metric) the elapsed time of a block of
// code.  The 'balm::StopwatchScopedGuard' is supplied the identity of a metric
// on construction, and an optional enumerated constant indicating the time
// units to report values in (by default, values are reported in seconds).  The
// guard measures the elapsed time between its construction and destruction,
// and on destruction records that elapsed time, in the indicated time units,
// to the supplied metric.
//
///Alternative Systems for Telemetry
///---------------------------------
// Bloomberg software may alternatively use the GUTS telemetry API, which is
// integrated into Bloomberg infrastructure.
//
///Choosing Between 'balm::StopwatchScopedGuard' and Macros
///--------------------------------------------------------
// The 'balm::StopwatchScopedGuard' class and the macros defined in the
// 'balm_metrics' component provide the same basic functionality.  Clients may
// find that using a 'balm::StopwatchScopedGuard' object (in coordination with
// a 'balm::Metric' object) is better suited to collecting 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, however, choosing between the
// two is a matter of taste.
//
///Thread Safety
///-------------
// 'balm::StopwatchScopedGuard' is *const* *thread-safe*, meaning that
// accessors may be invoked concurrently from different threads, but it is not
// safe to access or modify a 'balm::StopwatchScopedGuard' in one thread while
// thread modifies the same object.  Note however, that at this another time
// 'balm::StopwatchScopedGuard' provides no manipulator methods.
//
///Usage
///-----
// The following examples demonstrate how to record the elapsed time of a block
// of code using a 'balm::StopwatchScopedGuard'.
//
///Example 1: Create and Configure 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
// the 'balm::DefaultMetricsManager::create' method must also explicitly call
// the 'balm::DefaultMetricsManager::release' method.
//
///Example 2: Metric Collection with 'balm::StopwatchScopedGuard'
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Alternatively, we can use the 'balm::StopwatchScopedGuard' to record metric
// values.  In the following example we implement a hypothetical request
// processor similar to the one in example 3.  We use a 'balm::Metric'
// ('d_elapsedTime') and a 'balm::StopwatchScopedGuard' ('guard') to record the
// elapsed time of the request-processing function.
//..
//  class RequestProcessor {
//
//      // DATA
//      balm::Metric d_elapsedTime;
//
//    public:
//
//      // CREATORS
//      RequestProcessor()
//      : d_elapsedTime("MyCategory", "RequestProcessor/elapsedTime")
//      {}
//
//      // MANIPULATORS
//      int processRequest(const bsl::string& request)
//          // Process the specified 'request'.  Return 0 on success, and a
//          // non-zero value otherwise.
//      {
//          (void)request;
//
//          int returnCode = 0;
//
//          balm::StopwatchScopedGuard guard(&d_elapsedTime);
//
//  // ...
//
//          return returnCode;
//      }
//
//  // ...
//  };
//
//  // ...
//
//      RequestProcessor processor;
//
//      processor.processRequest("ab");
//      processor.processRequest("abc");
//      processor.processRequest("abc");
//      processor.processRequest("abdef");
//
//      manager->publishAll();
//
//      processor.processRequest("ab");
//      processor.processRequest("abc");
//      processor.processRequest("abc");
//      processor.processRequest("abdef");
//
//      processor.processRequest("a");
//      processor.processRequest("abc");
//      processor.processRequest("abc");
//      processor.processRequest("abdefg");
//
//      manager->publishAll();
//
//..

#include <balscm_version.h>

#include <balm_collector.h>
#include <balm_collectorrepository.h>
#include <balm_defaultmetricsmanager.h>
#include <balm_metric.h>
#include <balm_metricsmanager.h>

#include <bsls_platform.h>
#include <bsls_stopwatch.h>

namespace BloombergLP {

namespace balm {
                         // ==========================
                         // class StopwatchScopedGuard
                         // ==========================

class StopwatchScopedGuard {
    // This class provides a mechanism for recording, to a metric, the elapsed
    // time from the construction of an instance of the guard until that
    // instance goes out of scope (and is destroyed).  The constructor of this
    // class takes an optional argument indicating the time units in which to
    // report the elapsed time; by default a guard will report time in seconds.
    // The supplied time units determine the scale of the double value reported
    // by this guard, but does *not* affect the precision of the elapsed time
    // measurement.  Each instance of this class delegates to a 'Collector' for
    // the metric.  This 'Collector' is initialized on construction based on
    // the constructor arguments.  If this scoped guard is not initialized with
    // an active metric, or if the supplied metric becomes inactive before the
    // scoped guard is destroyed, then 'isActive()' will return 'false' and no
    // metric values will be recorded.  Note that if the metric supplied at
    // construction is not active when the scoped guard is constructed, the
    // scoped guard will not become active or record metric values regardless
    // of the future state of that supplied metric.

  public:
    // PUBLIC TYPES
    enum Units {
        // An enumeration of supported time units.

        k_NANOSECONDS   = 1000000000,
        k_MICROSECONDS  = 1000000,
        k_MILLISECONDS  = 1000,
        k_SECONDS       = 1
#ifndef BDE_OMIT_INTERNAL_DEPRECATED
      , BAEM_NANOSECONDS   = k_NANOSECONDS
      , BAEM_MICROSECONDS  = k_MICROSECONDS
      , BAEM_MILLISECONDS  = k_MILLISECONDS
      , BAEM_SECONDS       = k_SECONDS
      , NANOSECONDS        = k_NANOSECONDS
      , MICROSECONDS       = k_MICROSECONDS
      , MILLISECONDS       = k_MILLISECONDS
      , SECONDS            = k_SECONDS
#endif // BDE_OMIT_INTERNAL_DEPRECATED
    };

  private:
    // DATA
    bsls::Stopwatch d_stopwatch;    // stopwatch

    Units           d_timeUnits;    // time units to record elapsed time in

    Collector *d_collector_p;  // metric collector (held, not owned); may
                                    // be 0, but cannot be invalid

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

  public:
    // CREATORS
    explicit StopwatchScopedGuard(Metric *metric,
                                  Units   timeUnits = k_SECONDS);
        // Initialize this scoped guard to record elapsed time using the
        // specified 'metric'.  Optionally specify the 'timeUnits' in which to
        // report elapsed time.  If 'metric->isActive()' is 'false', this
        // object will also be inactive (i.e., will not record any values).
        // The behavior is undefined unless 'metric' is a valid address of a
        // 'Metric' object.  Note that 'timeUnits' indicates the scale of the
        // double value reported by this guard, but does *not* affect the
        // precision of the elapsed time measurement.

    explicit StopwatchScopedGuard(Collector *collector,
                                  Units      timeUnits = k_SECONDS);
        // Initialize this scoped guard to record elapsed time using the
        // specified 'collector'.  Optionally specify the 'timeUnits' in which
        // to report elapsed time.  If 'collector' is 0 or
        //'collector->category().enabled() == false', this object will be
        // inactive (i.e., will not record any values).  The behavior is
        // undefined unless
        // 'collector == 0 || collector->metricId().isValid()'.  Note that
        // 'timeUnits' indicates the scale of the double value reported by
        // this guard, but does *not* affect the precision of the elapsed time
        // measurement.

    StopwatchScopedGuard(const MetricId&  metricId,
                         MetricsManager  *manager = 0);
    StopwatchScopedGuard(const MetricId&  metricId,
                         Units            timeUnits,
                         MetricsManager  *manager = 0);
        // Initialize this scoped guard to record an elapsed time to the
        // specified 'metricId' from the optionally specified 'manager'.
        // Optionally specify the 'timeUnits' in which to report elapsed time.
        // If 'timeUnits' is not provided, the elapsed time will be reported in
        // seconds.  If 'manager' is 0, the 'DefaultMetricsManager' singleton
        // instance is used.  If no 'manager' is supplied and the default
        // instance has not been created, this object will be inactive (i.e.,
        // it will not record any values); similarly, if the metric's
        // associated category is disabled (i.e.,
        // 'metricId.category()->enabled()' is 'false'), then this object will
        // be inactive.  The behavior is undefined unless unless 'metricId' is
        // a valid id returned by the 'MetricRepository' object owned by the
        // indicated metrics manager.  Note that 'timeUnits' indicates the
        // scale of the double value reported by this guard, but does *not*
        // affect the precision of the elapsed time measurement.

    StopwatchScopedGuard(const char     *category,
                         const char     *name,
                         MetricsManager *manager = 0);
    StopwatchScopedGuard(const char     *category,
                         const char     *name,
                         Units           timeUnits,
                         MetricsManager *manager = 0);
        // Initialize this scoped guard to record an elapsed time to the
        // metric, identified by the specified 'category' and 'name', from the
        // optionally specified 'manager'.  Optionally specify the 'timeUnits'
        // in which to report elapsed time.  If 'timeUnits' is not provided,
        // the elapsed time will be reported in seconds.  If 'manager' is 0,
        // use the 'DefaultMetricsManager' instance.  If no 'manager' is
        // supplied, and the default instance has not been created, this
        // object will be inactive (i.e., it will not record any values);
        // similarly, if the identified 'category' is disabled, then this
        // object will be inactive.  The behavior is undefined unless
        // 'category' and 'name' are null-terminated.  Note that 'timeUnits'
        // indicates the scale of the double value reported by this guard, but
        // does *not* affect the precision of the elapsed time measurement.

    ~StopwatchScopedGuard();
        // Destroy this scoped guard and, if the scoped guard is active,
        // record the accumulated elapsed time from its creation..

    // ACCESSORS
    bool isActive() const;
        // Return 'true' if this scoped guard will actively record metrics, and
        // 'false' otherwise.  If the returned value is 'false' the destructor
        // will not record a value to the metric.  A scoped guard will be
        // inactive if either (1) it was not initialized with a valid metric,
        // (2) the metric it was initialized with was not active at the time
        // of construction, or (3) the metric supplied at construction is
        // currently inactive, meaning the category of metrics this metric
        // belongs to has been disabled since this object's construction (see
        // the 'MetricsManager' method 'setCategoryEnabled').
};

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

                         // --------------------------
                         // class StopwatchScopedGuard
                         // --------------------------

// CREATORS
inline
StopwatchScopedGuard::StopwatchScopedGuard(Metric *metric,
                                           Units   timeUnits)
: d_stopwatch()
, d_timeUnits(timeUnits)
, d_collector_p(metric->isActive() ? metric->collector() : 0)
{
    if (d_collector_p) {
        d_stopwatch.start();
    }
}

inline
StopwatchScopedGuard::StopwatchScopedGuard(Collector *collector,
                                           Units      timeUnits)
: d_stopwatch()
, d_timeUnits(timeUnits)
, d_collector_p((collector && collector->metricId().category()->enabled())
                ? collector
                : 0)
{
    if (d_collector_p) {
        d_stopwatch.start();
    }
}

inline
StopwatchScopedGuard::StopwatchScopedGuard(const MetricId&  metricId,
                                           MetricsManager  *manager)
: d_stopwatch()
, d_timeUnits(k_SECONDS)
, d_collector_p(0)
{
    Collector *collector = Metric::lookupCollector(metricId, manager);
    d_collector_p = (collector &&
                     collector->metricId().category()->enabled())
                    ? collector : 0;
    if (d_collector_p) {
        d_stopwatch.start();
    }
}

inline
StopwatchScopedGuard::StopwatchScopedGuard(const MetricId&  metricId,
                                           Units            timeUnits,
                                           MetricsManager  *manager)
: d_stopwatch()
, d_timeUnits(timeUnits)
, d_collector_p(0)
{
    Collector *collector = Metric::lookupCollector(metricId, manager);
    d_collector_p = (collector &&
                     collector->metricId().category()->enabled())
                    ? collector : 0;
    if (d_collector_p) {
        d_stopwatch.start();
    }
}

inline
StopwatchScopedGuard::StopwatchScopedGuard(const char     *category,
                                           const char     *name,
                                           MetricsManager *manager)
: d_stopwatch()
, d_timeUnits(k_SECONDS)
, d_collector_p(0)
{
    Collector *collector = Metric::lookupCollector(category, name, manager);

    d_collector_p = (collector &&
                     collector->metricId().category()->enabled())
                    ? collector : 0;

    if (d_collector_p) {
        d_stopwatch.start();
    }
}

inline
StopwatchScopedGuard::StopwatchScopedGuard(const char     *category,
                                           const char     *name,
                                           Units           timeUnits,
                                           MetricsManager *manager)
: d_stopwatch()
, d_timeUnits(timeUnits)
, d_collector_p(0)
{
    Collector *collector = Metric::lookupCollector(category, name, manager);
    d_collector_p = (collector && collector->metricId().category()->enabled())
                    ? collector : 0;
    if (d_collector_p) {
        d_stopwatch.start();
    }
}

inline
StopwatchScopedGuard::~StopwatchScopedGuard()
{
    if (isActive()) {
        d_collector_p->update(d_stopwatch.elapsedTime() * +d_timeUnits);
    }
}

// ACCESSORS
inline
bool StopwatchScopedGuard::isActive() const
{
    return 0 != d_collector_p
        && d_collector_p->metricId().category()->enabled();
}

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