// balm_metricsmanager.h                                              -*-C++-*-
#ifndef INCLUDED_BALM_METRICSMANAGER
#define INCLUDED_BALM_METRICSMANAGER

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

//@PURPOSE: Provide a manager for recording and publishing metric data.
//
//@CLASSES:
//  balm::MetricsManager: manager for recording and publishing metric data
//
//@SEE_ALSO: balm_publisher, balm_collectorrepository, balm_metricregistry,
//           balm_metric, balm_defaultmetricsmanager, balm_publicationscheduler
//
//@DESCRIPTION: This component provides a `balm::MetricsManager` class for
// managing the recording and publishing of metric data.  The metrics manager
// retrieves `balm::MetricRecords` from both the collector repository it owns
// as well as any `RecordsCollectionCallbacks` registered with it.  The metrics
// manager also provides methods to register `balm::Publisher` objects.  The
// `publish` method collects metrics for a category (or set of categories) and
// then sends the collected metrics to the publishers associated with that
// category (or set of categories).
//
// Note that a metric in this context is an event associated with a measured
// value.  This component does *not* define what constitutes an event or what
// the associated value represents.  A collected metric contains the count of
// event occurrences along with the total, minimum, and maximum aggregates of
// the measured values.
//
///Alternative Systems for Telemetry
///---------------------------------
// Bloomberg software may alternatively use the GUTS telemetry API, which is
// integrated into Bloomberg infrastructure.
//
///Thread Safety
///-------------
// `balm::MetricsManager` is fully *thread-safe*, meaning that all non-creator
// operations on a given instance can be safely invoked simultaneously from
// multiple threads.  To avoid synchronization problems with user functions
// invoked by `balm::MetricsManager`, special consideration must be taken when
// implementing these functions as specified below.
//
///Registered Concrete `balm::Publisher` Implementations
///- - - - - - - - - - - - - - - - - - - - - - - - - - -
// Concrete implementations of the `balm::Publisher` protocol (pure abstract
// base-class) registered with a `balm::MetricsManager` object must *not* call
// (either directly or indirectly) the `publish` method on the
// `balm::MetricsManager` object with which they are registered.
//
///Registered `RecordsCollectionCallback` Implementations
/// - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Implementations of `balm::MetricsManager::RecordsCollectionCallback`
// registered with a `balm::MetricsManager` will be invoked by a function
// holding mutex locks that provide synchronized access to data in that
// `balm::MetricsManager`.  Therefore registered implementations of
// `RecordsCollectionCallback` must *not* make any re-entrant calls (either
// directly or indirectly) to member functions of the `balm::MetricManager`
// object with which they are registered.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Initialize a `balm::MetricsManager`
/// - - - - - - - - - - - - - - - - - - - - - - -
// This example demonstrates how to create and configure a
// `balm::MetricsManager` that we will use to record and publish metric values.
// We first create a `balm::MetricsManager` object and a
// `SimpleStreamPublisher` object.  Note that `SimpleStreamPublisher` is an
// example implementation of the `balm::Publisher` protocol defined in the
// `balm_publisher` component.  In practice, clients typically use a standard
// publisher class (e.g., `balm::StreamPublisher`).
// ```
// int main(int argc, char *argv[]) {
// {
//
//     // ...
//
//     bslma::Allocator   *allocator = bslma::Default::allocator(0);
//     balm::MetricsManager manager(allocator);
//
//     bsl::shared_ptr<balm::Publisher> publisher(
//                         new (*allocator) SimpleStreamPublisher(bsl::cout),
//                         allocator);
//     manager.addGeneralPublisher(publisher);
//
//     // ...
//
// ```
//
///Example 2: Recording Metric Values with `balm::Collector`
///- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// This second example demonstrates using `balm::Collector` objects (obtained
// from a metrics manager's collector repository) to collect metrics related to
// a hypothetical `EventHandler` class.  On construction, the event handler
// obtains references to `balm::Collector` objects from the metrics manager's
// collector repository.  On each handled event, the `EventHandler`, updates
// its collectors with the appropriate metric values.
//
// Note that the `balm_metric` component provides both classes and macros to
// reduce the code required for collecting metric values.
// ```
// class EventHandler {
//     // Provide an example event-handling mechanism that records metrics
//     // for (1) the size of the processed event messages and (2) the number
//     // of failures, using 'balm::Collector' objects provided by a
//     // 'balm::MetricsManager'.
//
//     // PRIVATE DATA
//     balm::Collector *d_eventMessageSizes_p;  // collect the message sizes
//
//     balm::Collector *d_eventFailures_p;      // collect the number of
//                                              // failures
//
// // ...
//
//   public:
//     // CREATORS
// ```
// We obtain the addresses of the respective `balm::Collector` objects that we
// will use to collect metrics values from the metrics managers' collector
// repository.  Note that, in practice, clients can use the
// `balm::DefaultMetricManager` (see `balm_defaultmetricsmanager` and
// `balm_metric`) rather than explicitly pass the address of a metrics manager.
// ```
//     EventHandler(balm::MetricsManager *manager)
//     : d_eventMessageSizes_p(
//            manager->collectorRepository().getDefaultCollector(
//                                             "MyCategory", "messageSizes"))
//     , d_eventFailures_p(
//            manager->collectorRepository().getDefaultCollector(
//                                             "MyCategory", "eventFailures"))
//     {}
//
//     // MANIPULATORS
// ```
// Then, when processing an "event", we update the `balm::Collector` objects
// with the appropriate metric values for the event.
// ```
//     int handleEvent(int eventId, const bsl::string& eventMessage)
//         // Process the event described by the specified 'eventId' and
//         // 'eventMessage' .  Return 0 on success, and a non-zero value
//         // if there was an error handling the event.
//     {
//        int returnCode = 0;
//        d_eventMessageSizes_p->update(
//                                  static_cast<double>(eventMessage.size()));
//
// // ...    (Process the event)
//        (void)eventId;
//
//        if (0 != returnCode) {
//            d_eventFailures_p->update(1);
//        }
//        return returnCode;
//     }
//
// // ...
//
// };
// ```
//
///Example 3: Recording Metric Values with a Callback
/// - - - - - - - - - - - - - - - - - - - - - - - - -
// The metrics manager provides a facility to register a callback that will
// report metric values.  A callback should be used if clients want to
// customize how a metric, or group of metrics, are recorded.  In the following
// example, the `EventHandlerWithCallback` class maintains a metric for the
// average number of events per second that it reports through a
// `balm::MetricsManager::MetricsCollectionCallback`.
// ```
// // eventhandlerwithcallback.h
//
// class EventHandlerWithCallback {
//     // Provide an example event handling mechanism that records a
//     // metric for the number of events per second and reports that metric
//     // using a 'balm::MetricsManager::RecordsCollectionCallback'.
//
//     // PRIVATE DATA
//     bsls::AtomicInt       d_numEvents;         // number of events
//
//     bsls::TimeInterval    d_periodStart;       // start of the current
//                                                // period
//
//     balm::MetricId        d_eventsPerSecId;    // identifies the events-
//                                                // per-second metric
//     balm::MetricsManager::CallbackHandle
//                           d_callbackHandle;    // identifies the callback
//
//     balm::MetricsManager *d_metricsManager_p;  // metrics manager (held,
//                                                // but not owned)
//  // ...
//
//     // PRIVATE MANIPULATORS
//     void collectMetricsCb(bsl::vector<balm::MetricRecord> *records,
//                           bool                             resetFlag);
//         // Append to the specified 'records' the aggregated values of the
//         // metrics recorded by this event handler and, if 'resetFlag' is
//         // 'true', reset those metric values to their default state.  Note
//         // that this method is intended to be used as a callback, and is
//         // consistent with the
//         // 'balm::MetricsManager::RecordsCollectionCallback' function
//         // prototype.
//
//   public:
//     // CREATORS
//     EventHandlerWithCallback(balm::MetricsManager *manager,
//                              bslma::Allocator    *basicAllocator = 0);
//         // Initialize this object to use the specified 'manager' to record
//         // and publish metrics.  Optionally specify a 'basicAllocator'
//         // used to supply memory.  If 'basicAllocator' is 0, the currently
//         // installed default allocator is used.
//
//     ~EventHandlerWithCallback();
//         // Destroy this event handler.
//
//     // MANIPULATORS
//     int handleEvent(int eventId, const bsl::string& eventMessage);
//         // Process the event described by the specified 'eventId' and
//         // 'eventMessage'.  Return 0 on success, and a non-zero value if
//         // there was an error processing the event.
//
// // ...
//
// };
// ```
// In the implementation of `EventHandlerWithCallback` below, we ensure that
// the callback is registered on construction and removed before the object is
// destroyed.
// ```
// // eventhandlerwithcallback.cpp
//
// namespace {
//
// const char *METRIC_CATEGORY = "MyCategory";
//
// }
//
// ```
// The callback creates metric records and populates them with data collected
// by the event handler.
// ```
// // PRIVATE MANIPULATORS
// void EventHandlerWithCallback::collectMetricsCb(
//                                 bsl::vector<balm::MetricRecord> *records,
//                                 bool                             resetFlag)
// {
//     int numEvents = resetFlag ?
//                     (int)d_numEvents.swap(0) :
//                     (int)d_numEvents;
//     bsls::TimeInterval now         = bdlt::CurrentTime::now();
//     bsls::TimeInterval elapsedTime = now - d_periodStart;
//     d_periodStart = now;
//
//     balm::MetricRecord record(d_eventsPerSecId);
//     record.count() = 1;
//     record.total() = numEvents / elapsedTime.totalSecondsAsDouble();
//
//     records->push_back(record);
// }
//
// ```
// In the constructor, we initialize a metric id from the specified `manager`
// object's metric registry.  We will also register the collection callback
// (`collectMetricsCb`) with the supplied `manager`.
// ```
// // CREATORS
// EventHandlerWithCallback::EventHandlerWithCallback(
//                                       balm::MetricsManager *manager,
//                                       bslma::Allocator     *basicAllocator)
// : d_numEvents(0)
// , d_periodStart(bdlt::CurrentTime::now())
// , d_eventsPerSecId()
// , d_callbackHandle(balm::MetricsManager::e_INVALID_HANDLE)
// , d_metricsManager_p(manager)
// {
//     (void)basicAllocator;
//
//     d_eventsPerSecId = d_metricsManager_p->metricRegistry().getId(
//                                       METRIC_CATEGORY, "eventsPerSecond");
// ```
// We now register the callback function `collectMetricsCb` with the metrics
// manager.  We use `bdlf::BindUtil` to bind the member function to a
// `bsl::function` matching the
// `balm::MetricsManager::RecordsCollectionCallback` function prototype.  The
// private data member `d_callbackHandle` is used to store the
// `balm::MetricsManager::CallbackHandle` returned for the registered callback;
// we will use this handle later to remove the callback from the metrics
// manager.
// ```
//     d_callbackHandle =
//        d_metricsManager_p->registerCollectionCallback(
//           METRIC_CATEGORY,
//           bdlf::BindUtil::bindA(basicAllocator,
//                                &EventHandlerWithCallback::collectMetricsCb,
//                                this,
//                                bdlf::PlaceHolders::_1,
//                                bdlf::PlaceHolders::_2));
// }
//
// ```
// In the destructor we use the `balm::MetricsManager::CallbackHandle`, stored
// in `d_callbackHandle`, to remove the callback from the metrics manager.
// This prevents the metrics manager from invoking the callback method on an
// object that has already been destroyed.
// ```
// EventHandlerWithCallback::~EventHandlerWithCallback()
// {
//     int rc =
//            d_metricsManager_p->removeCollectionCallback(d_callbackHandle);
//     assert(0 == rc);
// }
//
// // MANIPULATORS
// int EventHandlerWithCallback::handleEvent(int                eventId,
//                                           const bsl::string& eventMessage)
// {
// ```
// We increment the atomic integer `d_numEvents` to keep track of the number
// events handled by the `handleEvent` method.  If collecting a metric is
// expensive (e.g., metrics requiring a system call to collect), clients should
// test whether the metric is enabled before performing the collection
// operation.
// ```
//     // We don't test 'd_eventsPerSecId.category()->enabled())' before
//     // incrementing 'd_numEvents' because, in this instance, it will not
//     // improve performance.
//     ++d_numEvents;
//
// // ...    (Process the event)
//     (void)eventId;
//     (void)eventMessage;
//
//     return 0;
//  }
//
// // ...
// ```
//
///Example 4: Publishing a Metric
/// - - - - - - - - - - - - - - -
// The metrics manager provides a `publish` operation to publish metrics for a
// category or set of categories.  In this example we will use the metrics
// manager's `publishAll` operation to publish all the metrics managed by the
// metrics manager.  We will record metrics for "MyCategory" using instances of
// the `EventHandler` and `EventHandlerWithCallback` classes (defined above).
// This example assumes that an instance, `manager`, of the
// `balm::MetricsManager` class has been initialized as in example 1.  Note
// that, in practice the publish operation is normally tied to a scheduling
// mechanism (e.g., see `balm_publicationscheduler`).
// ```
// EventHandler             eventHandler(&manager);
// EventHandlerWithCallback eventHandlerWithCallback(&manager);
//
// eventHandler.handleEvent(0, "A 28 character event message");
// eventHandlerWithCallback.handleEvent(1, "A different event message");
// manager.publishAll();
//
// eventHandler.handleEvent(0, "A 28 character event message");
// eventHandler.handleEvent(0, "A 28 character event message");
// eventHandlerWithCallback.handleEvent(1, "A different event message");
// manager.publishAll();
// ```
// Executing the example should result in two samples being published to
// standard output.  Each sample should contain 3 metrics belonging to the
// metric category "MyCategory".  The metric "eventsPerSecond" is collected by
// the `EventHandlerWithCallback`, while "messageSizes", and "eventFailures"
// (both collected by `EventHandler`).
// ```
// 09FEB2009_18:52:51.093+0000 3 Records
//         Elapsed Time: 0.001358s
//         MyCategory.eventsPerSecond [count = 1, total = 2267.57, ... ]
//         MyCategory.messageSizes [count = 1, total = 28, min = 28, max = 28]
//         MyCategory.eventFailures [count = 0, total = 0, ... ]
// 09FEB2009_18:52:51.096+0000 3 Records
//         Elapsed Time: 0.002217s
//         MyCategory.eventsPerSecond [count = 1, total = 453.721, ... ]
//         MyCategory.messageSizes [count = 2, total = 56, min = 28, max = 28]
//         MyCategory.eventFailures [count = 0, total = 0, ... ]
// ```

