// balm_metricregistry.h                                              -*-C++-*-
#ifndef INCLUDED_BALM_METRICREGISTRY
#define INCLUDED_BALM_METRICREGISTRY

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

//@PURPOSE: Provide a registry for metrics.
//
//@CLASSES:
//   balm::MetricRegistry: a registry for metrics
//
//@SEE_ALSO: balm_metricsmanager, balm_metricrecord
//
//@DESCRIPTION: This component defines a class, `balm::MetricRegistry`, that
// provides operations to register both metric categories and individual
// metrics.  A metric is uniquely identified by its name and category, and the
// metric registry provides a mapping from those identifying properties to a
// `balm::MetricId`.  A `balm::MetricRegistry` object also provides a mapping
// from a category name to the address of a non-modifiable `balm::Category`
// object.
//
///Alternative Systems for Telemetry
///---------------------------------
// Bloomberg software may alternatively use the GUTS telemetry API, which is
// integrated into Bloomberg infrastructure.
//
///Thread Safety
///-------------
// `balm::MetricRegistry` is fully *thread-safe*, meaning that all non-creator
// operations on a given object can be safely invoked simultaneously from
// multiple threads.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Basic Usage
/// - - - - - - - - - - -
// The following example illustrates how to create and use a
// `balm::MetricRegistry`.  We start by creating a `balm::MetricRegistry`
// object, `registry`, and then using this registry to create a
// `balm::MetricId` for a metric named "MetricA" belonging to the category
// "MyCategory" (i.e., "MyCategory.MetricA").
// ```
// bslma::Allocator    *allocator = bslma::Default::allocator(0);
// balm::MetricRegistry  registry(allocator);
//
// balm::MetricId idA = registry.addId("MyCategory", "MetricA");
// ```
// Now that we have added a metric id, "MyCategory.MetricA", attempting to add
// the metric id again will return an invalid id.  We retrieve the same
// identifier we have created using either `getId` or `findId`:
// ```
// balm::MetricId invalidId = registry.addId("MyCategory", "MetricA");
//       assert(!invalidId.isValid());
//
// balm::MetricId idA_copy1 = registry.getId("MyCategory", "MetricA");
//       assert(idA_copy1.isValid());
//       assert(idA_copy1 == idA);
//
// balm::MetricId idA_copy2 = registry.findId("MyCategory", "MetricA");
//       assert(idA_copy2.isValid());
//       assert(idA_copy2 == idA);
// ```
// We use the `getId` method to add a new metric to the registry, then verify
// we can lookup the metric:
// ```
// balm::MetricId idB = registry.getId("MyCategory", "MetricB");
//       assert(idB.isValid());
//       assert(idB == registry.getId("MyCategory", "MetricB"));
//       assert(idB == registry.findId("MyCategory", "MetricB"));
//       assert(!registry.addId("MyCategory", "MetricB").isValid());
// ```
// Next we use `getCategory` to find the address of the `balm::Category` object
// corresponding to "MyCategory":
// ```
// const balm::Category *myCategory = registry.getCategory("MyCategory");
//       assert(myCategory == idA.category());
//       assert(myCategory == idB.category());
//       assert(myCategory->isEnabled());
// ```
// Finally we use the `setCategoryEnabled` method to disable the category
// "MyCategory":
// ```
// registry.setCategoryEnabled(myCategory, false);
//       assert(!myCategory->isEnabled());
// ```

#include <balscm_version.h>

#include <balm_category.h>
#include <balm_metricdescription.h>
#include <balm_metricid.h>
#include <balm_publicationtype.h>

#include <bslmt_rwmutex.h>

#include <bdlb_cstringless.h>

#include <bslma_allocator.h>

#include <bslmf_nestedtraitdeclaration.h>

#include <bsl_iosfwd.h>
#include <bsl_map.h>
#include <bsl_memory.h>
#include <bsl_set.h>
#include <bsl_string.h>
#include <bsl_utility.h>
#include <bsl_vector.h>
#include <bsl_cstddef.h>
#include <bsl_cstring.h>

#include <bslma_allocator.h>

namespace BloombergLP {


namespace balm {

class MetricFormat;

