// balm_metrics.h                                                     -*-C++-*-
#ifndef INCLUDED_BALM_METRICS
#define INCLUDED_BALM_METRICS

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

//@PURPOSE: Provide a suite of operations for recording metric values.
//
//@CLASSES:
//
//@SEE_ALSO: balm_collector, balm_integercollector, balm_defaultmetricsmanager
//
//@DESCRIPTION: This component provides a suite of macros to simplify the
// process of collecting metrics.  A metric records the number of times an
// event occurs, as well as an associated measurement value.  A metric
// maintains a count of event occurrences and the aggregated minimum, maximum,
// and total of the measured metric-event values.  Note that this component
// does *not* define what constitutes an event nor what the associated value
// represents.
//
///Alternative Systems for Telemetry
///---------------------------------
// Bloomberg software may alternatively use the GUTS telemetry API, which is
// integrated into Bloomberg infrastructure.
//
///Thread Safety
///-------------
// All the macros defined in this component are *thread-safe*, meaning that
// they can be safely invoked simultaneously from multiple threads.  It is
// *not* safe, however, to invoke any of the macros defined in this component
// while the default metrics manager is being either created or destroyed.
//
///Macro Summary
///-------------
// This section provides a brief description of the macros defined in this
// component.  In most cases, category and metric names supplied as macro
// arguments are required to be *runtime* *constants*, meaning that their
// values must be constant for the lifetime of the process.  More complete
// documentation is provided in the `Macro Reference` section.
// ```
//  BALM_METRICS_IF_CATEGORY_ENABLED(CATEGORY)
//      This macro behaves like an 'if' clause, executing the subsequent
//      (compound) statement if the specified 'CATEGORY' is enabled.
//
//  BALM_METRICS_UPDATE(CATEGORY, METRIC1, VALUE1)
//  BALM_METRICS_UPDATE2(CATEGORY, METRIC1, VALUE1, METRIC2, VALUE2)
//  BALM_METRICS_UPDATE3(CATEGORY, METRIC1, VALUE1, ..., METRIC3, VALUE3)
//  BALM_METRICS_UPDATE4(CATEGORY, METRIC1, VALUE1, ..., METRIC4, VALUE4)
//  BALM_METRICS_UPDATE5(CATEGORY, METRIC1, VALUE1, ..., METRIC5, VALUE5)
//  BALM_METRICS_UPDATE6(CATEGORY, METRIC1, VALUE1, ..., METRIC6, VALUE6)
//  BALM_METRICS_INT_UPDATE(CATEGORY, METRIC1, VALUE1)
//  BALM_METRICS_INT_UPDATE2(CATEGORY, METRIC1, VALUE1, METRIC2, VALUE2)
//  BALM_METRICS_INT_UPDATE3(CATEGORY, METRIC1, VALUE1, ..., METRIC3, VALUE3)
//  BALM_METRICS_INT_UPDATE4(CATEGORY, METRIC1, VALUE1, ..., METRIC4, VALUE4)
//  BALM_METRICS_INT_UPDATE5(CATEGORY, METRIC1, VALUE1, ..., METRIC5, VALUE5)
//  BALM_METRICS_INT_UPDATE6(CATEGORY, METRIC1, VALUE1, ..., METRIC6, VALUE6)
//      Update each of up to 6 metrics by the corresponding values.
//      The supplied category and metric names must be *runtime* *constants*.
//
//  BALM_METRICS_TYPED_UPDATE(CATEGORY, METRIC, VALUE, PREFERRED_TYPE)
//  BALM_METRICS_TYPED_INT_UPDATE(CATEGORY, METRIC, VALUE, PREFERRED_TYPE)
//      Update the identified metric by 'VALUE' and set its preferred
//      publication type.  'CATEGORY' and 'METRIC' must be *runtime*
//      *constants*.
//
//  BALM_METRICS_INCREMENT(CATEGORY, METRIC)
//      Increment (by 1) the identified metric.  'CATEGORY' and 'METRIC' must
//      be *runtime* *constants*.
//
//  BALM_METRICS_TYPED_INCREMENT(CATEGORY, METRIC, PREFERRED_TYPE)
//      Increment (by 1) the identified metric and set the metric's preferred
//      publication type.  'CATEGORY' and 'METRIC' must be *runtime*
//      *constants*.
//
//  BALM_METRICS_DYNAMIC_UPDATE(CATEGORY, METRIC, VALUE)
//  BALM_METRICS_DYNAMIC_INT_UPDATE(CATEGORY, METRIC, VALUE)
//      Update the identified metric by 'VALUE'.  This operation performs a
//      lookup on 'CATEGORY' and 'METRIC' on each invocation, so those values
//      need *not* be runtime constants.
//
//  BALM_METRICS_DYNAMIC_INCREMENT(CATEGORY, METRIC)
//      Increment (by 1) the identified metric.  This operation performs a
//      lookup on 'CATEGORY' and 'METRIC' on each invocation, so those values
//      need *not* be runtime constants.
//
//  BALM_METRICS_TIME_BLOCK(CATEGORY, METRIC, TIME_UNITS)
//  BALM_METRICS_TIME_BLOCK_SECONDS(CATEGORY, METRIC)
//  BALM_METRICS_TIME_BLOCK_MILLISECONDS(CATEGORY, METRIC)
//  BALM_METRICS_TIME_BLOCK_MICROSECONDS(CATEGORY, METRIC)
//  BALM_METRICS_TIME_BLOCK_NANOSECONDS(CATEGORY, METRIC)
//      Update the identified metric by the elapsed (wall) time, in the
//      indicated units, from the instantiation point of the macro to the end
//      of the enclosing lexical scope.  'CATEGORY' and 'METRIC' must
//      be *runtime* *constants*.
//
//  BALM_METRICS_DYNAMIC_TIME_BLOCK(CATEGORY, METRIC, TIME_UNITS)
//  BALM_METRICS_DYNAMIC_TIME_BLOCK_SECONDS(CATEGORY, METRIC)
//  BALM_METRICS_DYNAMIC_TIME_BLOCK_MILLISECONDS(CATEGORY, METRIC)
//  BALM_METRICS_DYNAMIC_TIME_BLOCK_MICROSECONDS(CATEGORY, METRIC)
//  BALM_METRICS_DYNAMIC_TIME_BLOCK_NANOSECONDS(CATEGORY, METRIC)
//      Update the identified metric by the elapsed (wall) time, in the
//      indicated units, from the instantiation point of the macro to the end
//      of the enclosing lexical scope.  This operation performs a lookup on
//      'CATEGORY' and 'METRIC' on each invocation, so those values need *not*
//      be runtime constants.
// ```
//
///Macro Reference
///---------------
// The macros defined in this component make use of the default instance of
// `balm::MetricsManager`.  The macros have no effect unless the metrics
// manager default instance has been initialized via a call to
// `balm::DefaultMetricsManager::create`.
//
// The macros defined below provide two basic operations identified by their
// suffixes: update (`*_UPDATE`) and increment (`*_INCREMENT`).  The update
// operation increments (by 1) the identified metric's count of events,
// increases the metric's total by the supplied value, if the supplied value
// is less than the metric's current minimum value then the minimum value is
// set to the supplied value, and if the supplied value is greater than the
// metric's current maximum value then the maximum value is set to the supplied
// value.  The increment operation is logically equivalent to an update of 1.
//
// The update, increment, and timing operations come in two variations:
// standard, and dynamic (*<u>DYNAMIC</u>*).  The standard variation maintains a
// (function-scope static) cache containing the identity of the metric being
// collected.  This cache is initialized the *first* time the flow of control
// passes through the instantiated macro, which in practice means that the
// identifiers for the metric(s) passed as parameters to that macro must be
// *runtime* *constants*.  The dynamic variant looks up the supplied category
// and metric(s) on each invocation of the macro, resulting in additional
// runtime overhead compared to the standard variant.
//
// The following are the standard (non-dynamic) macros provided by this
// component for updating a metric's value:
// ```
//  BALM_METRICS_UPDATE(CATEGORY, METRIC, VALUE)
//      Update the indicated metric, identified by the specified 'CATEGORY'
//      and 'METRIC' names, with the specified 'VALUE'.  'CATEGORY' and
//      'METRIC' must be null-terminated strings of a type convertible to
//      'const char *', and 'VALUE' is assumed to be of a type convertible to
//      'double'.  This macro maintains a (function-scope static) cache
//      containing the identity of the metric being updated.  This cache is
//      initialized using the 'CATEGORY' and 'METRIC' specified on the *first*
//      application of this macro at a particular instantiation point;
//      subsequent applications use that cached information, which in
//      practice means that 'CATEGORY' and 'METRIC' must be *runtime*
//      *constants*.  If the default metrics manager has not been
//      initialized, or if the indicated 'CATEGORY' is currently disabled,
//      this macro has no effect.
//
//  BALM_METRICS_INT_UPDATE(CATEGORY, METRIC, VALUE)
//      Update the indicated metric, identified by the specified 'CATEGORY'
//      and 'METRIC' names, with the specified *integer* 'VALUE'.  'CATEGORY'
//      and 'METRIC' must be null-terminated strings of a type convertible to
//      'const char *', and 'VALUE' is assumed to be of a type convertible to
//      'int'.  This macro maintains a (function-scope static) cache
//      containing the identity of the metric being updated.  This cache is
//      initialized using the 'CATEGORY' and 'METRIC' specified on the *first*
//      application of this macro at a particular instantiation point;
//      subsequent applications use that cached information, which in
//      practice, means that 'CATEGORY' and 'METRIC' must be *runtime*
//      *constants*.  If the default metrics manager has not been initialized,
//      or if the indicated 'CATEGORY' is currently disabled, this macro has
//      no effect.
//
//  BALM_METRICS_UPDATE2(CATEGORY, METRIC1, VALUE1, METRIC2, VALUE2)
//  BALM_METRICS_UPDATEn(CATEGORY, METRIC1, VALUE1, ..., METRICn, VALUEn)
//                                                           Where 2 <= n <= 6
//      Update each of the indicated metrics, identified by the specified
//      'CATEGORY' name and the respective metric names 'METRIC1', 'METRIC2',
//      ..., 'METRICn' (2 <= n <= 6), with the corresponding specified
//      'VALUE1', 'VALUE2', ..., 'VALUEn'.  'CATEGORY' and 'METRIC1',
//      'METRIC2', ..., 'METRICn', must be null-terminated strings of a type
//      convertible to 'const char *', and 'VALUE1', 'VALUE2', ..., 'VALUEn'
//      are assumed to be of a type convertible to 'double'.  These macros
//      maintain a (function-scope static) cache containing the identity of
//      the metrics being updated.  This cache is initialized using the
//      'CATEGORY' and metric identifiers specified on the *first*
//      application of these macros at a particular instantiation point;
//      subsequent applications use that cached information, which in
//      practice means that the 'CATEGORY' and metric identifiers must be
//      *runtime* *constants*.  If the default metrics manager has not been
//      initialized, or if the indicated 'CATEGORY' is currently disabled,
//      these macros have no effect.
//
//  BALM_METRICS_INT_UPDATE2(CATEGORY, METRIC1, VALUE1, METRIC2, VALUE2)
//  BALM_METRICS_INT_UPDATEn(CATEGORY, METRIC1, VALUE1, ..., METRICn, VALUEn)
//                                                           Where 2 <= n <= 6
//      Update each of the indicated metrics, identified by the specified
//      'CATEGORY' name and the respective metric names 'METRIC1', 'METRIC2',
//      ..., 'METRICn' (2 <= n <= 6), with the corresponding specified
//      *integer* 'VALUE1', 'VALUE2', ..., 'VALUEn'.  'CATEGORY' and
//      'METRIC1', 'METRIC2', ..., 'METRICn', must be null-terminated strings
//      of a type convertible to 'const char *', and 'VALUE1', 'VALUE2', ...,
//      'VALUEn' are assumed to be of a type convertible to 'int'.  These
//      macros maintain a (function-scope static) cache containing the
//      identity of the metrics being updated.  This cache is initialized
//      using the 'CATEGORY' and metric identifiers specified on the *first*
//      application of these macros at a particular instantiation point;
//      subsequent applications use that cached information, which in
//      practice means that the 'CATEGORY' and metric identifiers must be
//      *runtime* *constants*.  If the default metrics manager has not been
//      initialized, or if the indicated 'CATEGORY' is currently disabled,
//      these macros have no effect.
//
//  BALM_METRICS_TYPED_UPDATE(CATEGORY,
//                            METRIC,
//                            VALUE,
//                            PREFERRED_PUBLICATION_TYPE)
//  BALM_METRICS_TYPED_INT_UPDATE(CATEGORY,
//                                METRIC,
//                                VALUE,
//                                PREFERRED_PUBLICATION_TYPE)
//      The behavior of these macros is logically equivalent to
//      'BALM_METRICS_UPDATE(CATEGORY, METRIC, VALUE)' and
//      'BALM_METRICS_INT_UPDATE(CATEGORY, METRIC, VALUE)', respectively,
//      except that, on the first invocation, these macros also set the
//      indicated metric's 'PREFERRED_PUBLICATION_TYPE'.  The preferred
//      publication type of a metric indicates the preferred aggregate to
//      publish for that metric (e.g., 'COUNT', 'TOTAL', 'MIN', 'MAX', or
//      'RATE').  The behavior is undefined unless
//      'PREFERRED_PUBLICATION_TYPE' is convertible to
//      'balm::PublicationType::Value'.  Note that there is no uniform
//      definition for how publishers will interpret this value; an
//      'UNSPECIFIED' value, however, generally indicates that all of the
//      collected aggregates (total, count, minimum, and maximum value)
//      should be published.
//
//  BALM_METRICS_INCREMENT(CATEGORY, METRIC)
//      The behavior of this macro is logically equivalent to:
//      'BALM_METRICS_INT_UPDATE(CATEGORY, METRIC, 1)'.
//
//  BALM_METRICS_TYPED_INCREMENT(CATEGORY, METRIC, PREFERRED_TYPE)
//      The behavior of this macro is logically equivalent to
//      'BALM_METRICS_TYPED_UPDATE(CATEGORY, METRIC, 1, PREFERRED_TYPE)'.
// ```
//  The following are the dynamic macros provided by this component for
//  updating a metric's value; these macros do not statically cache the
//  identity of the metric and look up the supplied `CATEGORY` and `METRIC` on
//  each invocation:
// ```
//  BALM_METRICS_DYNAMIC_UPDATE(CATEGORY, METRIC, VALUE)
//      Update the indicated metric, identified by the specified 'CATEGORY'
//      and 'METRIC' names, by the specified 'VALUE'.  'CATEGORY' and
//      'METRIC' must be null-terminated strings of a type convertible to
//      'const char *', and 'VALUE' is assumed to be of a type convertible to
//      'double'.  If the default metrics manager has not been initialized,
//      or if the indicated 'CATEGORY' is currently disabled, this macro has
//      no effect.  Note that this operation looks up the 'CATEGORY'
//      and 'METRIC' on *each* application, resulting in (unnecessary)
//      additional runtime overhead (if the 'CATEGORY' and 'METRIC' values are
//      always the same for a particular point of call).
//
//  BALM_METRICS_DYNAMIC_INT_UPDATE(CATEGORY, METRIC, VALUE)
//      Update the indicated metric, identified by the specified 'CATEGORY'
//      and 'METRIC' names, by the specified *integer* 'VALUE'.  'CATEGORY'
//      and 'METRIC' must be null-terminated strings of a type convertible to
//      'const char *', and 'VALUE' is assumed to be of a type convertible
//      to 'int'.  If the default metrics manager has not been initialized,
//      or if the indicated 'CATEGORY' is currently disabled, this macro has
//      no effect.  Note that this operation looks up the 'CATEGORY'
//      and 'METRIC' on *each* application, resulting in (unnecessary)
//      additional runtime overhead (if the 'CATEGORY' and 'METRIC' values
//      are always the same for a particular point of call).
//
//  BALM_METRICS_DYNAMIC_INCREMENT(CATEGORY, METRIC)
//      The behavior of this macro is logically equivalent to
//      'BALM_METRICS_DYNAMIC_INT_UPDATE(CATEGORY, METRIC, 1)'.
// ```
// The following macro, `BALM_METRICS_IF_CATEGORY_ENABLED`, allows clients to
// (efficiently) determine if a (*runtime* *constant*) category is enabled:
// ```
//  BALM_METRICS_IF_CATEGORY_ENABLED(CATEGORY)
//      This macro behaves like an 'if' clause, executing the subsequent
//      (compound) statement if the specified 'CATEGORY' is enabled.
//      'CATEGORY' must be a null-terminated string of a type convertible to
//      'const char *'.  If the default metrics manager has not been
//      initialized, or if the indicated 'CATEGORY' is currently disabled,
//      the following statement is not executed (i.e., the 'if'-condition
//      is 'false').  This macro maintains a (function-scope static) cache
//      containing the identity of the category.  This cache is initialized
//      using the 'CATEGORY' specified on the *first* application of this
//      macro at a particular instantiation point; subsequent applications
//      use that cached information, which in practice means that
//      'CATEGORY' must be a *runtime* *constant*.  *WARNING*: although the
//      'BALM_METRICS_IF_CATEGORY_ENABLED' may look like a function call,
//      it actually consists of a declaration and an 'if' statement, which
//      means that the following is syntactically *incorrect*:
//      'if (BALM_METRICS_IF_CATEGORY_ENABLED("myCategory")) { stuff() }'.
//      That should instead be written as:
//      'BALM_METRICS_IF_CATEGORY_ENABLED("myCategory") { stuff(); }'.
// ```
// Finally, this component provides a set of macros to record the elapsed wall
// time of a block of code:
// ```
//  BALM_METRICS_TIME_BLOCK(CATEGORY, METRIC, TIME_UNITS)
//      Update the indicated metric, identified by the specified 'CATEGORY'
//      and 'METRIC' names, by the elapsed (wall) time, in the specified
//      'TIME_UNITS', from the point of instantiation of the macro to the end
//      of the enclosing lexical scope.  'CATEGORY' and 'METRIC'  must be
//      null-terminated strings of a type convertible to 'const char *',
//      while 'TIME_UNITS' is assumed to be of a type convertible to the
//      enumerated type 'balm::StopwatchScopedGuard::Units'.  This macro
//      maintains a (function-scope static) cache containing the identity of
//      the metric being updated.  This cache is initialized using the
//      'CATEGORY' and 'METRIC' specified on the *first* application of this
//      macro at a particular instantiation point; subsequent applications
//      use that cached information, which in practice means that
//      'CATEGORY' and 'METRIC' must be *runtime* *constants*.  If the
//      default metrics manager has not been initialized, or the identified
//      'CATEGORY' is disabled, this macro has no effect.  Note that
//      'TIME_UNITS' indicates the scale of value to report, but does *not*
//      affect the precision of the elapsed time measurement.
//
//  BALM_METRICS_TIME_BLOCK_SECONDS(CATEGORY, METRIC)
//      The behavior of this macro is logically equivalent to
//      'BALM_METRICS_TIME_BLOCK' called with
//      'balm::StopwatchScopedGuard::k_SECONDS'.
//
//  BALM_METRICS_TIME_BLOCK_MILLISECONDS(CATEGORY, METRIC)
//      The behavior of this macro is logically equivalent to
//      'BALM_METRICS_TIME_BLOCK' called with
//      'balm::StopwatchScopedGuard::k_MILLISECONDS'.
//
//  BALM_METRICS_TIME_BLOCK_MICROSECONDS(CATEGORY, METRIC)
//      The behavior of this macro is logically equivalent to
//      'BALM_METRICS_TIME_BLOCK' called with
//      'balm::StopwatchScopedGuard::k_MICROSECONDS'.
//
//  BALM_METRICS_TIME_BLOCK_NANOSECONDS(CATEGORY, METRIC)
//      The behavior of this macro is logically equivalent to
//      'BALM_METRICS_TIME_BLOCK' called with
//      'balm::StopwatchScopedGuard::k_NANOSECONDS'.
//
//  BALM_METRICS_DYNAMIC_TIME_BLOCK(CATEGORY, METRIC, TIME_UNITS)
//      Update the indicated metric, identified by the specified 'CATEGORY'
//      and 'METRIC' names, by the elapsed (wall) time, in the specified
//      'TIME_UNITS', from the instantiation of the macro to the end of the
//      enclosing lexical scope.  'CATEGORY' and 'METRIC'  must be
//      null-terminated strings of a type convertible to 'const char *',
//      while 'TIME_UNITS' is assumed to be of a type convertible to the
//      enumerated type 'balm::StopwatchScopedGuard::Units'.  If the default
//      metrics manager has not been initialized, or the identified
//      'CATEGORY' is disabled, this macro has no effect.  Note that
//      this operation looks up the 'CATEGORY' and 'METRIC' on *each*
//      application, resulting in (unnecessary) additional runtime overhead
//      (if the 'CATEGORY' and 'METRIC' values are always the same for a
//      particular point of call).  Also note that 'TIME_UNITS' indicates the
//      scale of value to report, but does *not* affect the precision of the
//      elapsed time measurement.
//
//  BALM_METRICS_DYNAMIC_TIME_BLOCK_SECONDS(CATEGORY, METRIC)
//      The behavior of this macro is logically equivalent to
//      'BALM_METRICS_DYNAMIC_TIME_BLOCK' called with
//      'balm::StopwatchScopedGuard::k_SECONDS'.
//
//  BALM_METRICS_DYNAMIC_TIME_BLOCK_MILLISECONDS(CATEGORY, METRIC)
//      The behavior of this macro is logically equivalent to
//      'BALM_METRICS_DYNAMIC_TIME_BLOCK' called with
//      'balm::StopwatchScopedGuard::k_MILLISECONDS'.
//
//  BALM_METRICS_DYNAMIC_TIME_BLOCK_MICROSECONDS(CATEGORY, METRIC)
//      The behavior of this macro is logically equivalent to
//      'BALM_METRICS_DYNAMIC_TIME_BLOCK' called with
//      'balm::StopwatchScopedGuard::k_MICROSECONDS'.
//
//  BALM_METRICS_DYNAMIC_TIME_BLOCK_NANOSECONDS(CATEGORY, METRIC)
//      The behavior of this macro is logically equivalent to
//      'BALM_METRICS_DYNAMIC_TIME_BLOCK' called with
//      'balm::StopwatchScopedGuard::k_NANOSECONDS'.
// ```
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Create and Configure the Default `balm::MetricsManager` Instance
///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// This example demonstrates how to create the default `balm::MetricsManager`
// 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 this guard with an output stream (`stdout`) to which the default
// metrics manager will publish metrics.  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 manager object 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 the
// `managerGuard` exits this scoped and is destroyed.  Clients that choose to
// explicitly call `balm::DefaultMetricsManager::create()` must also explicitly
// call `balm::DefaultMetricsManager::release()`.
//
///Example 2: Updating a Metric
/// - - - - - - - - - - - - - -
// Once a metrics manager is initialized, we can use the various macros to
// record metric values.  In this second example, we collect metrics from a
// hypothetical event-processing function.  We use `BALM_METRICS_UPDATE` to
// record the size of the data being processed to a metric named "msgSize",
// and the elapsed time (in milliseconds) to process the event to a metric
// named "elapsedTime".  Finally, we use `BALM_METRICS_INCREMENT` to record a
// count of failures to a metric named "failureCount".  Note that we do not use
// the `*_DYNAMIC_*` variants of the `BALM_METRICS_UPDATE` or
// `BALM_METRICS_INCREMENT` macros because the category and metric names are
// constant across all applications of the macro at a particular instantiation
// point (the `DYNAMIC` variants look up the category and metric name on each
// application, which would incur unnecessary runtime overhead).
// ```
// int processEvent(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 otherwise.
// {
//     (void)eventId;
//
//     int returnCode = 0;
//
//     BALM_METRICS_UPDATE("processEvent",
//                         "msgSize",
//                         static_cast<double>(eventMessage.size()));
//     BALM_METRICS_TIME_BLOCK_MILLISECONDS("processingEvent", "elapsedTime");
//
//     // Process 'data'.
//
//     if (0 != returnCode) {
//         BALM_METRICS_INCREMENT("processEvent", "failureCount");
//     }
//
//     return returnCode;
// }
// ```
//
///Example 3: Using `BALM_METRICS_IF_CATEGORY_ENABLED`
///- - - - - - - - - - - - - - - - - - - - - - - - - -
// In this next example, we use `BALM_METRICS_IF_CATEGORY_ENABLED` to
// conditionally disable a (relatively) expensive operation involved in
// computing a metric value.  The `processEvent2` function, defined below, uses
// a `bsls::Stopwatch` to record the elapsed system, user, and wall times,
// associated with processing the event.  The system calls used (via
// `bsls::Stopwatch`) to record the elapsed time may be relatively expensive,
// so we use `BALM_METRICS_IF_CATEGORY_ENABLED` to ensure we perform those
// operations only if metrics collection is enabled.  Finally, we use
// `BALM_METRICS_UPDATE3` to update the three metrics, which is (slightly) more
// efficient than updating each metric individually using `BALM_METRIC_UPDATE`.
// ```
// int processEvent2(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 otherwise.
// {
//     (void)eventId;
//     (void)eventMessage;
//
//     int returnCode = 0;
//
//     bsls::Stopwatch stopwatch;
//     BALM_METRICS_IF_CATEGORY_ENABLED("processEvent2") {
//        stopwatch.start(true);
//     }
//
//     // Process 'data'.
//
//     BALM_METRICS_IF_CATEGORY_ENABLED("processEvent2") {
//        double systemTime, userTime, wallTime;
//        stopwatch.accumulatedTimes(&systemTime, &userTime, &wallTime);
//        BALM_METRICS_UPDATE3("processEvent2",
//                             "systemTime", systemTime,
//                             "userTime",   userTime,
//                             "wallTime",   wallTime);
//     }
//
//     return returnCode;
// }
// ```