#include <balscm_version.h>

#include <balm_collectorrepository.h>
#include <balm_metricregistry.h>

#include <bslmt_rwmutex.h>

#include <bsls_timeinterval.h>

#include <bslma_allocator.h>
#include <bslma_managedptr.h>

#include <bslmf_nestedtraitdeclaration.h>

#include <bsls_libraryfeatures.h>

#include <bsl_functional.h>
#include <bsl_map.h>
#include <bsl_memory.h>
#include <bsl_set.h>
#include <bsl_vector.h>

#include <set>                   // 'std::set', 'std::pmr::set'
#include <vector>                // 'std::vector', 'std::pmr::vector'

namespace BloombergLP {

namespace balm {

class Category;
class Publisher;
class MetricRecord;
class MetricSample;

class MetricsManager_PublisherRegistry;   // defined in implementation
class MetricsManager_CallbackRegistry;    // defined in implementation
struct MetricsManager_PublicationHelper;  // defined in implementation

                            // ====================
                            // class MetricsManager
                            // ====================

/// This class implements a manager for the recording and publishing of
/// metrics.  Metrics managed by a `MetricsManager` are grouped into
/// categories identified by a string.  The metrics manager allows clients
/// to register a `Publisher` object using the `addGeneralPublisher` and
/// `addSpecificPublisher` operations.  Metrics can be recorded in one of
/// two ways: Clients can (1) implement their own metric collection
/// facilities and register a callback using this metric manager's
/// `registerMetricsCallback` method; or (2) use the `Collector` objects
/// available from the `CollectorRepository` owned by this metrics manager.
class MetricsManager {

