• Skip to main content
  • Skip to header right navigation
  • Skip to site footer
CDP Studio logo

CDP Studio

The no-code and full-code software development tool for distributed control systems and HMI

  • Doc
  • Why CDP
    • Software developers
    • Automation engineers
    • Managers
  • Products
    • Automation Designer
    • HMI Designer
    • CDP Linux
  • Services
  • Use cases
  • Pricing
  • Try CDP

CDP Studio Documentation

  • Framework - CDP Core
  • Advanced: Custom Metric Math
  • 5.1.0

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

FunctionPurposeArguments (type)
InitializeInspect 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.
StartWindowNote the processing interval boundaries.fromTs (uint64_t): inclusive start timestamp. toTs (uint64_t): inclusive end timestamp.
ConsumeHandle ordered samples and control events.events (const std::vector<MetricEvent>&): channel id and CDPVariantValue (with timestamp).
FinishWindowFinalize 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.

HelperPurpose / Data
Factory helpersMakeProperty, MakeHistogramProperty, MakeSampledProperty, MakeTypedProperty create CDP properties via the injected factory.
Channel helpersm_measureIds, m_enableIds, m_blockIds, m_resetIds store channel ids; IsMeasureChannel() and ChannelKindLabel() classify events; m_channelKinds mirrors ids to kinds.
Control helpersProcessControlEvent() 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 helperRecordHistogramSample() and m_histogram (unique_ptr) when histogram is enabled.
Logging callbacksm_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 timingm_opts holds MetricOptions; m_fromTs and m_toTs store current window bounds.
Samples captureRegisterSampledPair() 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 an IMetricMath instance.

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

The content of this document is confidential information not to be published without the consent of CDP Technologies AS.

CDP Technologies AS, www.cdpstudio.com

Get started with CDP Studio today

Let us help you take your great ideas and turn them into the products your customer will love.

Try CDP Studio for free
Why CDP Studio?

CDP Technologies AS
Hundsværgata 8,
P.O. Box 144
6001 Ålesund, Norway

Tel: +47 990 80 900
E-mail: info@cdptech.com

Company

About CDP

Contact us

Services

Partners

Blog

Developers

Get started

User manuals

Support

Document download

Release notes

My account

Follow CDP

  • LinkedIn
  • YouTube
  • GitHub

© Copyright 2026 CDP Technologies. Privacy and cookie policy.

Return to top