Documentation

This page hosts the documentation of the SmartCGMS software architecture and all of its components.


Metric

A metric in an entity, that evaluates differences between a reference and an error signal, i.e.; calculates the metric value. The metric entity is originally designed to work with a constant memory pool, however it is merely a suggestion for implementors, rather than a requirement.

Descriptor

Every metric is described by its descriptor (scgms::TMetric_Descriptor)

struct TMetric_Descriptor {
    const GUID id;
    const wchar_t *description;
};

The descriptor fields are self-explanatory - the id field contains a unique GUID identifier of the metric entity and description contains the metric entity name, represented as a null-terminated string (must not be nullptr).

A shared object implementing a metric must:

  • export a do_get_metric_descriptors function
    • this function returns a continuous array of metric descriptors identified by its first and one-after-last element
  • export a do_create_metric function, and create a metric contained within that array when called

An example of do_get_metric_descriptors may be as follows:

const std::array<const scgms::TMetric_Descriptor, 1> metric_descriptors = { { descriptor_1, descriptor_2 } };

HRESULT IfaceCalling do_get_metric_descriptors(scgms::TMetric_Descriptor **begin, scgms::TMetric_Descriptor **end) {
    *begin = metric_descriptors.data();
    *end = metric_descriptors.data() + metric_descriptors.size();
    return S_OK;
}

An example of do_create_metric may be as follows:

HRESULT IfaceCalling do_create_metric(const scgms::TMetric_Parameters *parameters, scgms::IMetric **metric) {
    if (parameters->metric_id == descriptor_1.id)
        return Manufacture_Object(metric, *parameters);

    return E_NOTIMPL;
}

Interface

Every metric entity must implement the scgms::IMetric interface, which is defined as follows:

class IMetric: public virtual refcnt::IReferenced {
    public:
        virtual HRESULT IfaceCalling Accumulate(const double *times, const double *reference, const double *calculated, const size_t count) = 0;
        virtual HRESULT IfaceCalling Reset() = 0;
        virtual HRESULT IfaceCalling Calculate(double *metric, size_t *levels_accumulated, size_t levels_required) = 0;
        virtual HRESULT IfaceCalling Get_Parameters(TMetric_Parameters *parameters) = 0;
};

  • Accumulate - accumulates the given reference (reference array) and error (calculated array) values at given times; both arrays are of size count
  • Reset - resets the metric object, i.e.; reverses any prior Accumulate calls
  • Calculate - calculates the metric value and stores the result to the metric parameter, setting the levels_accumulated to a count of valid accumulated values
    • metric must not be nullptr
    • levels_accumulated can be nullptr if this value is not desired
    • if the count of accumulated levels is lower than given levels_required, the metric is not calculated and S_FALSE is returned
  • Get_Parameters - stores the metric parameters of this metric object into the parameters argument

A metric is parametrized with a scgms::TMetric_Parameters container, that is defined as follows:

struct TMetric_Parameters {
    const GUID metric_id;
    const unsigned char use_relative_error;
    const unsigned char use_squared_differences;
    const unsigned char prefer_more_levels;
    const double threshold;
};

  • metric_id - a GUID of a metric, that should be created by the factory function
  • use_relative_error - differences are converted to relative errors before accumulating
    • implementing metric may ignore this parameter, as it may not apply
  • use_squared_differences - differences are squared before accumulating
    • implementing metric may ignore this parameter, as it may not apply
  • prefer_more_levels - metric result value is divided by difference count
    • implementing metric may ignore this parameter, as it may not apply
  • threshold - a threshold for a metric; the meaning of this attribute is implementation-specific
    • implementing metric may ignore this parameter, as it may not apply

Example implementation

As an example of a metric, let us implement a simple average difference metric (the absolute/relative variant is configured via the parameters container).

Similarly to any other entities, let us create a descriptor header file, despite containing solely the metric GUID:

#include <rtl/guid.h>