  public:
    // TYPES

    /// `RecordsCollectionCallback` is an alias for a callback function
    /// that appends to the supplied `MetricRecord` vector the values of the
    /// collected metrics, and, if the provided `bool` is `true`, resets
    /// those metrics to their default values.  Clients can register
    /// callbacks matching this prototype with the metrics manager.  Here is
    /// an example prototype matching this callback:
    /// ```
    /// void collectionCb(bsl::vector<MetricRecord> *records,
    ///                   bool                       resetFlag);
    /// ```
    typedef bsl::function<void(bsl::vector<MetricRecord> *, bool)>
                                                     RecordsCollectionCallback;

    /// Identifies a registered `RecordsCollectionCallback`.
    typedef int CallbackHandle;

  private:
    // PRIVATE TYPES

    /// A mapping from a category to the most recent reset (represented as
    /// the interval since the epoch) of that category.  This is used to
    /// compute the time interval over which a metric was collected.
    typedef bsl::map<const Category *, bsls::TimeInterval> LastResetTimes;

    // DATA
    MetricRegistry           d_metricRegistry;  // registry of metrics

    CollectorRepository      d_collectors;      // repository of metric
                                                // collectors

    bslma::ManagedPtr<MetricsManager_CallbackRegistry>
                             d_callbacks;       // registry of callbacks
                                                // (owned)