#include <balscm_version.h>

#include <balm_category.h>
#include <balm_collector.h>
#include <balm_collectorrepository.h>
#include <balm_defaultmetricsmanager.h>
#include <balm_integercollector.h>
#include <balm_metricid.h>
#include <balm_metricregistry.h>
#include <balm_metricsmanager.h>
#include <balm_publicationtype.h>
#include <balm_stopwatchscopedguard.h>

#include <bsls_atomic.h>
#include <bsls_performancehint.h>
#include <bsls_platform.h>
#include <bsls_spinlock.h>

                        // ================================
                        // BALM_METRICS_IF_CATEGORY_ENABLED
                        // ================================

#define BALM_METRICS_IF_CATEGORY_ENABLED(CATEGORY)                            \
    BALM_METRICS_IF_CATEGORY_ENABLED_IMP(                                     \
                            CATEGORY, BALM_METRICS_UNIQUE_NAME(categoryHolder))

                        // ===================
                        // BALM_METRICS_UPDATE
                        // ===================

// Note that the static collector address must be assigned *before*
// initializing category holder to ensure initialization is thread safe.
#define BALM_METRICS_UPDATE(CATEGORY, METRIC1, VALUE1) do {                   \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::Collector> collector1;                    \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getCollector(CATEGORY, METRIC1);                  \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_UPDATE2(CATEGORY, METRIC1, VALUE1, METRIC2, VALUE2) do { \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::Collector> collector1;                    \
   static bsls::AtomicPointer<balm::Collector> collector2;                    \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC2, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getCollector(CATEGORY, METRIC1);                  \
       collector2 = Helper::getCollector(CATEGORY, METRIC2);                  \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
       collector2->update(VALUE2);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_UPDATE3(CATEGORY,                                        \
                             METRIC1,                                         \
                             VALUE1,                                          \
                             METRIC2,                                         \
                             VALUE2,                                          \
                             METRIC3,                                         \
                             VALUE3) do {                                     \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::Collector> collector1;                    \
   static bsls::AtomicPointer<balm::Collector> collector2;                    \
   static bsls::AtomicPointer<balm::Collector> collector3;                    \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC2, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC3, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getCollector(CATEGORY, METRIC1);                  \
       collector2 = Helper::getCollector(CATEGORY, METRIC2);                  \
       collector3 = Helper::getCollector(CATEGORY, METRIC3);                  \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
       collector2->update(VALUE2);                                            \
       collector3->update(VALUE3);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_UPDATE4(CATEGORY,                                        \
                             METRIC1,                                         \
                             VALUE1,                                          \
                             METRIC2,                                         \
                             VALUE2,                                          \
                             METRIC3,                                         \
                             VALUE3,                                          \
                             METRIC4,                                         \
                             VALUE4) do {                                     \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::Collector> collector1;                    \
   static bsls::AtomicPointer<balm::Collector> collector2;                    \
   static bsls::AtomicPointer<balm::Collector> collector3;                    \
   static bsls::AtomicPointer<balm::Collector> collector4;                    \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC2, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC3, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC4, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getCollector(CATEGORY, METRIC1);                  \
       collector2 = Helper::getCollector(CATEGORY, METRIC2);                  \
       collector3 = Helper::getCollector(CATEGORY, METRIC3);                  \
       collector4 = Helper::getCollector(CATEGORY, METRIC4);                  \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
       collector2->update(VALUE2);                                            \
       collector3->update(VALUE3);                                            \
       collector4->update(VALUE4);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_UPDATE5(CATEGORY,                                        \
                             METRIC1,                                         \
                             VALUE1,                                          \
                             METRIC2,                                         \
                             VALUE2,                                          \
                             METRIC3,                                         \
                             VALUE3,                                          \
                             METRIC4,                                         \
                             VALUE4,                                          \
                             METRIC5,                                         \
                             VALUE5) do {                                     \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::Collector> collector1;                    \
   static bsls::AtomicPointer<balm::Collector> collector2;                    \
   static bsls::AtomicPointer<balm::Collector> collector3;                    \
   static bsls::AtomicPointer<balm::Collector> collector4;                    \
   static bsls::AtomicPointer<balm::Collector> collector5;                    \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC2, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC3, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC4, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC5, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getCollector(CATEGORY, METRIC1);                  \
       collector2 = Helper::getCollector(CATEGORY, METRIC2);                  \
       collector3 = Helper::getCollector(CATEGORY, METRIC3);                  \
       collector4 = Helper::getCollector(CATEGORY, METRIC4);                  \
       collector5 = Helper::getCollector(CATEGORY, METRIC5);                  \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
       collector2->update(VALUE2);                                            \
       collector3->update(VALUE3);                                            \
       collector4->update(VALUE4);                                            \
       collector5->update(VALUE5);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_UPDATE6(CATEGORY,                                        \
                             METRIC1,                                         \
                             VALUE1,                                          \
                             METRIC2,                                         \
                             VALUE2,                                          \
                             METRIC3,                                         \
                             VALUE3,                                          \
                             METRIC4,                                         \
                             VALUE4,                                          \
                             METRIC5,                                         \
                             VALUE5,                                          \
                             METRIC6,                                         \
                             VALUE6) do {                                     \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::Collector> collector1;                    \
   static bsls::AtomicPointer<balm::Collector> collector2;                    \
   static bsls::AtomicPointer<balm::Collector> collector3;                    \
   static bsls::AtomicPointer<balm::Collector> collector4;                    \
   static bsls::AtomicPointer<balm::Collector> collector5;                    \
   static bsls::AtomicPointer<balm::Collector> collector6;                    \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY, Helper::e_TYPE_CATEGORY,                  \
                            __FILE__, __LINE__);                              \
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC2, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC3, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC4, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC5, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC6, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getCollector(CATEGORY, METRIC1);                  \
       collector2 = Helper::getCollector(CATEGORY, METRIC2);                  \
       collector3 = Helper::getCollector(CATEGORY, METRIC3);                  \
       collector4 = Helper::getCollector(CATEGORY, METRIC4);                  \
       collector5 = Helper::getCollector(CATEGORY, METRIC5);                  \
       collector6 = Helper::getCollector(CATEGORY, METRIC6);                  \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
       collector2->update(VALUE2);                                            \
       collector3->update(VALUE3);                                            \
       collector4->update(VALUE4);                                            \
       collector5->update(VALUE5);                                            \
       collector6->update(VALUE6);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_TYPED_UPDATE(CATEGORY, METRIC, VALUE, PREFERRED_TYPE)    \
 do {                                                                         \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::Collector> collector1;                    \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC, Helper::e_TYPE_METRIC, __FILE__, __LINE__); \
       collector1 = Helper::getCollector(CATEGORY, METRIC);                   \
       Helper::setPublicationType(collector1->metricId(), PREFERRED_TYPE);    \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE);                                             \
   }                                                                          \
 } while (0)

