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 sizecount
Reset
- resets the metric object, i.e.; reverses any priorAccumulate
callsCalculate
- calculates the metric value and stores the result to themetric
parameter, setting thelevels_accumulated
to a count of valid accumulated valuesmetric
must not benullptr
levels_accumulated
can benullptr
if this value is not desired- if the count of accumulated levels is lower than given
levels_required
, the metric is not calculated andS_FALSE
is returned
Get_Parameters
- stores the metric parameters of this metric object into theparameters
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 functionuse_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;
}