    bslma::ManagedPtr<MetricsManager_PublisherRegistry>
                             d_publishers;      // registry of publishers
                                                // (owned)

    bsls::TimeInterval       d_creationTime;    // time this object was
                                                // created

    LastResetTimes           d_prevResetTimes;  // time of a category's
                                                // previous reset

    bslmt::Mutex             d_publishLock;     // lock for 'publish',
                                                // acquired before 'd_rwLock'

    mutable bslmt::RWMutex   d_rwLock;          // lock for the data maps

    bslma::Allocator        *d_allocator_p;     // allocator (held not owned)

    // FRIENDS
    friend struct MetricsManager_PublicationHelper;

  private:
    // NOT IMPLEMENTED
    MetricsManager(const MetricsManager& );
    MetricsManager& operator=(const MetricsManager& );

  public:
    // CONSTANTS
    enum {
        e_INVALID_HANDLE = -1   // an invalid callback handle
#ifndef BDE_OMIT_INTERNAL_DEPRECATED
        , BAEM_INVALID_HANDLE = e_INVALID_HANDLE
#endif // BDE_OMIT_INTERNAL_DEPRECATED
    };

    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(MetricsManager, bslma::UsesBslmaAllocator);

  private:
    // PRIVATE MANIPULATORS

    /// Load into the specified `sample` a metric sample collected from the
    /// indicated categories, and append to `records` those collected
    /// records which are referred to by `sample`.  Optionally specify a
    /// `resetFlag` that determines if the collected metrics are reset as
    /// part of this operation.  This operation will collect aggregated
    /// metric values for each *enabled* category in the specified
    /// categories from registered callbacks as well as from its own
    /// `CollectorRepository`, and then append those values to `records` and
    /// update `sample` with the addresses of those collected `records`.  If
    /// `resetFlag` is `true`, the metrics being collected are reset to
    /// their default state.  This operation also populates the `sample`
    /// with the time interval is computed as the elapsed time since the
    /// interval over which the sampled metrics were collected.  This last
    /// time the metrics were reset (either through a call to the `publish`
    /// or `collectSample` methods).  If `category` has not previously been
    /// reset then this interval is taken to be the elapsed time since the
    /// creation of this metrics manager.  The behavior is undefined unless
    /// `0 <= numCategories`, `categories` refers to a contiguous sequence
    /// of (at least) `numCategories`, and each category in `categories`
    /// appears only once.  Note that `sample` is loaded with the
    /// *addresses* of the metric records appended to `records`, and
    /// modifying `records` after this call returns may invalidate `sample`.
    void collectSampleImp(MetricSample              *sample,
                          bsl::vector<MetricRecord> *records,
                          const Category * const     categories[],
                          int                        numCategories,
                          bool                       resetFlag = false);