                            // ====================
                            // class MetricRegistry
                            // ====================

/// The class defines a thread-aware mechanism for registering metrics and
/// metric categories.  A metric is uniquely identified by its name and
/// category, and the metric registry provides a mapping from those
/// identifying properties to a `balm::MetricId`.  A `balm::MetricRegistry`
/// object also provides a mapping from a category name to the address of a
/// non-modifiable `balm::Category` object.
class MetricRegistry {

    // PRIVATE TYPES

    /// `CategoryAndName` is an alias for a pair of null-terminated
    /// constant strings that represent the category and name of a metric.
    /// The first element is the category and the second is the name.
    typedef bsl::pair<const char *, const char *> CategoryAndName;

    /// This `struct` defines an ordering on `CategoryAndName` values
    /// allowing them to be included in sorted containers such as
    /// `bsl::map`.  Note that the category and name strings are compared
    /// by value.
    struct CategoryAndNameLess {

        typedef bsl::pair<const char *, const char *> CategoryAndName;

        /// Return `true` if the value of the specified `lhs` is less than
        /// (ordered before) the value of the specified `rhs`, and `false`
        /// otherwise.  The `lhs` value is considered less than the `rhs`
        /// value if the first value in the `lhs` pair (the category) is
        /// less than the first value in the `rhs` pair or, if the first
        /// values are equal, if the second value in the `lhs` pair (the
        /// name) is less than the second value in the `rhs` pair.
        bool operator()(const CategoryAndName& lhs,
                        const CategoryAndName& rhs) const
        {
            int cmp = bsl::strcmp(lhs.first, rhs.first);
            if (0 == cmp) {
                cmp = bsl::strcmp(lhs.second, rhs.second);
            }
            return cmp < 0;
        }
    };

    /// A `MetricMap` is a type that maps a category and name to a
    /// `balm::MetricDescription` object address.
    typedef bsl::map<CategoryAndName,
                     bsl::shared_ptr<MetricDescription>,
                     CategoryAndNameLess>                MetricMap;

    /// A `CategoryRegistry` is a type that maps a name to a
    /// `balm::Category` object address.
    typedef bsl::map<const char *,
                     bsl::shared_ptr<Category>,
                     bdlb::CStringLess>                    CategoryRegistry;

    /// `UserDataRegistry` is an alias for a type that maps a category (or
    /// category prefix) to the user data set for that category (or group of
    /// categories).
    typedef bsl::map<const char *,
                     bsl::vector<const void *>,
                     bdlb::CStringLess>  UserDataRegistry;

    // DATA
    bsl::set<bsl::string>  d_uniqueStrings;  // unique string memory

    CategoryRegistry       d_categories;     // category -> 'balm::Category'

    MetricMap              d_metrics;        // map (category,name) -> MetricId

    bool                   d_defaultEnabled; // default enabled status

    UserDataRegistry       d_categoryUserData;
                                             // map category -> user data

    UserDataRegistry       d_categoryPrefixUserData;
                                             // map category-prefix -> user
                                             // data

    int                    d_nextKey;        // next valid user data key

    mutable bslmt::RWMutex d_lock;           // read-write property lock

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

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

  private:
    // PRIVATE MANIPULATORS

    /// Insert a metric id having the specified `category` and `name` into
    /// this metric registry.  Return a pair whose first member is the id
    /// of the metric, and whose second member is `true` if the returned
    /// metric id is newly-created and `false` otherwise.  The behavior is
    /// undefined unless the calling thread has a *write* lock on `d_lock`.
    bsl::pair<MetricId, bool> insertId(const char *category,
                                       const char *name);

    /// Associate the specified `value` with the specified `key` for every
    /// metric belonging to the specified `category`.  Note that this
    /// operation modifies existing metrics, but does not affect metrics
    /// created after this method is called.  The behavior is undefined
    /// unless the calling thread has a *write* lock on `d_lock`.
    void setCurrentUserData(const char                     *category,
                            MetricDescription::UserDataKey  key,
                            const void                     *value);

    // PRIVATE ACCESSORS