#define BALM_METRICS_DYNAMIC_UPDATE(CATEGORY, METRIC, VALUE) do {             \
    using namespace BloombergLP;                                              \
    if (balm::DefaultMetricsManager::instance()) {                            \
        balm::CollectorRepository& repository =                               \
             balm::DefaultMetricsManager::instance()->collectorRepository();  \
        balm::Collector *collector = repository.getDefaultCollector(          \
                                                        (CATEGORY), (METRIC));\
        if (collector->metricId().category()->enabled()) {                    \
            collector->update((VALUE));                                       \
        }                                                                     \
    }                                                                         \
  } while(0)

                        // =======================
                        // BALM_METRICS_INT_UPDATE
                        // =======================

#define BALM_METRICS_INT_UPDATE(CATEGORY, METRIC1, VALUE1) do {               \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::IntegerCollector> collector1;             \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getIntegerCollector(CATEGORY, METRIC1);           \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_INT_UPDATE2(CATEGORY,                                    \
                                 METRIC1,                                     \
                                 VALUE1,                                      \
                                 METRIC2,                                     \
                                 VALUE2) do {                                 \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::IntegerCollector> collector1;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector2;             \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC2, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getIntegerCollector(CATEGORY, METRIC1);           \
       collector2 = Helper::getIntegerCollector(CATEGORY, METRIC2);           \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
       collector2->update(VALUE2);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_INT_UPDATE3(CATEGORY,                                    \
                                 METRIC1,                                     \
                                 VALUE1,                                      \
                                 METRIC2,                                     \
                                 VALUE2,                                      \
                                 METRIC3,                                     \
                                 VALUE3) do {                                 \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::IntegerCollector> collector1;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector2;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector3;             \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC2, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC3, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getIntegerCollector(CATEGORY, METRIC1);           \
       collector2 = Helper::getIntegerCollector(CATEGORY, METRIC2);           \
       collector3 = Helper::getIntegerCollector(CATEGORY, METRIC3);           \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
       collector2->update(VALUE2);                                            \
       collector3->update(VALUE3);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_INT_UPDATE4(CATEGORY,                                    \
                                 METRIC1,                                     \
                                 VALUE1,                                      \
                                 METRIC2,                                     \
                                 VALUE2,                                      \
                                 METRIC3,                                     \
                                 VALUE3,                                      \
                                 METRIC4,                                     \
                                 VALUE4) do {                                 \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::IntegerCollector> collector1;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector2;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector3;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector4;             \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC2, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC3, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC4, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getIntegerCollector(CATEGORY, METRIC1);           \
       collector2 = Helper::getIntegerCollector(CATEGORY, METRIC2);           \
       collector3 = Helper::getIntegerCollector(CATEGORY, METRIC3);           \
       collector4 = Helper::getIntegerCollector(CATEGORY, METRIC4);           \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
       collector2->update(VALUE2);                                            \
       collector3->update(VALUE3);                                            \
       collector4->update(VALUE4);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_INT_UPDATE5(CATEGORY,                                    \
                                 METRIC1,                                     \
                                 VALUE1,                                      \
                                 METRIC2,                                     \
                                 VALUE2,                                      \
                                 METRIC3,                                     \
                                 VALUE3,                                      \
                                 METRIC4,                                     \
                                 VALUE4,                                      \
                                 METRIC5,                                     \
                                 VALUE5) do {                                 \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::IntegerCollector> collector1;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector2;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector3;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector4;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector5;             \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC2, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC3, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC4, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC5, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getIntegerCollector(CATEGORY, METRIC1);           \
       collector2 = Helper::getIntegerCollector(CATEGORY, METRIC2);           \
       collector3 = Helper::getIntegerCollector(CATEGORY, METRIC3);           \
       collector4 = Helper::getIntegerCollector(CATEGORY, METRIC4);           \
       collector5 = Helper::getIntegerCollector(CATEGORY, METRIC5);           \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
       collector2->update(VALUE2);                                            \
       collector3->update(VALUE3);                                            \
       collector4->update(VALUE4);                                            \
       collector5->update(VALUE5);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_INT_UPDATE6(CATEGORY,                                    \
                                 METRIC1,                                     \
                                 VALUE1,                                      \
                                 METRIC2,                                     \
                                 VALUE2,                                      \
                                 METRIC3,                                     \
                                 VALUE3,                                      \
                                 METRIC4,                                     \
                                 VALUE4,                                      \
                                 METRIC5,                                     \
                                 VALUE5,                                      \
                                 METRIC6,                                     \
                                 VALUE6) do {                                 \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::IntegerCollector> collector1;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector2;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector3;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector4;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector5;             \
   static bsls::AtomicPointer<balm::IntegerCollector> collector6;             \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC1, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC2, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC3, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC4, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC5, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
     Helper::logEmptyName(METRIC6, Helper::e_TYPE_METRIC, __FILE__, __LINE__);\
       collector1 = Helper::getIntegerCollector(CATEGORY, METRIC1);           \
       collector2 = Helper::getIntegerCollector(CATEGORY, METRIC2);           \
       collector3 = Helper::getIntegerCollector(CATEGORY, METRIC3);           \
       collector4 = Helper::getIntegerCollector(CATEGORY, METRIC4);           \
       collector5 = Helper::getIntegerCollector(CATEGORY, METRIC5);           \
       collector6 = Helper::getIntegerCollector(CATEGORY, METRIC6);           \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE1);                                            \
       collector2->update(VALUE2);                                            \
       collector3->update(VALUE3);                                            \
       collector4->update(VALUE4);                                            \
       collector5->update(VALUE5);                                            \
       collector6->update(VALUE6);                                            \
   }                                                                          \
 } while (0)

#define BALM_METRICS_TYPED_INT_UPDATE(CATEGORY,                               \
                                      METRIC,                                 \
                                      VALUE,                                  \
                                      PREFERRED_TYPE)                         \
do {                                                                          \
   using namespace BloombergLP;                                               \
   typedef balm::Metrics_Helper Helper;                                       \
   static balm::CategoryHolder holder = { false, 0, 0 };                      \
   static bsls::AtomicPointer<balm::IntegerCollector> collector1;             \
   if (0 == holder.category() && balm::DefaultMetricsManager::instance()) {   \
     static bsls::SpinLock lock = BSLS_SPINLOCK_UNLOCKED;                     \
     bsls::SpinLockGuard guard(&lock);                                        \
     if (0 == holder.category()) {                                            \
     Helper::logEmptyName(CATEGORY,Helper::e_TYPE_CATEGORY,__FILE__,__LINE__);\
     Helper::logEmptyName(METRIC, Helper::e_TYPE_METRIC, __FILE__, __LINE__); \
       collector1 = Helper::getIntegerCollector(CATEGORY, METRIC);            \
       Helper::setPublicationType(collector1->metricId(), PREFERRED_TYPE);    \
       Helper::initializeCategoryHolder(&holder, CATEGORY);                   \
     }                                                                        \
   }                                                                          \
   if (holder.enabled()) {                                                    \
       collector1->update(VALUE);                                             \
   }                                                                          \
 } while (0)

#define BALM_METRICS_DYNAMIC_INT_UPDATE(CATEGORY, METRIC, VALUE) do {         \
    using namespace BloombergLP;                                              \
    if (balm::DefaultMetricsManager::instance()) {                            \
        balm::CollectorRepository& repository =                               \
             balm::DefaultMetricsManager::instance()->collectorRepository();  \
        balm::IntegerCollector *collector =                                   \
               repository.getDefaultIntegerCollector((CATEGORY), (METRIC));   \
        if (collector->metricId().category()->enabled()) {                    \
            collector->update((VALUE));                                       \
        }                                                                     \
    }                                                                         \
  } while (0)