  public:
    // CREATORS

    /// Create a `MetricsManager`.  Optionally specify a `basicAllocator`
    /// used to supply memory.  If `basicAllocator` is 0, the currently
    /// installed default allocator is used.
    explicit
    MetricsManager(bslma::Allocator *basicAllocator = 0);

    /// Destroy this `MetricsManager`.
    ~MetricsManager();

    // MANIPULATORS

    /// Register the specified `callback` to collect records for the
    /// category having the specified `categoryName`, and return an opaque
    /// integer handle that can be used later to remove the `callback`.
    /// The supplied `callback` will be called to collect metrics from the
    /// identified category each time the category is published or
    /// collected, even if the category is disabled.  If the `publish` or
    /// `collectSample` method is called on the identified category, and the
    /// category is disabled, the `callback` will be invoked so clients may
    /// update any internal state, but the collected metrics will be
    /// ignored.  Clients that wish to avoid overhead associated with
    /// collecting metrics for a disabled category must test (within their
    /// code) whether the category is disabled.  The behavior is undefined
    /// if the `callback` returns metrics belonging to metric categories
    /// other than the one identified by `categoryName`, or if
    /// `categoryName` is not null-terminated.  Note that the same
    /// `callback` can be registered for a category multiple times, and
    /// that registering the same `callback` multiple times will result in
    /// an equal number of invocations of the `callback` when publishing
    /// the category.
    CallbackHandle registerCollectionCallback(
                               const char                       *categoryName,
                               const RecordsCollectionCallback&  callback);

    /// Register the specified `callback` to collect records for the
    /// specified `category`, and return an opaque integer handle that can
    /// be used later to remove the `callback`.  The supplied `callback`
    /// will be called to collect metrics from `category` each time
    /// `category` is published, even if `category` is disabled (i.e.,
    /// `category->enabled()` is `false`).  If the `publish` method is
    /// called on `category` and `category` is disabled, the `callback` will
    /// be invoked so clients may update any internal state, but the
    /// collected metrics will be ignored.  Clients that wish to avoid
    /// overhead associated with collecting metrics for a disabled category
    /// must test (within their code) whether the category is disabled.  The
    /// behavior is undefined if the `callback` returns metrics belonging to
    /// metric categories other than `category`.  Note that the same
    /// `callback` can be registered for a `category` multiple times, and
    /// that registering the same `callback` multiple times will result in
    /// an equal number of invocations of the `callback` when publishing the
    /// `category`.
    CallbackHandle registerCollectionCallback(
                                   const Category                   *category,
                                   const RecordsCollectionCallback&  callback);

    /// Remove the callback associated with the specified `handle`.  Return
    /// 0 on success, or a non-zero value if `handle` cannot be found.
    int removeCollectionCallback(CallbackHandle handle);

    /// Add the specified `publisher` to the set of publishers that will be
    /// used to propagate records for *every* category published by this
    /// metrics manager.  Return 0 on success, and a non-zero value with no
    /// effect if `publisher` has already been registered.  After this
    /// method returns, *any* category published by this metrics manager
    /// will be published to `publisher`.  Note that this method will have
    /// no effect and return a non-zero value if `publisher` was previously
    /// registered for one or more specific categories (using the
    /// alternative `addSpecificPublisher` method).
    int addGeneralPublisher(const bsl::shared_ptr<Publisher>& publisher);

    /// Add the specified `publisher` to the set of publishers that will be
    /// used to publish metrics for the category identified by the
    /// specified `categoryName`.  Return 0 on success, and a non-zero
    /// value with no effect if `publisher` has already been registered to
    /// publish the identified category.  The behavior is undefined unless
    /// `categoryName` is null-terminated.  Note that this method will fail
    /// and return a non-zero value if `publisher` has previously been
    /// registered to publish *every* category (using the alternative
    /// `addGeneralPublisher` method).
    int addSpecificPublisher(const char                        *categoryName,
                             const bsl::shared_ptr<Publisher>&  publisher);

    /// Add the specified `publisher` to the set of publishers that will be
    /// used to publish metrics for the specified `category`.  Return 0 on
    /// success, and a non-zero value with no effect if `publisher` has
    /// already been registered to publish `category`.  The behavior is
    /// undefined unless `category` is a valid address returned by the
    /// `metricRegistry` method.  Note that this method will fail and return
    /// a non-zero value if `publisher` has previously been registered to
    /// publish *every* category (using the alternative
    /// `addGeneralPublisher` method).
    int addSpecificPublisher(const Category                    *category,
                             const bsl::shared_ptr<Publisher>&  publisher);

    /// Stop publishing metrics to the specified `publisher` (for each
    /// category), and remove it from this metrics manager.  Return 0 on
    /// success, and a non-zero value if `publisher` cannot be found.
    int removePublisher(const Publisher *publisher);
    int removePublisher(const bsl::shared_ptr<Publisher>& publisher);

