// balm_metricsmanager.h                                              -*-C++-*-

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

#ifndef INCLUDED_BALM_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
///-----
// The following examples demonstrate how to configure, collect, and publish
// metrics.
//
///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>

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
#include <memory_resource>  // 'std::pmr::polymorphic_allocator'
#endif  // BSLS_LIBRARYFEATURES_HAS_CPP17_PMR

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

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.

  public:
    // TYPES
    typedef bsl::function<void(bsl::vector<MetricRecord> *, bool)>
                                                     RecordsCollectionCallback;
        // '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 int CallbackHandle;
        // Identifies a registered 'RecordsCollectionCallback'.

  private:
    // PRIVATE TYPES
    typedef bsl::map<const Category *, bsls::TimeInterval> LastResetTimes;
        // 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.

    // 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
    void collectSampleImp(MetricSample              *sample,
                          bsl::vector<MetricRecord> *records,
                          const Category * const     categories[],
                          int                        numCategories,
                          bool                       resetFlag = false);
        // 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'.

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

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

    // MANIPULATORS
    CallbackHandle registerCollectionCallback(
                               const char                       *categoryName,
                               const RecordsCollectionCallback&  callback);
        // 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 Category                   *category,
                                   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'.

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

    int addGeneralPublisher(const bsl::shared_ptr<Publisher>& publisher);
        // 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 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 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 Category                    *category,
                             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 removePublisher(const Publisher *publisher);
    int removePublisher(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.

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

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

    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);
        // 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 publish(const Category *category, bool resetFlag = true);
        // 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 *const categories[],
                 int                   numCategories,
                 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 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.

    void publishAll(bool resetFlag = true);
        // 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(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.

    void setCategoryEnabled(const char *category,
                            bool        isEnabled = true);
        // 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 Category *category,
                            bool            value = 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 setAllCategoriesEnabled(bool value);
        // 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).

    // 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).

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

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

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