#define BALM_METRICS_INCREMENT(CATEGORY, METRIC)                              \
    BALM_METRICS_INT_UPDATE(CATEGORY, METRIC, 1)

#define BALM_METRICS_TYPED_INCREMENT(CATEGORY, METRIC, PREFERRED_TYPE)        \
    BALM_METRICS_TYPED_INT_UPDATE(CATEGORY, METRIC, 1, PREFERRED_TYPE)

#define BALM_METRICS_DYNAMIC_INCREMENT(CATEGORY, METRIC)                      \
    BALM_METRICS_DYNAMIC_INT_UPDATE(CATEGORY, METRIC, 1)

                        // =======================
                        // BALM_METRICS_TIME_BLOCK
                        // =======================

#define BALM_METRICS_TIME_BLOCK(CATEGORY, METRIC, TIME_UNITS)                 \
  BALM_METRICS_TIME_BLOCK_IMP((CATEGORY),                                     \
                               (METRIC),                                      \
                               TIME_UNITS,                                    \
                               BALM_METRICS_UNIQUE_NAME(_bAlM_CoLlEcToR))

#define BALM_METRICS_DYNAMIC_TIME_BLOCK(CATEGORY, METRIC, TIME_UNITS)         \
  BALM_METRICS_DYNAMIC_TIME_BLOCK_IMP(                                        \
                                  (CATEGORY),                                 \
                                  (METRIC),                                   \
                                  TIME_UNITS,                                 \
                                  BALM_METRICS_UNIQUE_NAME(_bAlM_CoLlEcToR))