    /// Return a reference to the modifiable repository of collectors
    /// managed by this object.
    CollectorRepository& collectorRepository();

    /// Return a reference to the modifiable registry of metrics managed by
    /// this object.
    MetricRegistry& metricRegistry();

    /// Load into the specified `sample` a metric sample collected from the
    /// indicated categories, and append to `records` those collected
    /// records which are referred to by `sample`.  Optionally specify a
    /// sequence of `categories` of length `numCategories`.  If a sequence
    /// of categories is not supplied, a sample is collected from all
    /// registered categories.  Optionally specify a `resetFlag` that
    /// determines if the collected metrics are reset as part of this
    /// operation.  This operation will collect aggregated metric values
    /// for each *enabled* category in the indicated categories from
    /// registered callbacks as well as from its own `CollectorRepository`,
    /// and then append those values to `records` and update `sample` with
    /// the addresses of those collected `records`.  If `resetFlag` is
    /// `true`, the metrics being collected are reset to their default
    /// state.  This operation also populates the `sample` with the time
    /// interval is computed as the elapsed time since the interval over
    /// which the sampled metrics were collected.  This last time the
    /// metrics were reset (either through a call to the `publish` or
    /// `collectSample` methods).  If `category` has not previously been
    /// reset then this interval is taken to be the elapsed time since the
    /// creation of this metrics manager.  The behavior is undefined unless
    /// `0 <= numCategories`, `categories` refers to a contiguous sequence
    /// of (at least) `numCategories`, and each category in `categories`
    /// appears only once.  Note that `sample` is loaded with the
    /// *addresses* of the metric records appended to `records`, and
    /// modifying `records` after this call returns may invalidate `sample`.
    void collectSample(MetricSample              *sample,
                       bsl::vector<MetricRecord> *records,
                       bool                       resetFlag = false);
    void collectSample(MetricSample              *sample,
                       bsl::vector<MetricRecord> *records,
                       const Category    * const  categories[],
                       int                        numCategories,
                       bool                       resetFlag = false);

    /// Publish metrics associated with the specified `category` if
    /// `category` is enabled; otherwise (if `category` is not enabled)
    /// this method has no effect.  Optionally specify a `resetFlag` that
    /// determines if the collected metrics are reset as part of this
    /// operation.  If `category` is enabled (i.e., `category->isEnabled()`
    /// is `true`),  this operation will collect aggregated metric values
    /// for `category` from any registered callbacks as well as from its
    /// own `CollectorRepository`, and then publish those records using any
    /// publishers associated with `category`.  If `resetFlag` is `true`,
    /// the metrics being collected are reset to their default state.  The
    /// metrics manager provides publishers the time interval over which the
    /// published metrics were collected.  This interval is computed as the
    /// elapsed time since the last time the `category` was reset (either
    /// through a call to the `publish` or `collectSample` methods).  If
    /// `category` has not previously been reset then this interval is taken
    /// to be the elapsed time since the creation of this metrics manager.
    /// Note that the alternative `publish` methods that publish multiple
    /// categories in a single invocation are more efficient than publishing
    /// a sequence of categories individually.
    void publish(const Category *category, bool resetFlag = true);

    /// Publish metrics belonging to the specified sequence of (unique)
    /// `categories`, of specified length `numCategories`.  Optionally
    /// specify a `resetFlag` that determines if the collected metrics are
    /// reset as part of this operation.  This operation will collect
    /// aggregated metric values for each *enabled* category in `categories`
    /// from registered callbacks as well as from its own
    /// `CollectorRepository`, and then publish those records using any
    /// publishers associated with the category.  Any individual category in
    /// `categories` that is not enabled is ignored.  If `resetFlag` is
    /// `true`, the metrics being collected are reset to their default
    /// state.  The metrics manager provides publishers the time interval
    /// over which the published metrics were collected.  This interval is
    /// computed as the elapsed time since the last time the `category` was
    /// reset (either through a call to the `publish` or `collectSample`
    /// methods).  If a category has not previously been reset then this
    /// interval is taken to be the elapsed time since the creation of this
    /// metrics manager.  The behavior is undefined unless
    /// `0 <= numCategories`, `categories` refers to a contiguous sequence
    /// of (at least) `numCategories`, and each category in `categories`
    /// appears only once.
    void publish(const Category *const categories[],
                 int                   numCategories,
                 bool                  resetFlag = true);

    void publish(const bsl::set<const Category *>&      categories,
                 bool                                   resetFlag = true);
    void publish(const std::set<const Category *>&      categories,
                 bool                                   resetFlag = true);
#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
    void publish(const std::pmr::set<const Category *>& categories,
                 bool                                   resetFlag = true);
#endif
        // Publish metrics belonging to the specified 'categories'.
        // Optionally specify a 'resetFlag' that determines if the metrics are
        // reset as part of this operation.  This operation will collect
        // aggregated metric values for each *enabled* category in 'categories'
        // from registered callbacks as well as from its own
        // 'CollectorRepository', and then publish those records using any
        // publishers associated with the category.   Any category in
        // 'categories' that is not enabled is ignored.  If 'resetFlag' is
        // 'true', the metrics being collected are reset to their default
        // state.  The metrics manager provides publishers the time interval
        // over which the published metrics were collected.  This interval is
        // computed as the elapsed time since the last time the 'category' was
        // reset (either through a call to the 'publish' or 'collectSample'
        // methods).  If a category has not previously been reset then this
        // interval is taken to be the elapsed time since the creation of this
        // metrics manager.