namespace example_metric {
    constexpr GUID id = { 0x47a8815, 0x9747, 0x4621, { 0xb2, 0xf1, 0xf9, 0x8f, 0x61, 0xcb, 0x64, 0xc3 } };	// {047A8815-9747-4621-B2F1-F98F61CB64C3}
}

Then, we define the metric class, complying to the scgms::IMetric interface. We also split the definition and implementation for clarity:

class CExample_Metric : public virtual scgms::IMetric, public virtual refcnt::CReferenced {

    protected:
        const scgms::TMetric_Parameters mParameters;
        std::vector mDifferences;
    public:
        CExample_Metric(const scgms::TMetric_Parameters ¶ms) : mParameters(params) {}
        virtual ~CExample_Metric() = default;

        virtual HRESULT IfaceCalling Accumulate(const double *times, const double *reference, const double *calculated, const size_t count) final;
        virtual HRESULT IfaceCalling Reset() final;
        virtual HRESULT IfaceCalling Calculate(double *metric, size_t *levels_accumulated, size_t levels_required) final;
        virtual HRESULT IfaceCalling Get_Parameters(scgms::TMetric_Parameters *parameters) final;
};

The implementation then may be as follows:

#include <cmath>

HRESULT IfaceCalling CExample_Metric::Accumulate(const double *times, const double *reference, const double *calculated, const size_t count) {

    for (size_t i = 0; i < count; i++) {

        double diff = std::fabs(reference[i] - calculated[i]);
        // the difference should not be NaN, nor infinity
        if (std::isnan(diff) || std::isinf(diff)) {
            continue;
        }

        if (mParameters.use_relative_error) {
            diff /= std::fabs(calculated[i]);
        }
        if (mParameters.use_squared_differences) {
            diff *= diff;
        }

        // push back valid difference to internal vector
        mDifferences.push_back(diff);
    }

    return S_OK;
}

HRESULT IfaceCalling CExample_Metric::Reset() {
    mDifferences.clear();
    return S_OK;
}

HRESULT IfaceCalling CExample_Metric::Calculate(double *metric, size_t *levels_accumulated, size_t levels_required) {

    // did we satisfy the input requirement on level count?
    *levels_accumulated = mDifferences.size();
    if (*levels_accumulated < std::max(static_cast<size_t>(1), levels_required)) {
        return S_FALSE;
    }

    // let us calculate average absolute difference as an example of such metric
    double metricValue = 0;
	
    for (size_t i = 0; i < mDifferences.size(); i++) {
        metricValue += mDifferences[i];
    }
    metricValue /= static_cast<double>(*levels_accumulated);

    // satisfy the prefer_more_levels requirement, if requested
    if (mParameters.prefer_more_levels) {
        metricValue /= static_cast<double>(*levels_accumulated);
    }

    // set the resulting metric
    *metric = metricValue;

    return S_OK;
}

HRESULT IfaceCalling CExample_Metric::Get_Parameters(scgms::TMetric_Parameters *parameters) {
    memcpy(parameters, &mParameters, sizeof(mParameters));
    return S_OK;
}

Then, we define the descriptor block:

namespace example_metric {
    scgms::TMetric_Descriptor descriptor = {
        id,
        L"Example average difference metric"
    };
}

We have to implement and export the do_get_metric_descriptors to expose implemented metric:

const std::array<scgms::TMetric_Descriptor, 1> metric_descriptors = { { example_metric::descriptor } };

HRESULT IfaceCalling do_get_metric_descriptors(scgms::TMetric_Descriptor const **begin, scgms::TMetric_Descriptor const **end) {
    return do_get_descriptors(metric_descriptors, begin, end);
}

And as a last step, implement and export the do_create_metric factory function:

HRESULT IfaceCalling do_create_metric(const scgms::TMetric_Parameters *parameters, scgms::IMetric **metric) {
    if (parameters->metric_id == example_metric::id)
        return Manufacture_Object<CExample_Metric>(metric, *parameters);

    return E_NOTIMPL;
}