#define BALM_METRICS_TIME_BLOCK_SECONDS(CATEGORY, METRIC)                     \
  BALM_METRICS_TIME_BLOCK((CATEGORY),                                         \
                          (METRIC),                                           \
                          BloombergLP::balm::StopwatchScopedGuard::k_SECONDS);

#define BALM_METRICS_TIME_BLOCK_MILLISECONDS(CATEGORY, METRIC)                \
  BALM_METRICS_TIME_BLOCK(                                                    \
                      (CATEGORY),                                             \
                      (METRIC),                                               \
                      BloombergLP::balm::StopwatchScopedGuard::k_MILLISECONDS);

#define BALM_METRICS_TIME_BLOCK_MICROSECONDS(CATEGORY, METRIC)                \
  BALM_METRICS_TIME_BLOCK(                                                    \
                      (CATEGORY),                                             \
                      (METRIC),                                               \
                      BloombergLP::balm::StopwatchScopedGuard::k_MICROSECONDS);

#define BALM_METRICS_TIME_BLOCK_NANOSECONDS(CATEGORY, METRIC)                 \
  BALM_METRICS_TIME_BLOCK(                                                    \
                       (CATEGORY),                                            \
                       (METRIC),                                              \
                       BloombergLP::balm::StopwatchScopedGuard::k_NANOSECONDS);

