// balm_metricsample.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_METRICSAMPLE
#define INCLUDED_BALM_METRICSAMPLE

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

//@PURPOSE: Provide a container for a sample of collected metric records.
//
//@CLASSES:
// balm::MetricSampleGroup: a group of records describing the same time period
//      balm::MetricSample: a sample of collected metric records
//
//@SEE_ALSO: balm_publisher, balm_metricrecord
//
//@DESCRIPTION: This component provides a container used to store a sample of
// recorded metric information.  A 'balm::MetricSample' contains a collection
// of addresses to (external) 'balm::MetricRecord' objects containing the
// aggregated record values for a series of metrics.  The records in a sample
// are broken into a series of groups, each group is represented by a
// 'balm::MetricSampleGroup' object.  Each 'balm::MetricSampleGroup' contains
// a sequence of records and an elapsed time value, indicating the time period
// over which those records were collected.  Finally, a 'balm::MetricSample'
// object contains a timestamp value used to indicate when the sample was
// taken.
//
///Alternative Systems for Telemetry
///---------------------------------
// Bloomberg software may alternatively use the GUTS telemetry API, which is
// integrated into Bloomberg infrastructure.
//
///Thread Safety
///-------------
// 'balm::MetricSample' and 'balm::MetricSampleGroup' are both *const*
// *thread-safe*, meaning that accessors may be invoked concurrently from
// different threads, but it is not safe to access or modify an object in one
// thread while another thread modifies the same object.
//
///Usage
///-----
// The following example demonstrates how to create and use a metric sample.
// We start by initializing several 'balm::MetricRecord' values, which we will
// add to the sample.  Note that in this example we create the 'balm::MetricId'
// objects by hand; however, in practice ids should be obtained from a
// 'balm::MetricRegistry' object (such as the one owned by a
// 'balm::MetricsManager').
//..
//  bslma::Allocator *allocator = bslma::Default::allocator(0);
//
//  balm::Category myCategory("MyCategory");
//  balm::MetricDescription descA(&myCategory, "MetricA");
//  balm::MetricDescription descB(&myCategory, "MetricB");
//  balm::MetricDescription descC(&myCategory, "MetricC");
//
//  balm::MetricId metricA(&descA);
//  balm::MetricId metricB(&descB);
//  balm::MetricId metricC(&descC);
//
//  const int TZ = 0;  // UTC time zone offset
//
//  bdlt::DatetimeTz timeStamp(bdlt::Datetime(2008, 3, 26, 13, 30, 0, 0), TZ);
//  balm::MetricRecord recordA(metricA, 0, 0, 0, 0);
//  balm::MetricRecord recordB(metricB, 1, 2, 3, 4);
//  balm::MetricRecord recordC(metricC, 4, 3, 2, 1);
//..
// Now we create the two arrays of metric records whose addresses we will
// later add to the metric sample:
//..
//  balm::MetricRecord              buffer1[] = { recordA, recordB };
//  bsl::vector<balm::MetricRecord> buffer2(allocator);
//  buffer2.push_back(recordC);
//..
// Next we create a 'balm::MetricSample' object, 'sample', and set its
// timestamp property.  Then we add two groups of records (containing the
// addresses of our two record arrays) to the sample we have created.  Since
// the records were not actually collected over a period of time, we supply an
// arbitrary elapsed time value of 1 second and 2 seconds (respectively) for
// the two groups added to the sample.  Note that these arrays must remain
// valid for the lifetime of 'sample'.
//..
//  balm::MetricSample sample(allocator);
//  sample.setTimeStamp(timeStamp);
//  sample.appendGroup(buffer1,
//                     sizeof(buffer1) / sizeof(*buffer1),
//                     bsls::TimeInterval(1.0));
//  sample.appendGroup(buffer2.data(),
//                     buffer2.size(),
//                     bsls::TimeInterval(2.0));
//..
// We can verify the basic properties of our sample:
//..
//       assert(timeStamp             == sample.timeStamp());
//       assert(2                     == sample.numGroups());
//       assert(3                     == sample.numRecords());
//       assert(bsls::TimeInterval(1) == sample.sampleGroup(0).elapsedTime());
//       assert(buffer1               == sample.sampleGroup(0).records());
//       assert(2                     == sample.sampleGroup(0).numRecords());
//       assert(bsls::TimeInterval(2) == sample.sampleGroup(1).elapsedTime());
//       assert(buffer2.data()        == sample.sampleGroup(1).records());
//       assert(1                     == sample.sampleGroup(1).numRecords());
//..
// Finally we can obtain an iterator over the sample's sequence of groups.  In
// this simple example, we iterate over the groups of records in the sample
// and, for each group, iterate over the records in that group, writing those
// records to the console.
//..
//  balm::MetricSample::const_iterator sampleIt = sample.begin();
//  for ( ; sampleIt != sample.end(); ++sampleIt) {
//      balm::MetricSampleGroup::const_iterator groupIt = sampleIt->begin();
//      for ( ; groupIt != sampleIt->end(); ++groupIt) {
//          bsl::cout << *groupIt << bsl::endl;
//      }
//  }
//..
// The output will look like:
//..
//  [ MyCategory.MetricA: 0 0 0 0 ]
//  [ MyCategory.MetricB: 1 2 3 4 ]
//  [ MyCategory.MetricC: 4 3 2 1 ]
//..