    /// Publish metrics for every category registered with the contained
    /// `MetricsRegistry` object.  Optionally specify a `resetFlag` that
    /// determines if the metrics are reset as part of this operation.  This
    /// operation will collect aggregated metric values for each enabled
    /// category in its `metricRegistry()` from registered callbacks as well
    /// as from its own `CollectorRepository`, and then publish those
    /// records using any publishers associated with the category.  Any
    /// category that is not enabled is ignored.  If `resetFlag` is `true`,
    /// the metrics being collected are reset to their default state.  The
    /// metrics manager provides publishers the time interval over which a
    /// published category of metrics were collected.  This interval is
    /// computed as the elapsed time since the last time the category was
    /// reset (either through a call to the `publish` or `collectSample`
    /// methods).  If a category has not previously been reset then this
    /// interval is taken to be the elapsed time since the creation of this
    /// metrics manager.
    void publishAll(bool resetFlag = true);

    void publishAll(const bsl::set<const Category *>&      excludedCategories,
                    bool                                   resetFlag = true);
    void publishAll(const std::set<const Category *>&      excludedCategories,
                    bool                                   resetFlag = true);
#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
    void publishAll(const std::pmr::set<const Category *>& excludedCategories,
                    bool                                   resetFlag = true);
#endif
        // Publish metrics for every category registered with the contained
        // 'MetricsRegistry' object, except for the specified
        // 'excludedCategories'.  Optionally specify a 'resetFlag' that
        // determines if the metrics are reset as part of this operation.
        // This operation will collect aggregated metric values for each
        // *enabled* category in its 'metricRegistry()' (that is not in
        // 'excludedCategories') from registered callbacks as well as from its
        // own 'CollectorRepository', and then publish those records using any
        // publishers associated with the category.  Any category that is not
        // enabled is ignored.  If 'resetFlag' is 'true', the metrics being
        // collected are reset to their default state.  The metrics manager
        // provides publishers the time interval over which a published
        // category of metrics were collected.  This interval is computed as
        // the elapsed time since the last time the category was reset (either
        // through a call to the 'publish' or 'collectSample' methods).  If a
        // category has not previously been reset then this interval is taken
        // to be the elapsed time since the creation of this metrics manager.

    /// Set whether the specified `category` is enabled to the optionally
    /// specified `isEnabled` value.  If `category` has not been registered,
    /// register it with the `metricRegistry()`.  If a category is disabled
    /// it will not be published (see the `publish` methods), and higher
    /// level components may not record values for metrics belonging to the
    /// category (for an example, see the `balm_metric` documentation).
    /// Note that this operation is *not* atomic, and other threads may
    /// simultaneously access the current enabled value for `category` while
    /// this operation is performed.  Also note that this operation has
    /// *linear* performance with respect to the number of registered
    /// category holders for `category` (see `balm_metricregistry` and
    /// `balm_category` for information on category holders).
    void setCategoryEnabled(const char *category,
                            bool        isEnabled = true);

    /// Set whether the specified `category` is enabled to the optionally
    /// specified `value`.  If a category is disabled it will not be
    /// published (see the `publish` methods), and higher level components
    /// may not record values for metrics belonging to the category (for an
    /// example, see the `balm_metric` documentation).  The behavior is
    /// undefined unless `category` is a valid address of a category
    /// previously returned by the metric registry owned by this
    /// `MetricManager` object (i.e., `metricRegistry()`).  Note that this
    /// operation is thread-safe, but *not* atomic: Other threads may
    /// simultaneously access the current enabled value for `category` while
    /// this operation is performed.  Also note that this operation has
    /// *linear* performance with respect to the number of registered
    /// category holders for `category` (see `balm_metricregistry` and
    /// `balm_category` for information on category holders).
    void setCategoryEnabled(const Category *category,
                            bool            value = true);

    /// Set whether each category currently registered with
    /// `metricRegistry()` is enabled to the specified `value`, and ensure
    /// that categories registered after this call are initialized as
    /// either enabled or disabled, accordingly.  If a category is disabled
    /// it will not be published (see the `publish` methods), and higher
    /// level components may not record values for metrics belonging to the
    /// category (for an example, see the `balm_metric` documentation).
    /// This operation is logically equivalent to iterating over the list of
    /// categories currently registered with `metricRegistry()` and calling
    /// `setCategoryEnabled` on each category individually, and also setting
    /// a default `isEnabled()` value (for newly-created categories).
    /// Hence, subsequent calls to `setCategoryEnabled` will override this
    /// value for a particular category.  Note that this operation is
    /// thread-safe, but *not* atomic: Other threads may simultaneously
    /// access the current enabled status for a category while this
    /// operation completes.  Also note that this operation has *linear*
    /// runtime performance with respect to the total number of category
    /// holders registered with `metricRegistry()` (see
    /// `balm_metricregistry` and `balm_category` for information on
    /// category holders).
    void setAllCategoriesEnabled(bool value);