#define BALM_METRICS_DYNAMIC_TIME_BLOCK_SECONDS(CATEGORY, METRIC)             \
  BALM_METRICS_DYNAMIC_TIME_BLOCK(                                            \
                           (CATEGORY),                                        \
                           (METRIC),                                          \
                           BloombergLP::balm::StopwatchScopedGuard::k_SECONDS);

#define BALM_METRICS_DYNAMIC_TIME_BLOCK_MILLISECONDS(CATEGORY, METRIC)        \
  BALM_METRICS_DYNAMIC_TIME_BLOCK(                                            \
                      (CATEGORY),                                             \
                      (METRIC),                                               \
                      BloombergLP::balm::StopwatchScopedGuard::k_MILLISECONDS);

#define BALM_METRICS_DYNAMIC_TIME_BLOCK_MICROSECONDS(CATEGORY, METRIC)        \
  BALM_METRICS_DYNAMIC_TIME_BLOCK(                                            \
                      (CATEGORY),                                             \
                      (METRIC),                                               \
                      BloombergLP::balm::StopwatchScopedGuard::k_MICROSECONDS);

#define BALM_METRICS_DYNAMIC_TIME_BLOCK_NANOSECONDS(CATEGORY, METRIC)         \
  BALM_METRICS_DYNAMIC_TIME_BLOCK(                                            \
                       (CATEGORY),                                            \
                       (METRIC),                                              \
                       BloombergLP::balm::StopwatchScopedGuard::k_NANOSECONDS);

                        // =====================
                        // Macro Implementations
                        // =====================