#include <balscm_version.h>

#include <balm_metricrecord.h>

#include <bdlt_datetimetz.h>

#include <bsls_timeinterval.h>

#include <bslma_allocator.h>

#include <bslmf_nestedtraitdeclaration.h>

#include <bsl_iosfwd.h>
#include <bsl_vector.h>

namespace BloombergLP {

namespace balm {
                          // =======================
                          // class MetricSampleGroup
                          // =======================

class MetricSampleGroup {
    // This class provides an *in-core* value-semantic representation of a
    // group of metric record values.  This class contains the address of an
    // array of (externally managed) 'MetricRecord' objects, the number of
    // records in that array, and an elapsed time value (used to indicate the
    // time span over which the metric values were aggregated).

    // DATA
    const MetricRecord *d_records_p;   // array of records (held, not
                                       // owned)

    int                 d_numRecords;  // number of records in array

    bsls::TimeInterval  d_elapsedTime; // interval described by records

  public:
    // PUBLIC TYPES
    typedef const MetricRecord *const_iterator;
        // A 'const_iterator' is an alias for an iterator over the
        // non-modifiable records referenced in a 'MetricSampleGroup'.

    // CREATORS
    MetricSampleGroup();
        // Create an empty sample group.  By default, the 'records()' address
        // is 0, 'numRecords()' is 0, and the 'elapsedTime()' is the default-
        // constructed 'bsls::TimeInterval'.

    MetricSampleGroup(const MetricRecord             *records,
                           int                        numRecords,
                           const bsls::TimeInterval&  elapsedTime);
        // Create a sample group containing the specified sequence of
        // 'records' of specified length 'numRecords', recorded over a period
        // whose duration is the specified 'elapsedTime'.  The behavior is
        // undefined unless '0 <= numRecords' and 'records' points to a
        // contiguous sequence of (at least) 'numRecords' metric records.  Note
        // that the contents of 'records' is *not* copied and the supplied
        // array must remain valid for the productive lifetime of this object
        // or until the records are set to a different sequence by calling the
        // 'setRecords' manipulator.

    MetricSampleGroup(const MetricSampleGroup& original);
        // Create a sample group having the same (in-core) value as the
        // specified 'original' sample group.

    // ~MetricSampleGroup();
        // Destroy this sample group.  Note that this trivial destructor is
        // generated by the compiler.

    // MANIPULATORS
    MetricSampleGroup& operator=(const MetricSampleGroup& rhs);
        // Assign to this sample group the value of the specified 'rhs' sample
        // group, and return a reference to this modifiable sample group.
        // Note that only the pointer to the 'MetricRecord' array and the
        // length are copied, and not the records themselves.

    void setElapsedTime(const bsls::TimeInterval& elapsedTime);
        // Set the elapsed time (used to indicate the time span over which
        // this object's metric records were aggregated) to the specified
        // 'elapsedTime'.

    void setRecords(const MetricRecord *records, int numRecords);
        // Set the sequence of records referred to by this sample group to the
        // specified sequence of 'records' of specified length 'numRecords'.
        // The behavior is undefined unless '0 <= numRecords', and 'records'
        // refers to a contiguous sequence of (at least) 'numRecords'.  Note
        // that the contents of 'records' is *not* copied and the supplied
        // array must remain valid for the productive lifetime of this object
        // or until the records are set to a different sequence by calling the
        // 'setRecords' manipulator.

    // ACCESSORS
    const MetricRecord *records() const;
        // Return the address of the contiguous sequence of non-modifiable
        // records of length 'numRecords()'.