    // ACCESSORS
    int findGeneralPublishers(bsl::vector<Publisher *>      *publishers) const;
    int findGeneralPublishers(std::vector<Publisher *>      *publishers) const;
#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
    int findGeneralPublishers(std::pmr::vector<Publisher *> *publishers) const;
#endif
        // Append to the specified 'publishers' the addresses of publishers
        // registered to publish metrics for every category.  Return the number
        // of publishers found.  Note that this method will not find publishers
        // associated with individual categories (i.e., category specific
        // publishers).

    int findSpecificPublishers(bsl::vector<Publisher *>   *publishers,
                               const char                 *categoryName) const;
    int findSpecificPublishers(std::vector<Publisher *>   *publishers,
                               const char                 *categoryName) const;
#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
    int findSpecificPublishers(
                            std::pmr::vector<Publisher *> *publishers,
                            const char                    *categoryName) const;
#endif
        // Append to the specified 'publishers' the addresses of any
        // publishers associated with the (particular) category identified by
        // the specified 'categoryName'.  Return the number of publishers
        // found for the 'category'.  The behavior is undefined unless
        // 'category' is null-terminated.  Note that this method will not find
        // publishers registered for every category (i.e., general publishers)

    int findSpecificPublishers(bsl::vector<Publisher *> *publishers,
                               const Category           *category) const;
    int findSpecificPublishers(std::vector<Publisher *> *publishers,
                               const Category           *category) const;
#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
    int findSpecificPublishers(std::pmr::vector<Publisher *> *publishers,
                               const Category                *category) const;
#endif
        // Append to the specified 'publishers' the addresses of any
        // publishers associated with the (particular) specified 'category'.
        // Return the number of publishers found for the 'category'.  The
        // behavior is undefined unless 'category' is a valid address supplied
        // by 'metricRegistry()'.  Note that this method will not find
        // publishers registered for every category (i.e., general publishers).

    /// Return a reference to the non-modifiable repository of collectors
    /// managed by this object.
    const CollectorRepository& collectorRepository() const;

    /// Return a reference to the non-modifiable registry of metrics managed
    /// by this object.
    const MetricRegistry& metricRegistry() const;
};

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

                            // --------------------
                            // class MetricsManager
                            // --------------------

// MANIPULATORS
inline
MetricsManager::CallbackHandle
MetricsManager::registerCollectionCallback(
                                const char                       *categoryName,
                                const RecordsCollectionCallback&  callback)
{
    return registerCollectionCallback(
                                d_metricRegistry.getCategory(categoryName),
                                callback);
}

inline
int MetricsManager::addSpecificPublisher(
                               const char                        *categoryName,
                               const bsl::shared_ptr<Publisher>&  publisher)
{
    return addSpecificPublisher(d_metricRegistry.getCategory(categoryName),
                                publisher);
}

inline
void MetricsManager::setCategoryEnabled(const Category *category,
                                        bool            value)
{
    d_metricRegistry.setCategoryEnabled(category, value);
}

inline
void MetricsManager::setCategoryEnabled(const char *category,
                                        bool        isEnabled)
{
    d_metricRegistry.setCategoryEnabled(
                            d_metricRegistry.getCategory(category), isEnabled);
}

inline
void MetricsManager::setAllCategoriesEnabled(bool value)
{
    d_metricRegistry.setAllCategoriesEnabled(value);
}

inline
CollectorRepository& MetricsManager::collectorRepository()
{
    return d_collectors;
}

inline
MetricRegistry& MetricsManager::metricRegistry()
{
    return d_metricRegistry;
}

// ACCESSORS

inline
int MetricsManager::findSpecificPublishers(
                                  bsl::vector<Publisher *> *publishers,
                                  const char               *categoryName) const
{
    const Category *categoryPtr = d_metricRegistry.findCategory(categoryName);
    return categoryPtr ? findSpecificPublishers(publishers, categoryPtr) : 0;
}

inline
int MetricsManager::findSpecificPublishers(
                                  std::vector<Publisher *> *publishers,
                                  const char               *categoryName) const
{
    const Category *categoryPtr = d_metricRegistry.findCategory(categoryName);
    return categoryPtr ? findSpecificPublishers(publishers, categoryPtr) : 0;
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
inline
int MetricsManager::findSpecificPublishers(
                             std::pmr::vector<Publisher *> *publishers,
                             const char                    *categoryName) const
{
    const Category *categoryPtr = d_metricRegistry.findCategory(categoryName);
    return categoryPtr ? findSpecificPublishers(publishers, categoryPtr) : 0;
}
#endif

inline
const CollectorRepository&
MetricsManager::collectorRepository() const
{
    return d_collectors;
}

inline
const MetricRegistry& MetricsManager::metricRegistry() const
{
    return d_metricRegistry;
}

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