#define BALM_METRICS_IF_CATEGORY_ENABLED_IMP(CATEGORY, HOLDER_NAME)           \
    static BloombergLP::balm::CategoryHolder HOLDER_NAME = { false, 0, 0 };   \
    if (BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(!HOLDER_NAME.category())        \
     && BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(                                \
                 BloombergLP::balm::DefaultMetricsManager::instance() != 0)) {\
        BloombergLP::balm::Metrics_Helper::initializeCategoryHolder(          \
                                                   &HOLDER_NAME, CATEGORY);   \
    }                                                                         \
    if (HOLDER_NAME.enabled())

// Declare a static pointer to a 'balm::Collector' with the specified
// 'VARIABLE_NAME' and an initial value of 0.  If the default metrics manager
// is available and the declared pointer variable (named 'VARIABLE_NAME') is 0,
// assign to 'VARIABLE_NAME' the address of a collector for the specified
// 'CATEGORY' and 'METRIC'.  Finally, declare a 'balm::StopwatchScopedGuard'
// object with a unique variable name and supply its constructor the collector
// address held in 'VARIABLE_NAME' and the specified 'TIME_UNITS'.
#define BALM_METRICS_TIME_BLOCK_IMP(CATEGORY,                                 \
                                    METRIC,                                   \
                                    TIME_UNITS,                               \
                                    VARIABLE_NAME)                            \
    static BloombergLP::balm::Collector *VARIABLE_NAME = 0;                   \
    if (BloombergLP::balm::DefaultMetricsManager::instance()) {               \
       using namespace BloombergLP;                                           \
       typedef balm::Metrics_Helper Helper;                                   \
       static balm::CategoryHolder holder = { false, 0, 0 };                  \
       if (0 == holder.category()) {                                          \
           balm::CollectorRepository& repository =                            \
              balm::DefaultMetricsManager::instance()->collectorRepository(); \
           VARIABLE_NAME = repository.getDefaultCollector((CATEGORY),         \
                                                          (METRIC));          \
           Helper::initializeCategoryHolder(&holder, CATEGORY);               \
       }                                                                      \
    }                                                                         \
    else {                                                                    \
       VARIABLE_NAME = 0;                                                     \
    }                                                                         \
    BloombergLP::balm::StopwatchScopedGuard                                   \
         BALM_METRICS_UNIQUE_NAME(__bAlM_gUaRd)(VARIABLE_NAME, TIME_UNITS);

