Advanced: Custom Metric Math
Use Custom Metric Math when Metric.MeasureType is 8 (Custom). You create a CDP model that derives from CustomMetricMath, and place that model under the MetricMath child of a Metric block. MetricsManager stores the XML prototype and clones it into every runtime MetricNode that uses MeasureType 8.
Quick Steps
- Create a new CDP model deriving from
CustomMetricMath. - Implement the IMetricMath API (Initialize, StartWindow, Consume, FinishWindow).
- Add your custom model as the MetricMath child under a Metric with MeasureType set to 8.
IMetricMath API
| Function | Purpose | Arguments (type) |
|---|---|---|
| Initialize | Inspect channels, options, create properties, and cache callbacks. | measureType (int): MeasureType code. channels (std::vector<MetricChannelSpec>): channel ids, kinds (Measure/Enable/Block/Reset), and value types for Measure. options (MetricOptions): histogram settings, bounds, Threshold, LoggingFlags, labels. factory (IMetricPropertyFactory&): creates CDP properties owned by the MetricNode. callbacks (MetricCallbacks): flushNow(uint64_t ts) to request a normal sink flush of the current metric state, and flushSelected(uint64_t ts, const std::vector<CDPPropertyBase*>&) to request sparse logging of only selected output properties. |
| StartWindow | Note the processing interval boundaries. | fromTs (uint64_t): inclusive start timestamp. toTs (uint64_t): inclusive end timestamp. |
| Consume | Handle ordered samples and control events. | events (const std::vector<MetricEvent>&): channel id and CDPVariantValue (with timestamp). |
| FinishWindow | Finalize accumulation to window end (e.g., advance histogram). | none |
MetricMathBase Helpers
MetricMathBase provides helper APIs and member variables that you can use directly in CustomMetricMath based models.
| Helper | Purpose / Data |
|---|---|
| Factory helpers | MakeProperty, MakeHistogramProperty, MakeSampledProperty, MakeTypedProperty create CDP properties via the injected factory. |
| Channel helpers | m_measureIds, m_enableIds, m_blockIds, m_resetIds store channel ids; IsMeasureChannel() and ChannelKindLabel() classify events; m_channelKinds mirrors ids to kinds. |
| Control helpers | ProcessControlEvent() handles Enable/Block/Reset with hysteresis and optional flush-on-edge; IsEnabled() and CoerceToTruth() compute gating via internal m_controlState; m_cachedEnabled caches the last enable state. |
| Histogram helper | RecordHistogramSample() and m_histogram (unique_ptr) when histogram is enabled. |
| Logging callbacks | m_callbacks.flushNow requests the DataSink flush with a given timestamp. Use it when the whole current metric state should be logged, such as on control-related events. m_callbacks.flushSelected requests logging of only selected output properties at a given timestamp. Use it when individual measure outputs should be written independently instead of emitting the full metric state on each sample event. |
| Options and timing | m_opts holds MetricOptions; m_fromTs and m_toTs store current window bounds. |
| Samples capture | RegisterSampledPair() and SnapshotSampledProperties() mirror properties for flushing; pairs reside in m_sampledPairs. |
CustomMetricMath Extras
- Stores a serialized prototype of your model (
GetPrototype) so every MetricNode can instantiate the same math with its own owner and name. Instantiate(proto, owner)builds the model from stored XML and returns anIMetricMathinstance.
Example: PercentMath (two measures)
The following snippet shows a custom math that takes Measure[0] as a value and Measure[1] as the full-scale (100 %) reference. Enable/Block/Reset apply automatically through ProcessControlEvent and IsEnabled. Threshold is used only for control truth conversion.
class PercentMath : public CustomMetricMath { public: void Initialize(int measureType, const std::vector<MetricChannelSpec>& channels, const MetricOptions& opts, IMetricPropertyFactory& factory, const MetricCallbacks& callbacks) override { (void)measureType; MetricMathBase::Initialize(measureType, channels, opts, factory, callbacks); PropertyCreateFlags flags; flags |= CDP::System::Base::e_PropertySaveOnChange; flags |= CDP::System::Base::e_PropertyReparent; m_percent = MakeTypedProperty<double>("Percent", flags); m_threshold = std::isfinite(opts.threshold) ? opts.threshold : 0.5; } void InitDone() override { MetricMathBase::InitDone(); // m_percent has now been restored to last saved value from previous run // same is true for any MakeTypedProperty created property that uses e_PropertySaveOnChange flag. } void StartWindow(uint64_t fromTs, uint64_t toTs) override { MetricMathBase::StartWindow(fromTs, toTs); } void Consume(const std::vector<MetricEvent>& events) override { if (!m_percent || m_measureIds.size() < 2) return; for (const auto& ev : events) { bool isMeasure = IsMeasureChannel(ev.channel); if (!isMeasure) { auto ctrl = ProcessControlEvent(ev, m_threshold, 1e-9); if (ctrl.resetTriggered) *m_percent = 0.0; continue; } if (!IsEnabled()) continue; auto numeric = CDPUtils::TryGetDouble(ev.value); if (!numeric) continue; double value = *numeric; RecordHistogramSample(ev.channel, ev.value); if (ev.channel == m_measureIds.front()) m_lastValue = value; else if (ev.channel == m_measureIds[1]) m_lastFull = value; if (m_lastFull != 0.0) { double pct = (m_lastValue / m_lastFull) * 100.0; *m_percent = pct; if (m_callbacks.flushNow && (m_opts.loggingFlags & MetricLogging::EverySample)) m_callbacks.flushNow(ev.value.GetTimeStamp()); } } } void FinishWindow() override { MetricMathBase::FinishWindow(); } private: CDPProperty<double>* m_percent{nullptr}; double m_lastValue{0.0}; double m_lastFull{0.0}; double m_threshold{0.5}; };
Get started with CDP Studio today
Let us help you take your great ideas and turn them into the products your customer will love.