    int numRecords() const;
        // Return the number of records (referenced to by 'records()') in this
        // object.

    const bsls::TimeInterval& elapsedTime() const;
        // Return a reference to the non-modifiable elapsed time interval over
        // which this object's metric records were aggregated.

    const_iterator begin() const;
        // Return an iterator positioned at the beginning of the sequence of
        // 'MetricRecord' objects referenced by this object.

    const_iterator end() const;
        // Return an iterator positioned one past the final 'MetricRecord'
        // object in the sequence of records referenced by this object.

    bsl::ostream& print(bsl::ostream& stream,
                        int           level = 0,
                        int           spacesPerLevel = 4) 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.
};

// FREE OPERATORS
bool operator==(const MetricSampleGroup& lhs,
                const MetricSampleGroup& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' sample groups have the
    // same value, and 'false' otherwise.  Two sample groups have the same
    // value if the respective record sequence-addresses, number of records,
    // and elapsed time are the same.

bool operator!=(const MetricSampleGroup& lhs,
                const MetricSampleGroup& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' sample groups do not
    // have the same value, and 'false' otherwise.  Two sample groups do not
    // have the same value if any of the respective record-sequence addresses,
    // number of records, or elapsed time, are not the same.

bsl::ostream& operator<<(bsl::ostream&            stream,
                         const MetricSampleGroup& rhs);
    // Write a formatted description of the specified 'rhs' to the specified
    // 'stream' and return a reference to the modifiable 'stream'.

                             // ==================
                             // class MetricSample
                             // ==================

class MetricSample {
    // This class provides an *in-core* value-semantic representation of a
    // sample of metric values.  The class contains a collection of addresses
    // to (external) 'MetricRecord' objects holding the values for their
    // respective metrics (aggregated over some period of time).  The metric
    // records contained by a sample are broken into a series of groups, which
    // are represented by 'MetricSampleGroup' objects.  Each group contains a
    // sequence of records and an elapsed time value, indicating the period of
    // time over which those records were taken.  This class also provides a
    // timestamp value, used to indicate when the sample was collected.  The
    // class provides a method, 'appendGroups', that appends a group of metric
    // records to the sample.  Arrays supplied using 'appendGroups' must be
    // valid for the productive lifetime of the 'MetricSample' object or until
    // they are removed by calling 'removeAllRecords'.

    // PRIVATE TYPES
    typedef MetricSampleGroup SampleGroup;

    // DATA
    bdlt::DatetimeTz           d_timeStamp;   // time the records were
                                             // collected

    bsl::vector<SampleGroup>  d_records;     // vector of groups of records

    int                       d_numRecords;  // total number of records

    // FRIENDS
    friend bool operator==(const MetricSample& lhs,
                           const MetricSample& rhs);

  public:
    // PUBLIC TYPES
    typedef bsl::vector<MetricSampleGroup>::const_iterator const_iterator;
        // A 'const_iterator' is an alias for an iterator over the
        // non-modifiable sample groups contained in a 'MetricSample'.

    // PUBLIC TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(MetricSample, bslma::UsesBslmaAllocator);

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

    MetricSample(const MetricSample&  original,
                 bslma::Allocator    *basicAllocator = 0);
        // Create a metric sample containing the same value as the specified
        // 'original' sample.  Optionally specify a 'basicAllocator' used to
        // supply memory.  If 'basicAllocator' is 0, the currently installed
        // default allocator is used.  Note that copying the contained
        // 'MetricSampleGroup' objects copies only the pointers to their
        // respective 'MetricRecord' arrays, and does not copy the records
        // themselves; hence, these record arrays must remain valid for the
        // productive lifetimes of all copied objects or until the records are
        // removed by calling 'removeAllRecords'.

    ~MetricSample();
        // Destroy this metric sample.

    // MANIPULATORS
    MetricSample& operator=(const MetricSample& rhs);
        // Assign to this sample the value of the specified 'rhs' sample and
        // return a reference to this modifiable sample.  Note that copying
        // the 'MetricSampleGroup' objects contained in 'rhs' copies only the
        // pointers to their respective 'MetricRecord' arrays, and does not
        // copy records themselves; hence, these record arrays must remain
        // valid for the productive lifetimes of all copied objects or until
        // records are removed by calling 'removeAllRecords'.

    void setTimeStamp(const bdlt::DatetimeTz& timeStamp);
        // Set the timestamp (used to indicate when the sample was taken) to
        // the specified 'timeStamp'.

    void appendGroup(const MetricSampleGroup& group);
        // Append the specified 'group' of records to the sequence of groups
        // maintained by this sample.  If 'group.numRecords()' is 0 this method
        // has no effect.  The behavior is undefined unless
        // 'group.elapsedTime() > bsls::TimeInterval(0, 0)'.  Note that the
        // 'MetricRecord' objects referred to by 'records' are *not* copied:
        // hence, the supplied array must remain valid for the productive
        // lifetime of this object or until the group is removed by calling
        // 'removeAllRecords()'.

    void appendGroup(const MetricRecord        *records,
                     int                        numRecords,
                     const bsls::TimeInterval&  elapsedTime);
        // Append to the sequence of groups maintained by this sample a new
        // group containing the specified sequence of 'records' of specified
        // length 'numRecords' measuring the specified 'elapsedTime'.  The
        // behavior is undefined unless '0 <= numRecords', 'records' refers to
        // a contiguous sequence of size (at least) 'numRecords', and
        // 'elapsedTime > bsls::TimeInterval(0, 0)'.  Note that 'records' is
        // *not* copied: hence, the supplied array must remain valid for the
        // lifetime of this object or until the records are removed by calling
        // 'removeAllRecords()'.  This method is functionally equivalent to:
        //..
        //  appendGroup(MetricSampleGroup(records, numRecords, elapsedTime));
        //..

    void removeAllRecords();
        // Remove all metric records from this sample.

    // ACCESSORS
    const MetricSampleGroup& sampleGroup(int index) const;
        // Return a reference to the non-modifiable 'MetricSampleGroup' object
        // at the specified 'index' in this sample.  The behavior is undefined
        // unless '0 <= index < numGroups()'.  Note that the returned
        // reference will remain valid until this sample is modified by
        // invoking 'appendGroup' or 'removeAllRecords()'.

    const bdlt::DatetimeTz& timeStamp() const;
        // Return a reference to the non-modifiable timestamp for this sample.

    const_iterator begin() const;
        // Return an iterator positioned at the beginning of the sequence of
        // 'MetricSampleGroup' objects contained by this object.  Note that the
        // iterator will remain valid until this sample is modified by invoking
        // either 'appendGroups' or 'removeAllRecords()'.

    const_iterator end() const;
        // Return an iterator positioned one one past the final
        // 'MetricSampleGroup' object in the sequence of sample groups
        // contained by this object.  Note that the iterator will remain valid
        // until this sample is modified by invoking 'appendGroup' or
        // 'removeAllRecords()'.

    int numGroups() const;
        // Return the number of record groups (i.e., 'MetricSampleGroup'
        // objects) that are contained in this object.

    int numRecords() const;
        // Return the total number of records in this sample (i.e., the sum of
        // the lengths of all the appended record groups).

    bsl::ostream& print(bsl::ostream& stream,
                        int           level = 0,
                        int           spacesPerLevel = 4) 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.
};

// FREE OPERATORS
bool operator==(const MetricSample& lhs, const MetricSample& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' samples have the same
    // value, and 'false' otherwise.  Two samples have the same value if they
    // have the same timestamp value, contain the same number of record
    // groups, and if the respective groups of records at each index position
    // have the same value.

bool operator!=(const MetricSample& lhs, const MetricSample& rhs);
    // Return 'true' if the specified 'lhs' and 'rhs' samples do not have the
    // same value, and 'false' otherwise.  Two samples do not have the same
    // value if they have different values for their timestamps, or number of
    // record groups, or if any of the groups of records at corresponding
    // indices have different values.

bsl::ostream& operator<<(bsl::ostream& stream, const MetricSample& rhs);
    // Write a formatted description of the specified 'rhs' to the specified
    // 'stream' and return a reference to the modifiable 'stream'.

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

                          // -----------------------
                          // class MetricSampleGroup
                          // -----------------------

// CREATORS
inline
MetricSampleGroup::MetricSampleGroup()
: d_records_p(0)
, d_numRecords(0)
, d_elapsedTime()
{
}

inline
MetricSampleGroup::MetricSampleGroup(const MetricRecord        *records,
                                     int                        numRecords,
                                     const bsls::TimeInterval&  elapsedTime)
: d_records_p(records)
, d_numRecords(numRecords)
, d_elapsedTime(elapsedTime)
{
}

inline
MetricSampleGroup::MetricSampleGroup(const MetricSampleGroup& original)
: d_records_p(original.d_records_p)
, d_numRecords(original.d_numRecords)
, d_elapsedTime(original.d_elapsedTime)
{
}

// MANIPULATORS
inline
MetricSampleGroup& MetricSampleGroup::operator=(const MetricSampleGroup& rhs)
{
    d_records_p   = rhs.d_records_p;
    d_numRecords  = rhs.d_numRecords;
    d_elapsedTime = rhs.d_elapsedTime;
    return *this;
}

inline
void MetricSampleGroup::setElapsedTime(const bsls::TimeInterval& elapsedTime)
{
    d_elapsedTime = elapsedTime;
}

inline
void MetricSampleGroup::setRecords(const MetricRecord *records,
                                   int                 numRecords)
{
    d_records_p  = records;
    d_numRecords = numRecords;
}

// ACCESSORS
inline
const MetricRecord *MetricSampleGroup::records() const
{
    return d_records_p;
}

inline
int MetricSampleGroup::numRecords() const
{
    return d_numRecords;
}

inline
const bsls::TimeInterval& MetricSampleGroup::elapsedTime() const
{
    return d_elapsedTime;
}

inline
MetricSampleGroup::const_iterator MetricSampleGroup::begin() const
{
    return d_records_p;
}

inline
MetricSampleGroup::const_iterator MetricSampleGroup::end() const
{
    return d_records_p + d_numRecords;
}

}  // close package namespace

// FREE OPERATORS
inline
bool balm::operator==(const MetricSampleGroup& lhs,
                      const MetricSampleGroup& rhs)
{
    return lhs.records()     == rhs.records()
        && lhs.numRecords()  == rhs.numRecords()
        && lhs.elapsedTime() == rhs.elapsedTime();
}

inline
bool balm::operator!=(const MetricSampleGroup& lhs,
                      const MetricSampleGroup& rhs)
{
    return !(lhs == rhs);
}

inline
bsl::ostream& balm::operator<<(bsl::ostream&            stream,
                               const MetricSampleGroup& rhs)
{
    return rhs.print(stream, 0, -1);
}

namespace balm {
                             // ------------------
                             // class MetricSample
                             // ------------------

// CREATORS
inline
MetricSample::MetricSample(bslma::Allocator *basicAllocator)
: d_timeStamp()
, d_records(basicAllocator)
, d_numRecords(0)
{
}

inline
MetricSample::~MetricSample()
{
}

// MANIPULATORS
inline
void MetricSample::setTimeStamp(const bdlt::DatetimeTz& timeStamp)
{
    d_timeStamp = timeStamp;
}

inline
void MetricSample::appendGroup(const MetricSampleGroup& group)
{
    if (0 < group.numRecords()) {
        d_records.push_back(group);
        d_numRecords += group.numRecords();
    }
}

inline
void MetricSample::appendGroup(const MetricRecord        *records,
                               int                        numRecords,
                               const bsls::TimeInterval&  elapsedTime)
{
    if (0 < numRecords) {
        d_records.push_back(SampleGroup(records, numRecords, elapsedTime));
        d_numRecords += numRecords;
    }
}

inline
void MetricSample::removeAllRecords()
{
    d_records.clear();
    d_numRecords = 0;
}

// ACCESSORS
inline
const MetricSampleGroup& MetricSample::sampleGroup(int index) const
{
    return d_records[index];
}

inline
const bdlt::DatetimeTz& MetricSample::timeStamp() const
{
    return d_timeStamp;
}

inline
MetricSample::const_iterator MetricSample::begin() const
{
    return d_records.begin();
}

inline
MetricSample::const_iterator MetricSample::end() const
{
    return d_records.end();
}

inline
int MetricSample::numGroups() const
{
    return static_cast<int>(d_records.size());
}

inline
int MetricSample::numRecords() const
{
    return d_numRecords;
}
}  // close package namespace

// FREE OPERATORS
inline
bool balm::operator==(const MetricSample& lhs, const MetricSample& rhs)
{
    return lhs.d_timeStamp == rhs.d_timeStamp
        && lhs.d_records   == rhs.d_records;
}

inline
bool balm::operator!=(const MetricSample& lhs, const MetricSample& rhs)
{
    return !(lhs == rhs);
}

inline
bsl::ostream& balm::operator<<(bsl::ostream& stream, const MetricSample& rhs)
{
    return rhs.print(stream, 0, -1);
}

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