// Declare a pointer to a 'balm::Collector' with the specified 'VARIABLE_NAME'.
// If the default metrics manager is available, assign to the declared pointer
// variable (named 'VARIABLE_NAME') the address of a collector for the
// specified 'CATEGORY' and 'METRIC'.  Finally, declare a
// 'balm::StopwatchScopedGuard' object with a unique variable name and supply
// its constructor the collector address held in 'VARIABLE_NAME' and the
// specified 'TIME_UNITS'.
#define BALM_METRICS_DYNAMIC_TIME_BLOCK_IMP(CATEGORY,                         \
                                            METRIC,                           \
                                            TIME_UNITS,                       \
                                            VARIABLE_NAME)                    \
    BloombergLP::balm::Collector *VARIABLE_NAME = 0;                          \
    if (BloombergLP::balm::DefaultMetricsManager::instance()) {               \
        using namespace BloombergLP;                                          \
        balm::CollectorRepository& repository =                               \
             balm::DefaultMetricsManager::instance()->collectorRepository();  \
        VARIABLE_NAME = repository.getDefaultCollector((CATEGORY),            \
                                                       (METRIC));             \
    }                                                                         \
    BloombergLP::balm::StopwatchScopedGuard                                   \
         BALM_METRICS_UNIQUE_NAME(__bAlM_gUaRd)(VARIABLE_NAME, TIME_UNITS);

                        // ------------------------
                        // Unique line number macro
                        // ------------------------

#if defined(BSLS_PLATFORM_CMP_MSVC)
// MSVC: __LINE__ macro breaks when /ZI is used (see Q199057 or KB199057)
// Fortunately the __COUNTER__ extension provided by MSVC is even better.
#   define BALM_METRICS_UNIQNUM __COUNTER__
#else
#   define BALM_METRICS_UNIQNUM __LINE__
#endif

                        // ----------------------------------
                        // Token concatenation support macros
                        // ----------------------------------

// Second layer needed to ensure that arguments are expanded before
// concatenation.
#define BALM_METRICS_CAT(X, Y) BALM_METRICS_CAT_IMP(X, Y)
#define BALM_METRICS_CAT_IMP(X, Y) X##Y

                        // ----------------------------------
                        // Unique variable name support macro
                        // ----------------------------------

// Create a unique variable name by concatenating the specified 'X' string
// with a unique integer value.
#define BALM_METRICS_UNIQUE_NAME(X)                                           \
           BALM_METRICS_CAT(X, BALM_METRICS_UNIQNUM)

namespace BloombergLP {

namespace balm {
                           // =====================
                           // struct Metrics_Helper
                           // =====================

/// This `struct` provides a namespace for a suite of functions used in the
/// implementation of the macros defined in this component.
///
/// This type is an implementation detail and *must* *not* be used
/// (directly) by clients outside of this component.
struct Metrics_Helper {

    // TYPES
    enum NameType {
        // Enumeration indicating the type of identifier supplied to
        // 'logEmptyName'.

        e_TYPE_CATEGORY = 0,
        e_TYPE_METRIC   = 1
#ifndef BDE_OMIT_INTERNAL_DEPRECATED
        , TYPE_CATEGORY = e_TYPE_CATEGORY
        , TYPE_METRIC   = e_TYPE_METRIC
#endif
    };

    // CLASS METHODS

    /// Load into the specified `holder` the address and enabled status of
    /// the specified `category`, and add `holder` to the list of category
    /// holders for `category`.  The behavior is undefined unless the balm
    /// metrics manager singleton is valid.
    static void initializeCategoryHolder(CategoryHolder *holder,
                                         const char     *category);

    /// Return the address of the default metrics collector for the metric
    /// identified by the specified `category` and `metric` names.  The
    /// behavior is undefined unless the `balm` metrics manager singleton is
    /// valid.
    static Collector *getCollector(const char *category,
                                   const char *metric);

    /// Return the address of the default integer metrics collector for the
    /// metric identified by the specified `category` and `metric` names.
    /// The behavior is undefined unless the `balm` metrics manager
    /// singleton is valid.
    static IntegerCollector *getIntegerCollector(const char *category,
                                                 const char *metric);

    /// Set the publication type for the metric identified by the specified
    /// `id` to the specified `type`.  The behavior is undefined unless the
    /// `balm` metrics manager singleton is valid, and `id` is a valid
    /// identifier supplied by the singleton metrics manager.
    static void setPublicationType(const MetricId&        id,
                                   PublicationType::Value type);

    /// If the specified `name` is empty or contains only whitespace, then
    /// log a warning message indicating whether `name` is a category or a
    /// metric, depending on the specified `type`, and the location at which
    /// this function is called, indicated by the specified `file` and
    /// `line`; otherwise, do nothing.
    static void logEmptyName(const char *name,
                             NameType    type,
                             const char *file,
                             int         line);
};

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

                           // ---------------------
                           // struct Metrics_Helper
                           // ---------------------

// CLASS METHODS
inline
void Metrics_Helper::initializeCategoryHolder(CategoryHolder *holder,
                                              const char     *category)
{
    MetricsManager  *manager  = DefaultMetricsManager::instance();
    MetricRegistry&  registry = manager->metricRegistry();
    registry.registerCategoryHolder(registry.getCategory(category), holder);
}

inline
Collector *Metrics_Helper::getCollector(const char *category,
                                        const char *metric)
{
    MetricsManager *manager = DefaultMetricsManager::instance();
    return manager->collectorRepository().getDefaultCollector(category,
                                                              metric);
}

inline
IntegerCollector *Metrics_Helper::getIntegerCollector(const char *category,
                                                      const char *metric)
{
    MetricsManager *manager = DefaultMetricsManager::instance();
    return manager->collectorRepository().getDefaultIntegerCollector(category,
                                                                     metric);
}

inline
void Metrics_Helper::setPublicationType(const MetricId&        id,
                                        PublicationType::Value type)
{
    MetricsManager *manager = DefaultMetricsManager::instance();
    return manager->metricRegistry().setPreferredPublicationType(id, type);

}

}  // close package namespace
}  // close enterprise namespace

#if !defined(BSL_DOUBLE_UNDERSCORE_XLAT) || 1 == BSL_DOUBLE_UNDERSCORE_XLAT
#define BALM_METRICS__UNIQUE_NAME(X) BALM_METRICS_UNIQUE_NAME(X)
#endif

#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 ----------------------------------