    /// Load into the specified `result` the user data associated (via
    /// `setUserData`) with a category having the specified `categoryName`.
    /// Each index position in `result` will contain 0, or an (opaque)
    /// application-specific data value provided by the client, either for
    /// `categoryName` or a prefix of `categoryName`.  If there is more
    /// than one non-null user-supplied data value applicable to an index
    /// position in `result`, it is unspecified which value will be
    /// returned.  The behavior is undefined unless the calling thread has a
    /// lock on `d_lock`.
    void defaultUserData(bsl::vector<const void *> *result,
                         const char                *categoryName) const;

  public:
    // PUBLIC TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(MetricRegistry, bslma::UsesBslmaAllocator);

    // CREATORS

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

    /// Destroy this metric registry.
    ~MetricRegistry();

    // MANIPULATORS

    /// Add the specified `category` and `name` to this registry, unless it
    /// has already been registered, and return a `balm::MetricId` object
    /// identifying the newly-registered metric.  If the indicated metric
    /// has already been registered, the returned `balm::MetricId` object
    /// will *not* be valid (i.e., `isValid` will return `false`).  The
    /// behavior is undefined unless `category` and `name` are
    /// null-terminated.
    MetricId addId(const char *category, const char *name);

    /// Return a `balm::MetricId` object for the metric identified by the
    /// specified `category` and `name`.  If no corresponding metric has
    /// already been registered, register a new metric and return a
    /// `balm::MetricId` object identifying that newly-registered metric.
    /// The behavior is undefined unless `category` and `name` are
    /// null-terminated.  Note that this operation is guaranteed to return
    /// a valid `balm::MetricId` object.
    MetricId getId(const char *category, const char *name);

    /// Add the specified `category` to this registry, unless it has already
    /// been registered.  Return the address of the newly-created
    /// non-modifiable `balm::Category` object on success, and 0 otherwise.
    /// The behavior is undefined unless `category` is null-terminated.
    const Category *addCategory(const char *category);

    /// Return the address of the non-modifiable `balm::Category` object for
    /// the specified `category`.  If no corresponding category exists,
    /// register a new category and return the address of the newly-created
    /// `balm::Category` object.  The behavior is undefined unless
    /// `category` is null-terminated.  Note that this operation is
    /// guaranteed to return a valid address.
    const Category *getCategory(const char *category);

    /// Set whether the specified `category` is enabled to the specified
    /// `value`.  The behavior is undefined unless `category` is a valid
    /// address of a category previously returned by this metric registry.
    /// Note that this operation is thread-safe, but *not* atomic: Other
    /// threads may simultaneously access the current enabled value for
    /// `category` while this operation completes.  Also note that this
    /// operation has *linear* runtime performance with respect to the
    /// number of registered category holders for `category`.
    void setCategoryEnabled(const Category* category,
                            bool            value);

    /// Set whether each currently registered category is enabled to the
    /// specified `value`, and ensure that categories registered after this
    /// call are initialized as either enabled or disabled, accordingly.
    /// This operation is logically equivalent to iterating over the list
    /// of currently registered categories and calling `setCategoryEnabled`
    /// on each category individually, and also setting a default `enabled`
    /// value (for newly-created categories).  Hence, subsequent calls
    /// `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 registered categories while this operation completes.
    ///  Also note that this operation has *linear* runtime performance with
    /// respect to the total number of category holders registered with this
    /// repository.
    void setAllCategoriesEnabled(bool value);

    /// Load into the specified  `holder` the address of the specified
    /// `category`, its `enabled` status, and the address of the next holder
    /// in the linked list of category holders maintained by `category`
    /// (prepending `holder` to the linked list of category holders for
    /// `category`).  The supplied `category` will update the value returned
    /// by `holder->enabled()` when its enabled state changes, and will
    /// reset `holder` (i.e., `holder->reset()`) when `category` is
    /// destroyed.  The behavior is undefined unless `holder` remains valid
    /// and *unmodified* (by the client) for the lifetime of this object.
    ///
    /// This method should *not* be used directly by client code.  It is an
    /// implementation detail of the `balm` metric collection system.
    void registerCategoryHolder(const Category *category,
                                CategoryHolder *holder);

    /// Set the preferred publication type of the specified `metric` to the
    /// specified `type`.  The preferred publication type of a metric
    /// indicates the preferred aggregate to publish for that metric, or
    /// `balm::PublicationType::UNSPECIFIED` if there is no preference.  The
    /// behavior is undefined unless `metric` was previously returned by
    /// this metric registry.  Note that there is no uniform definition for
    /// how publishers will interpret this value; an `UNSPECIFIED` value
    /// generally indicates that the all the collected aggregates (total,
    /// count, minimum, and maximum value) should be published.  Also note
    /// that the preferred publication type is accessed through the
    /// `balm::MetricDescription` (i.e.,
    /// `metric.description()->preferredPublicationType()`).
    void setPreferredPublicationType(const MetricId&        metric,
                                     PublicationType::Value type);

    /// Set the format for the specified `metricId` to the specified
    /// `format`.  Note that there is no uniform specification for how
    /// publisher implementations will interpret the supplied `format`.
    /// Also note that the format for a metric is accessed through the
    /// `balm::MetricDescription`.  For example:
    /// ```
    /// metric.description()->format();
    /// ```
    void setFormat(const MetricId&     metricId,
                   const MetricFormat& format);

    /// Return a new unique key that can be used to associate (via
    /// `setUserData`) and retrieve (via `userData`) a value with a metric
    /// (or group of metrics).  Note that the returned key can be used by
    /// clients of `balm` to associate additional information with a metric.
    MetricDescription::UserDataKey createUserDataKey();

    /// Associate the specified `value` with the specified `key` in the
    /// description of the specified `metricId`.  The behavior is undefined
    /// unless `key` was previously returned from `createUserDataKey`.  Note
    /// that this method allows clients of `balm` to associate (opaque)
    /// application-specific information with a metric.
    void setUserData(const MetricId&                 metricId,
                     MetricDescription::UserDataKey  key,
                     const void                     *value);

    /// Associate the specified `value` with the specified `key` in any
    /// metric belonging to a category having the specified `categoryName`,
    /// or a category whose name begins with `categoryName`, as determined
    /// by the optionally specified `prefixFlag`.  If `prefixFlag` is
    /// `false` or is not specified, only those metrics belonging to a
    /// category having `categoryName` will be mapped; otherwise, `value`
    /// will be associated with `key` for all metrics belonging to any
    /// category whose name begins with `categoryName`.  This association
    /// applies to existing metrics as well as any subsequently created
    /// ones.  When a metric is created that matches more than one
    /// registered category prefix, it is not specified which supplied value
    /// will be associated with `key`, unless only one of those values is
    /// non-null, in which case the unique non-null value is used.  The
    /// behavior is undefined unless `key` was previously returned from
    /// `createUserDataKey`.
    void setUserData(const char                     *categoryName,
                     MetricDescription::UserDataKey  key,
                     const void                     *value,
                     bool                            prefixFlag = false);

    // ACCESSORS

    /// Return the number of metrics in this registry.
    bsl::size_t numMetrics() const;

    /// Return the number of categories in this registry.
    bsl::size_t numCategories() const;

    /// Find the specified `category`, a null-terminated string, in this
    /// registry.  Return the address of the non-modifiable `balm::Category`
    /// object corresponding to the `category`, or 0 if no such category has
    /// been registered.
    const Category *findCategory(const char *category) const;

    /// Find the specified null-terminated strings `category` and `name` in
    /// this registry.  Return the `balm::MetricId` object corresponding to
    /// the metric having the `category` and `name`, if found, or an invalid
    /// metric id if no such metric has been registered (i.e., `isValid`
    /// will return `false`).
    MetricId findId(const char *category, const char *name) const;

    /// Append to the specified `categories` the addresses of all the
    /// categories registered by this `balm::MetricRegistry` object.
    void getAllCategories(bsl::vector<const Category *> *categories) const;

    /// Format this object to the specified output `stream` at the (absolute
    /// value of) the optionally specified indentation `level` and return a
    /// reference to `stream`.  If `level` is specified, optionally specify
    /// `spacesPerLevel`, the number of spaces per indentation level for
    /// this and all of its nested objects.  If `level` is negative,
    /// suppress indentation of the first line.  If `spacesPerLevel` is
    /// negative, format the entire output on one line, suppressing all but
    /// the initial indentation (as governed by `level`).  If `stream` is
    /// not valid on entry, this operation has no effect.
    bsl::ostream& print(bsl::ostream& stream,
                        int           level = 0,
                        int           spacesPerLevel = 4) const;
};

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