From c8f8abd3435b32b25c30c044202e1729ecdd470f Mon Sep 17 00:00:00 2001 From: duncanpo Date: Fri, 1 Dec 2023 14:45:19 -0500 Subject: [PATCH 01/11] Asynchronous instruments initial submission --- CMakeLists.txt | 7 +- .../+metrics/AsynchronousInstrument.m | 72 ++++++++++ api/metrics/+opentelemetry/+metrics/Meter.m | 129 +++++++++++++----- .../+metrics/ObservableCounter.m | 17 +++ .../+opentelemetry/+metrics/ObservableGauge.m | 17 +++ .../+metrics/ObservableResult.m | 46 +++++++ .../+metrics/ObservableUpDownCounter.m | 18 +++ .../metrics/AsynchronousInstrumentProxy.h | 31 +++++ .../metrics/MeasurementFetcher.h | 16 +++ .../opentelemetry-matlab/metrics/MeterProxy.h | 11 ++ .../metrics/ObservableCounterProxy.h | 20 +++ .../metrics/ObservableGaugeProxy.h | 20 +++ .../metrics/ObservableUpDownCounterProxy.h | 20 +++ .../src/AsynchronousInstrumentProxy.cpp | 30 ++++ api/metrics/src/MeasurementFetcher.cpp | 63 +++++++++ api/metrics/src/MeterProxy.cpp | 104 ++++++++++++++ 16 files changed, 588 insertions(+), 33 deletions(-) create mode 100644 api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m create mode 100644 api/metrics/+opentelemetry/+metrics/ObservableCounter.m create mode 100644 api/metrics/+opentelemetry/+metrics/ObservableGauge.m create mode 100644 api/metrics/+opentelemetry/+metrics/ObservableResult.m create mode 100644 api/metrics/+opentelemetry/+metrics/ObservableUpDownCounter.m create mode 100644 api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h create mode 100644 api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h create mode 100644 api/metrics/include/opentelemetry-matlab/metrics/ObservableCounterProxy.h create mode 100644 api/metrics/include/opentelemetry-matlab/metrics/ObservableGaugeProxy.h create mode 100644 api/metrics/include/opentelemetry-matlab/metrics/ObservableUpDownCounterProxy.h create mode 100644 api/metrics/src/AsynchronousInstrumentProxy.cpp create mode 100644 api/metrics/src/MeasurementFetcher.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e539110..e968f29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,6 +165,7 @@ endif() set(OPENTELEMETRY_PROXY_LIBRARY_NAME "OtelMatlabProxy") +find_package(Matlab REQUIRED) find_package(Protobuf REQUIRED) find_package(nlohmann_json REQUIRED) if(WIN32) @@ -196,7 +197,7 @@ set(TRACE_SDK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/trace/include) set(METRICS_SDK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/metrics/include) set(COMMON_SDK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/common/include) set(OTLP_EXPORTER_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/include) -set(OPENTELEMETRY_PROXY_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ${TRACE_API_INCLUDE_DIR} ${METRICS_API_INCLUDE_DIR} ${CONTEXT_API_INCLUDE_DIR} ${BAGGAGE_API_INCLUDE_DIR} ${COMMON_API_INCLUDE_DIR} ${TRACE_SDK_INCLUDE_DIR} ${METRICS_SDK_INCLUDE_DIR} ${COMMON_SDK_INCLUDE_DIR} ${OTLP_EXPORTER_INCLUDE_DIR} ${OTEL_CPP_PREFIX}/include) +set(OPENTELEMETRY_PROXY_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ${TRACE_API_INCLUDE_DIR} ${METRICS_API_INCLUDE_DIR} ${CONTEXT_API_INCLUDE_DIR} ${BAGGAGE_API_INCLUDE_DIR} ${COMMON_API_INCLUDE_DIR} ${TRACE_SDK_INCLUDE_DIR} ${METRICS_SDK_INCLUDE_DIR} ${COMMON_SDK_INCLUDE_DIR} ${OTLP_EXPORTER_INCLUDE_DIR} ${OTEL_CPP_PREFIX}/include ${Matlab_INCLUDE_DIRS}) set(OPENTELEMETRY_PROXY_FACTORY_CLASS_NAME OtelMatlabProxyFactory) set(OPENTELEMETRY_PROXY_FACTORY_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}) @@ -222,6 +223,8 @@ set(OPENTELEMETRY_PROXY_SOURCES ${METRICS_API_SOURCE_DIR}/UpDownCounterProxy.cpp ${METRICS_API_SOURCE_DIR}/HistogramProxy.cpp ${METRICS_API_SOURCE_DIR}/SynchronousInstrumentProxyFactory.cpp + ${METRICS_API_SOURCE_DIR}/MeasurementFetcher.cpp + ${METRICS_API_SOURCE_DIR}/AsynchronousInstrumentProxy.cpp ${CONTEXT_API_SOURCE_DIR}/TextMapPropagatorProxy.cpp ${CONTEXT_API_SOURCE_DIR}/CompositePropagatorProxy.cpp ${CONTEXT_API_SOURCE_DIR}/TextMapCarrierProxy.cpp @@ -295,7 +298,7 @@ set(OTEL_CPP_LINK_LIBRARIES ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_version${CMAKE_STATIC_LIBRARY_SUFFIX} ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_logs${CMAKE_STATIC_LIBRARY_SUFFIX} ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_metrics${CMAKE_STATIC_LIBRARY_SUFFIX} - ${Protobuf_LIBRARIES}) + ${Protobuf_LIBRARIES} ${Matlab_MEX_LIBRARY}) if(WITH_OTLP_HTTP) set(OTEL_CPP_LINK_LIBRARIES ${OTEL_CPP_LINK_LIBRARIES} ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_exporter_otlp_http${CMAKE_STATIC_LIBRARY_SUFFIX} ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_exporter_otlp_http_client${CMAKE_STATIC_LIBRARY_SUFFIX} diff --git a/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m b/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m new file mode 100644 index 0000000..f2e80e3 --- /dev/null +++ b/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m @@ -0,0 +1,72 @@ +classdef AsynchronousInstrument < handle + % Base class inherited by all asynchronous instruments + + % Copyright 2023 The MathWorks, Inc. + + properties (SetAccess=immutable) + Name (1,1) string + Description (1,1) string + Unit (1,1) string + end + + properties (SetAccess=private) + Callbacks + end + + properties (Access=private) + Proxy % Proxy object to interface C++ code + end + + methods (Access=protected) + function obj = AsynchronousInstrument(proxy, name, description, unit, callback) + obj.Proxy = proxy; + obj.Name = name; + obj.Description = description; + obj.Unit = unit; + obj.Callbacks = callback; + end + + end + + methods + function addCallback(obj, callback) + if isa(callback, "function_handle") + callbackstr = string(func2str(callback)); + if ~startsWith(callbackstr, '@') % do not allow anonymous functions for now + obj.Proxy.addCallback(callbackstr); + % append to Callbacks property + if isempty(obj.Callbacks) + obj.Callbacks = callback; + elseif isa(obj.Callbacks, "function_handle") + obj.Callbacks = {obj.Callbacks, callback}; + else + obj.Callbacks = [obj.Callbacks, {callback}]; + end + end + end + end + + function removeCallback(obj, callback) + if isa(callback, "function_handle") && ~isempty(obj.Callbacks) + callbackstr = string(func2str(callback)); + if iscell(obj.Callbacks) + found = strcmp(cellfun(@func2str, obj.Callbacks, 'UniformOutput', false), callbackstr); + else % scalar function handle + found = strcmp(func2str(obj.Callbacks), callbackstr); + end + if sum(found) > 0 + obj.Proxy.removeCallback(callbackstr); + % update Callback property + if isa(obj.Callbacks, "function_handle") + obj.Callbacks = []; + else + obj.Callbacks(find(found,1)) = []; % remove only the first match + if isscalar(obj.Callbacks) % if there is only one left, remove the cell + obj.Callbacks = obj.Callbacks{1}; + end + end + end + end + end + end +end diff --git a/api/metrics/+opentelemetry/+metrics/Meter.m b/api/metrics/+opentelemetry/+metrics/Meter.m index 17e2eda..5562ec0 100644 --- a/api/metrics/+opentelemetry/+metrics/Meter.m +++ b/api/metrics/+opentelemetry/+metrics/Meter.m @@ -29,7 +29,7 @@ methods - function counter = createCounter(obj, ctname, ctdescription, ctunit) + function counter = createCounter(obj, name, description, unit) % CREATECOUNTER Create a counter % C = CREATECOUNTER(M, NAME) creates a counter with the specified % name. A counter's value can only increase but not @@ -41,22 +41,20 @@ % See also CREATEUPDOWNCOUNTER, CREATEHISTOGRAM arguments obj - ctname - ctdescription = "" - ctunit = "" + name + description = "" + unit = "" end - import opentelemetry.common.mustBeScalarString - ctname = mustBeScalarString(ctname); - ctdescription = mustBeScalarString(ctdescription); - ctunit = mustBeScalarString(ctunit); - id = obj.Proxy.createCounter(ctname, ctdescription, ctunit); + [name, description, unit] = processSynchronousInputs(name, ... + description, unit); + id = obj.Proxy.createCounter(name, description, unit); CounterProxy = libmexclass.proxy.Proxy("Name", ... "libmexclass.opentelemetry.CounterProxy", "ID", id); - counter = opentelemetry.metrics.Counter(CounterProxy, ctname, ctdescription, ctunit); + counter = opentelemetry.metrics.Counter(CounterProxy, name, description, unit); end - function updowncounter = createUpDownCounter(obj, ctname, ctdescription, ctunit) + function updowncounter = createUpDownCounter(obj, name, description, unit) % CREATEUPDOWNCOUNTER Create an UpDownCounter % C = CREATEUPDOWNCOUNTER(M, NAME) creates an UpDownCounter % with the specified name. An UpDownCounter's value can @@ -68,23 +66,21 @@ % See also CREATECOUNTER, CREATEHISTOGRAM arguments obj - ctname - ctdescription = "" - ctunit = "" + name + description = "" + unit = "" end - import opentelemetry.common.mustBeScalarString - ctname = mustBeScalarString(ctname); - ctdescription = mustBeScalarString(ctdescription); - ctunit = mustBeScalarString(ctunit); - id = obj.Proxy.createUpDownCounter(ctname, ctdescription, ctunit); + [name, description, unit] = processSynchronousInputs(name, ... + description, unit); + id = obj.Proxy.createUpDownCounter(name, description, unit); UpDownCounterProxy = libmexclass.proxy.Proxy("Name", ... "libmexclass.opentelemetry.UpDownCounterProxy", "ID", id); - updowncounter = opentelemetry.metrics.UpDownCounter(UpDownCounterProxy, ctname, ctdescription, ctunit); + updowncounter = opentelemetry.metrics.UpDownCounter(UpDownCounterProxy, name, description, unit); end - function histogram = createHistogram(obj, hiname, hidescription, hiunit) + function histogram = createHistogram(obj, name, description, unit) % CREATEHISTOGRAM Create a histogram % H = CREATEHISTOGRAM(M, NAME) creates a histogram with the specified % name. A histogram aggregates values into bins. Bins can be @@ -97,21 +93,92 @@ % OPENTELEMETRY.SDK.METRICS.VIEW arguments obj - hiname - hidescription = "" - hiunit = "" + name + description = "" + unit = "" end - import opentelemetry.common.mustBeScalarString - hiname = mustBeScalarString(hiname); - hidescription = mustBeScalarString(hidescription); - hiunit = mustBeScalarString(hiunit); - id = obj.Proxy.createHistogram(hiname, hidescription, hiunit); + [name, description, unit] = processSynchronousInputs(name, ... + description, unit); + id = obj.Proxy.createHistogram(name, description, unit); HistogramProxy = libmexclass.proxy.Proxy("Name", ... "libmexclass.opentelemetry.HistogramProxy", "ID", id); - histogram = opentelemetry.metrics.Histogram(HistogramProxy, hiname, hidescription, hiunit); + histogram = opentelemetry.metrics.Histogram(HistogramProxy, name, description, unit); end + function obscounter = createObservableCounter(obj, callback, name, description, unit) + arguments + obj + callback + name + description = "" + unit = "" + end + + [callback, callbackstr, name, description, unit] = processAsynchronousInputs(... + callback, name, description, unit); + id = obj.Proxy.createObservableCounter(name, description, unit, callbackstr); + ObservableCounterproxy = libmexclass.proxy.Proxy("Name", ... + "libmexclass.opentelemetry.ObservableCounterProxy", "ID", id); + obscounter = opentelemetry.metrics.ObservableCounter(ObservableCounterproxy, name, description, unit, callback); + end + + function obsudcounter = createObservableUpDownCounter(obj, callback, name, description, unit) + arguments + obj + callback + name + description = "" + unit = "" + end + + [callback, callbackstr, name, description, unit] = processAsynchronousInputs(... + callback, name, description, unit); + id = obj.Proxy.createObservableUpDownCounter(name, description, unit, callbackstr); + ObservableUpDownCounterproxy = libmexclass.proxy.Proxy("Name", ... + "libmexclass.opentelemetry.ObservableUpDownCounterProxy", "ID", id); + obsudcounter = opentelemetry.metrics.ObservableUpDownCounter(... + ObservableUpDownCounterproxy, name, description, unit, callback); + end + + function obsgauge = createObservableGauge(obj, callback, name, description, unit) + arguments + obj + callback + name + description = "" + unit = "" + end + + [callback, callbackstr, name, description, unit] = processAsynchronousInputs(... + callback, name, description, unit); + id = obj.Proxy.createObservableGauge(name, description, unit, callbackstr); + ObservableGaugeproxy = libmexclass.proxy.Proxy("Name", ... + "libmexclass.opentelemetry.ObservableGaugeProxy", "ID", id); + obsgauge = opentelemetry.metrics.ObservableGauge(... + ObservableGaugeproxy, name, description, unit, callback); + end + end +end + +function [name, description, unit] = processSynchronousInputs(name, ... + description, unit) +import opentelemetry.common.mustBeScalarString +name = mustBeScalarString(name); +description = mustBeScalarString(description); +unit = mustBeScalarString(unit); +end + +function [callback, callbackstr, name, description, unit] = processAsynchronousInputs(... + callback, name, description, unit) +[name, description, unit] = processSynchronousInputs(name, description, unit); +if isa(callback, "function_handle") + callbackstr = string(func2str(callback)); + if ~startsWith(callbackstr, '@') % do not allow anonymous functions for now + return end - +end +% if we get here, callback is invalid +callback = []; +callbackstr = ""; end diff --git a/api/metrics/+opentelemetry/+metrics/ObservableCounter.m b/api/metrics/+opentelemetry/+metrics/ObservableCounter.m new file mode 100644 index 0000000..878b756 --- /dev/null +++ b/api/metrics/+opentelemetry/+metrics/ObservableCounter.m @@ -0,0 +1,17 @@ +classdef ObservableCounter < opentelemetry.metrics.AsynchronousInstrument + % ObservableCounter is an asynchronous counter that records its value + % via a callback and its value can only increase but not decrease + + % Copyright 2023 The MathWorks, Inc. + + methods (Access={?opentelemetry.metrics.Meter}) + + function obj = ObservableCounter(proxy, name, description, unit, callback) + % Private constructor. Use getObservableCounter method of Meter + % to create observable counters. + obj@opentelemetry.metrics.AsynchronousInstrument(proxy, name, ... + description, unit, callback); + end + + end +end diff --git a/api/metrics/+opentelemetry/+metrics/ObservableGauge.m b/api/metrics/+opentelemetry/+metrics/ObservableGauge.m new file mode 100644 index 0000000..1730265 --- /dev/null +++ b/api/metrics/+opentelemetry/+metrics/ObservableGauge.m @@ -0,0 +1,17 @@ +classdef ObservableGauge < opentelemetry.metrics.AsynchronousInstrument + % ObservableGauge is an asynchronous gauge that report its values via a + % callback and its value cannot be summed in aggregation. + + % Copyright 2023 The MathWorks, Inc. + + methods (Access={?opentelemetry.metrics.Meter}) + + function obj = ObservableGauge(proxy, name, description, unit, callback) + % Private constructor. Use getObservableGauge method of Meter + % to create observable gauges. + obj@opentelemetry.metrics.AsynchronousInstrument(proxy, name, ... + description, unit, callback); + end + + end +end diff --git a/api/metrics/+opentelemetry/+metrics/ObservableResult.m b/api/metrics/+opentelemetry/+metrics/ObservableResult.m new file mode 100644 index 0000000..0dcacb9 --- /dev/null +++ b/api/metrics/+opentelemetry/+metrics/ObservableResult.m @@ -0,0 +1,46 @@ +classdef ObservableResult + % Object to record results from observable instrument callbacks + + % Copyright 2023 The MathWorks, Inc + properties (SetAccess=private, Hidden) + Results = cell(1,0) + end + + methods + function obj = observe(obj, value, varargin) + if isnumeric(value) && isscalar(value) && isreal(value) + value = double(value); + if nargin == 2 + attrs = {}; + elseif isa(varargin{1}, "dictionary") + attrkeys = keys(varargin{1}, "cell"); + attrvals = values(varargin{1},"cell"); + if all(cellfun(@iscell, attrkeys)) + attrkeys = [attrkeys{:}]; + end + if all(cellfun(@iscell, attrvals)) + attrvals = [attrvals{:}]; + end + attrs = reshape([attrkeys(:).'; attrvals(:).'], 1, []); + else + if rem(length(varargin),2) == 0 + attrs = varargin; + else % mismatched attributes, ignore + attrs = {}; + end + end + % check attribute names must be string or char + for i = 1:2:length(attrs) + currkey = attrs{i}; + if ~(isstring(currkey) || (ischar(currkey) && isrow(currkey))) + attrs = {}; % attribute name not char or string, ignore all attributes + break + end + attrs{i} = string(currkey); %#ok + end + obj.Results = [obj.Results {value} attrs]; + end + end + end + +end diff --git a/api/metrics/+opentelemetry/+metrics/ObservableUpDownCounter.m b/api/metrics/+opentelemetry/+metrics/ObservableUpDownCounter.m new file mode 100644 index 0000000..46c86ae --- /dev/null +++ b/api/metrics/+opentelemetry/+metrics/ObservableUpDownCounter.m @@ -0,0 +1,18 @@ +classdef ObservableUpDownCounter < opentelemetry.metrics.AsynchronousInstrument + % ObservableUpDownCounter is an asynchronous up-down-counter that + % records its value via a callback and its value can both increase and + % decrease. + + % Copyright 2023 The MathWorks, Inc. + + methods (Access={?opentelemetry.metrics.Meter}) + + function obj = ObservableUpDownCounter(proxy, name, description, unit, callback) + % Private constructor. Use getObservableUpDownCounter method of Meter + % to create observable up-down-counters. + obj@opentelemetry.metrics.AsynchronousInstrument(proxy, name, ... + description, unit, callback); + end + + end +end diff --git a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h new file mode 100644 index 0000000..7d57192 --- /dev/null +++ b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h @@ -0,0 +1,31 @@ +// Copyright 2023 The MathWorks, Inc. + +#pragma once + +#include "libmexclass/proxy/Proxy.h" +#include "libmexclass/proxy/method/Context.h" + +#include "opentelemetry/metrics/async_instruments.h" + +namespace metrics_api = opentelemetry::metrics; +namespace nostd = opentelemetry::nostd; + +namespace libmexclass::opentelemetry { +class AsynchronousInstrumentProxy : public libmexclass::proxy::Proxy { + protected: + AsynchronousInstrumentProxy(nostd::shared_ptr inst) : CppInstrument(inst) {} + + public: + void addCallback(libmexclass::proxy::method::Context& context); + + void removeCallback(libmexclass::proxy::method::Context& context); + + std::list CallbackFunctions; + + private: + nostd::shared_ptr CppInstrument; + +}; +} // namespace libmexclass::opentelemetry + + diff --git a/api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h b/api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h new file mode 100644 index 0000000..632824f --- /dev/null +++ b/api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h @@ -0,0 +1,16 @@ +// Copyright 2023 The MathWorks, Inc. + +#pragma once + +namespace metrics_api = opentelemetry::metrics; + +namespace libmexclass::opentelemetry { +class MeasurementFetcher +{ +public: + __declspec(dllexport) static void Fetcher(metrics_api::ObserverResult observer_result, void * /* state */); + static std::shared_ptr mlptr; +}; +} // namespace libmexclass::opentelemetry + + diff --git a/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h index a3d4c32..baf7583 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h @@ -8,6 +8,9 @@ #include "opentelemetry-matlab/metrics/CounterProxy.h" #include "opentelemetry-matlab/metrics/HistogramProxy.h" #include "opentelemetry-matlab/metrics/UpDownCounterProxy.h" +#include "opentelemetry-matlab/metrics/ObservableCounterProxy.h" +#include "opentelemetry-matlab/metrics/ObservableUpDownCounterProxy.h" +#include "opentelemetry-matlab/metrics/ObservableGaugeProxy.h" #include "opentelemetry-matlab/metrics/SynchronousInstrumentProxyFactory.h" #include "opentelemetry/metrics/meter.h" @@ -22,6 +25,9 @@ class MeterProxy : public libmexclass::proxy::Proxy { REGISTER_METHOD(MeterProxy, createCounter); REGISTER_METHOD(MeterProxy, createUpDownCounter); REGISTER_METHOD(MeterProxy, createHistogram); + REGISTER_METHOD(MeterProxy, createObservableCounter); + REGISTER_METHOD(MeterProxy, createObservableUpDownCounter); + REGISTER_METHOD(MeterProxy, createObservableGauge); } void createCounter(libmexclass::proxy::method::Context& context); @@ -30,6 +36,11 @@ class MeterProxy : public libmexclass::proxy::Proxy { void createHistogram(libmexclass::proxy::method::Context& context); + void createObservableCounter(libmexclass::proxy::method::Context& context); + + void createObservableUpDownCounter(libmexclass::proxy::method::Context& context); + + void createObservableGauge(libmexclass::proxy::method::Context& context); private: void createSynchronous(libmexclass::proxy::method::Context& context, SynchronousInstrumentType type); diff --git a/api/metrics/include/opentelemetry-matlab/metrics/ObservableCounterProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/ObservableCounterProxy.h new file mode 100644 index 0000000..21681c4 --- /dev/null +++ b/api/metrics/include/opentelemetry-matlab/metrics/ObservableCounterProxy.h @@ -0,0 +1,20 @@ +// Copyright 2023 The MathWorks, Inc. + +#pragma once + +#include "opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h" + +namespace metrics_api = opentelemetry::metrics; +namespace nostd = opentelemetry::nostd; + +namespace libmexclass::opentelemetry { +class ObservableCounterProxy : public AsynchronousInstrumentProxy { + public: + ObservableCounterProxy(nostd::shared_ptr ct) : AsynchronousInstrumentProxy(ct) { + REGISTER_METHOD(ObservableCounterProxy, addCallback); + REGISTER_METHOD(ObservableCounterProxy, removeCallback); + } +}; +} // namespace libmexclass::opentelemetry + + diff --git a/api/metrics/include/opentelemetry-matlab/metrics/ObservableGaugeProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/ObservableGaugeProxy.h new file mode 100644 index 0000000..314b22b --- /dev/null +++ b/api/metrics/include/opentelemetry-matlab/metrics/ObservableGaugeProxy.h @@ -0,0 +1,20 @@ +// Copyright 2023 The MathWorks, Inc. + +#pragma once + +#include "opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h" + +namespace metrics_api = opentelemetry::metrics; +namespace nostd = opentelemetry::nostd; + +namespace libmexclass::opentelemetry { +class ObservableGaugeProxy : public AsynchronousInstrumentProxy { + public: + ObservableGaugeProxy(nostd::shared_ptr g) : AsynchronousInstrumentProxy(g) { + REGISTER_METHOD(ObservableGaugeProxy, addCallback); + REGISTER_METHOD(ObservableGaugeProxy, removeCallback); + } +}; +} // namespace libmexclass::opentelemetry + + diff --git a/api/metrics/include/opentelemetry-matlab/metrics/ObservableUpDownCounterProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/ObservableUpDownCounterProxy.h new file mode 100644 index 0000000..9f7c20c --- /dev/null +++ b/api/metrics/include/opentelemetry-matlab/metrics/ObservableUpDownCounterProxy.h @@ -0,0 +1,20 @@ +// Copyright 2023 The MathWorks, Inc. + +#pragma once + +#include "opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h" + +namespace metrics_api = opentelemetry::metrics; +namespace nostd = opentelemetry::nostd; + +namespace libmexclass::opentelemetry { +class ObservableUpDownCounterProxy : public AsynchronousInstrumentProxy { + public: + ObservableUpDownCounterProxy(nostd::shared_ptr ct) : AsynchronousInstrumentProxy(ct) { + REGISTER_METHOD(ObservableUpDownCounterProxy, addCallback); + REGISTER_METHOD(ObservableUpDownCounterProxy, removeCallback); + } +}; +} // namespace libmexclass::opentelemetry + + diff --git a/api/metrics/src/AsynchronousInstrumentProxy.cpp b/api/metrics/src/AsynchronousInstrumentProxy.cpp new file mode 100644 index 0000000..241d397 --- /dev/null +++ b/api/metrics/src/AsynchronousInstrumentProxy.cpp @@ -0,0 +1,30 @@ +// Copyright 2023 The MathWorks, Inc. + +#include "opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h" +#include "opentelemetry-matlab/metrics/MeasurementFetcher.h" + +#include "MatlabDataArray.hpp" +#include + +namespace libmexclass::opentelemetry { + + +void AsynchronousInstrumentProxy::addCallback(libmexclass::proxy::method::Context& context){ + matlab::data::StringArray callback_mda = context.inputs[0]; + std::string callback = static_cast(callback_mda[0]); + CallbackFunctions.push_back(callback); + CppInstrument->AddCallback(MeasurementFetcher::Fetcher, static_cast(&CallbackFunctions.back())); +} + + +void AsynchronousInstrumentProxy::removeCallback(libmexclass::proxy::method::Context& context){ + matlab::data::StringArray callback_mda = context.inputs[0]; + std::string callback = static_cast(callback_mda[0]); + auto iter = std::find(CallbackFunctions.begin(), CallbackFunctions.end(), callback); + if (iter != CallbackFunctions.end()) { // found a match + CallbackFunctions.erase(iter); + CppInstrument->RemoveCallback(MeasurementFetcher::Fetcher, static_cast(&(*iter))); + } +} + +} // namespace libmexclass::opentelemetry diff --git a/api/metrics/src/MeasurementFetcher.cpp b/api/metrics/src/MeasurementFetcher.cpp new file mode 100644 index 0000000..962bbd3 --- /dev/null +++ b/api/metrics/src/MeasurementFetcher.cpp @@ -0,0 +1,63 @@ + +#include "MatlabDataArray.hpp" +#include "mex.hpp" +#include "cppmex/detail/mexErrorDispatch.hpp" +#include "cppmex/detail/mexEngineUtilImpl.hpp" +#include "cppmex/detail/mexExceptionImpl.hpp" +#include "cppmex/detail/mexExceptionType.hpp" +#include "cppmex/detail/mexIOAdapterImpl.hpp" +#include "cppmex/detail/mexApiAdapterImpl.hpp" +#include "cppmex/detail/mexFutureImpl.hpp" +#include "cppmex/detail/mexTaskReferenceImpl.hpp" + + +#include "opentelemetry/metrics/observer_result.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/variant.h" + +#include "opentelemetry-matlab/metrics/MeasurementFetcher.h" +#include "opentelemetry-matlab/common/attribute.h" + +namespace metrics_api = opentelemetry::metrics; +namespace nostd = opentelemetry::nostd; + +namespace libmexclass::opentelemetry { +void MeasurementFetcher::Fetcher(metrics_api::ObserverResult observer_result, void * fcn_name_ptr) +{ + if (nostd::holds_alternative< + nostd::shared_ptr>>(observer_result)) + { + auto future = mlptr->fevalAsync(static_cast(fcn_name_ptr)->c_str(), std::vector()); + try { + matlab::data::ObjectArray resultobj = future.get(); + auto futureresult = mlptr->getPropertyAsync(resultobj, 0, u"Results"); + matlab::data::CellArray resultdata = futureresult.get(); + size_t n = resultdata.getNumberOfElements(); + size_t i = 0; + while (i < n) { + matlab::data::TypedArray val_mda = resultdata[i]; + double val = val_mda[0]; + + ProcessedAttributes attrs; + size_t j = 1; + while (i+j < n && resultdata[i+j].getType() == matlab::data::ArrayType::MATLAB_STRING) { + matlab::data::StringArray attrname_mda = resultdata[i+j]; + std::string attrname = static_cast(attrname_mda[0]); + matlab::data::Array attrvalue = resultdata[i+j+1]; + + processAttribute(attrname, attrvalue, attrs); + j += 2; + } + nostd::get>>( + observer_result)->Observe(val, attrs.Attributes); + i += j; + } + + } catch(...) { + // ran into an error in the callback, just do nothing and return + } + } +} + +std::shared_ptr MeasurementFetcher::mlptr = nullptr; +} // namespace diff --git a/api/metrics/src/MeterProxy.cpp b/api/metrics/src/MeterProxy.cpp index 68fbaea..230a3ac 100644 --- a/api/metrics/src/MeterProxy.cpp +++ b/api/metrics/src/MeterProxy.cpp @@ -1,6 +1,7 @@ // Copyright 2023 The MathWorks, Inc. #include "opentelemetry-matlab/metrics/MeterProxy.h" +#include "opentelemetry-matlab/metrics/MeasurementFetcher.h" #include "libmexclass/proxy/ProxyManager.h" @@ -45,5 +46,108 @@ void MeterProxy::createHistogram(libmexclass::proxy::method::Context& context) { createSynchronous(context, SynchronousInstrumentType::Histogram); } +void MeterProxy::createObservableCounter(libmexclass::proxy::method::Context& context) { + // Always assumes 4 inputs + matlab::data::StringArray name_mda = context.inputs[0]; + std::string name = static_cast(name_mda[0]); + matlab::data::StringArray description_mda = context.inputs[1]; + std::string description= static_cast(description_mda[0]); + matlab::data::StringArray unit_mda = context.inputs[2]; + std::string unit = static_cast(unit_mda[0]); + matlab::data::StringArray callback_mda = context.inputs[3]; + std::string callback = static_cast(callback_mda[0]); + + nostd::shared_ptr ct = CppMeter->CreateDoubleObservableCounter(name, description, unit); + + // instantiate a ObservableCounterProxy instance + ObservableCounterProxy* newproxy = new ObservableCounterProxy(ct); + auto proxy = std::shared_ptr(newproxy); + + if (MeasurementFetcher::mlptr == nullptr) { + MeasurementFetcher::mlptr = static_cast >(context.matlab); + } + + if (!callback.empty()) { + newproxy->CallbackFunctions.push_back(callback); + ct->AddCallback(MeasurementFetcher::Fetcher, static_cast(&(newproxy->CallbackFunctions.back()))); + } + + // obtain a proxy ID + libmexclass::proxy::ID proxyid = libmexclass::proxy::ProxyManager::manageProxy(proxy); + + // return the ID + matlab::data::ArrayFactory factory; + auto proxyid_mda = factory.createScalar(proxyid); + context.outputs[0] = proxyid_mda; +} + +void MeterProxy::createObservableUpDownCounter(libmexclass::proxy::method::Context& context) { + // Always assumes 4 inputs + matlab::data::StringArray name_mda = context.inputs[0]; + std::string name = static_cast(name_mda[0]); + matlab::data::StringArray description_mda = context.inputs[1]; + std::string description= static_cast(description_mda[0]); + matlab::data::StringArray unit_mda = context.inputs[2]; + std::string unit = static_cast(unit_mda[0]); + matlab::data::StringArray callback_mda = context.inputs[3]; + std::string callback = static_cast(callback_mda[0]); + + nostd::shared_ptr ct = CppMeter->CreateDoubleObservableUpDownCounter(name, description, unit); + + // instantiate a ObservableUpDownCounterProxy instance + ObservableUpDownCounterProxy* newproxy = new ObservableUpDownCounterProxy(ct); + auto proxy = std::shared_ptr(newproxy); + + if (MeasurementFetcher::mlptr == nullptr) { + MeasurementFetcher::mlptr = static_cast >(context.matlab); + } + + if (!callback.empty()) { + newproxy->CallbackFunctions.push_back(callback); + ct->AddCallback(MeasurementFetcher::Fetcher, static_cast(&(newproxy->CallbackFunctions.back()))); + } + // obtain a proxy ID + libmexclass::proxy::ID proxyid = libmexclass::proxy::ProxyManager::manageProxy(proxy); + + // return the ID + matlab::data::ArrayFactory factory; + auto proxyid_mda = factory.createScalar(proxyid); + context.outputs[0] = proxyid_mda; +} + +void MeterProxy::createObservableGauge(libmexclass::proxy::method::Context& context) { + // Always assumes 4 inputs + matlab::data::StringArray name_mda = context.inputs[0]; + std::string name = static_cast(name_mda[0]); + matlab::data::StringArray description_mda = context.inputs[1]; + std::string description= static_cast(description_mda[0]); + matlab::data::StringArray unit_mda = context.inputs[2]; + std::string unit = static_cast(unit_mda[0]); + matlab::data::StringArray callback_mda = context.inputs[3]; + std::string callback = static_cast(callback_mda[0]); + + nostd::shared_ptr gauge = CppMeter->CreateDoubleObservableGauge(name, description, unit); + + // instantiate an ObservableGaugeProxy instance + ObservableGaugeProxy* newproxy = new ObservableGaugeProxy(gauge); + auto proxy = std::shared_ptr(newproxy); + + if (MeasurementFetcher::mlptr == nullptr) { + MeasurementFetcher::mlptr = static_cast >(context.matlab); + } + + if (!callback.empty()) { + newproxy->CallbackFunctions.push_back(callback); + gauge->AddCallback(MeasurementFetcher::Fetcher, static_cast(&(newproxy->CallbackFunctions.back()))); + } + + // obtain a proxy ID + libmexclass::proxy::ID proxyid = libmexclass::proxy::ProxyManager::manageProxy(proxy); + + // return the ID + matlab::data::ArrayFactory factory; + auto proxyid_mda = factory.createScalar(proxyid); + context.outputs[0] = proxyid_mda; +} } // namespace libmexclass::opentelemetry From d6eca1a23185db712f70b3cef68cea34bc990244 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Mon, 4 Dec 2023 10:11:11 -0500 Subject: [PATCH 02/11] Change CallbackFunctions property of AsynchronousInstrumentProxy from public to private --- .../metrics/AsynchronousInstrumentProxy.h | 8 ++++++-- api/metrics/src/AsynchronousInstrumentProxy.cpp | 5 ++++- api/metrics/src/MeterProxy.cpp | 9 +++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h index 7d57192..faac52c 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h @@ -18,13 +18,17 @@ class AsynchronousInstrumentProxy : public libmexclass::proxy::Proxy { public: void addCallback(libmexclass::proxy::method::Context& context); - void removeCallback(libmexclass::proxy::method::Context& context); + // This method should ideally be an overloaded version of addCallback. However, addCallback is a registered + // method and REGISTER_METHOD macro doesn't like overloaded methods. Rename to avoid overloading. + void addCallback_helper(const std::string& callback); - std::list CallbackFunctions; + void removeCallback(libmexclass::proxy::method::Context& context); private: nostd::shared_ptr CppInstrument; + std::list CallbackFunctions; + }; } // namespace libmexclass::opentelemetry diff --git a/api/metrics/src/AsynchronousInstrumentProxy.cpp b/api/metrics/src/AsynchronousInstrumentProxy.cpp index 241d397..f4fb1bf 100644 --- a/api/metrics/src/AsynchronousInstrumentProxy.cpp +++ b/api/metrics/src/AsynchronousInstrumentProxy.cpp @@ -12,11 +12,14 @@ namespace libmexclass::opentelemetry { void AsynchronousInstrumentProxy::addCallback(libmexclass::proxy::method::Context& context){ matlab::data::StringArray callback_mda = context.inputs[0]; std::string callback = static_cast(callback_mda[0]); + addCallback_helper(callback); +} + +void AsynchronousInstrumentProxy::addCallback_helper(const std::string& callback){ CallbackFunctions.push_back(callback); CppInstrument->AddCallback(MeasurementFetcher::Fetcher, static_cast(&CallbackFunctions.back())); } - void AsynchronousInstrumentProxy::removeCallback(libmexclass::proxy::method::Context& context){ matlab::data::StringArray callback_mda = context.inputs[0]; std::string callback = static_cast(callback_mda[0]); diff --git a/api/metrics/src/MeterProxy.cpp b/api/metrics/src/MeterProxy.cpp index 230a3ac..e4c2c1c 100644 --- a/api/metrics/src/MeterProxy.cpp +++ b/api/metrics/src/MeterProxy.cpp @@ -68,8 +68,7 @@ void MeterProxy::createObservableCounter(libmexclass::proxy::method::Context& co } if (!callback.empty()) { - newproxy->CallbackFunctions.push_back(callback); - ct->AddCallback(MeasurementFetcher::Fetcher, static_cast(&(newproxy->CallbackFunctions.back()))); + newproxy->addCallback_helper(callback); } // obtain a proxy ID @@ -103,8 +102,7 @@ void MeterProxy::createObservableUpDownCounter(libmexclass::proxy::method::Conte } if (!callback.empty()) { - newproxy->CallbackFunctions.push_back(callback); - ct->AddCallback(MeasurementFetcher::Fetcher, static_cast(&(newproxy->CallbackFunctions.back()))); + newproxy->addCallback_helper(callback); } // obtain a proxy ID @@ -138,8 +136,7 @@ void MeterProxy::createObservableGauge(libmexclass::proxy::method::Context& cont } if (!callback.empty()) { - newproxy->CallbackFunctions.push_back(callback); - gauge->AddCallback(MeasurementFetcher::Fetcher, static_cast(&(newproxy->CallbackFunctions.back()))); + newproxy->addCallback_helper(callback); } // obtain a proxy ID From 999cabfd704f04ecc03405a929484dffe9f9a45f Mon Sep 17 00:00:00 2001 From: duncanpo Date: Mon, 4 Dec 2023 11:47:09 -0500 Subject: [PATCH 03/11] Add a AsynchronousInstrumentProxyFactory to reduce code duplication --- CMakeLists.txt | 1 + .../AsynchronousInstrumentProxyFactory.h | 28 +++++++ .../opentelemetry-matlab/metrics/MeterProxy.h | 3 + .../AsynchronousInstrumentProxyFactory.cpp | 39 +++++++++ api/metrics/src/MeterProxy.cpp | 84 +++---------------- 5 files changed, 82 insertions(+), 73 deletions(-) create mode 100644 api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h create mode 100644 api/metrics/src/AsynchronousInstrumentProxyFactory.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e968f29..a7df661 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,6 +225,7 @@ set(OPENTELEMETRY_PROXY_SOURCES ${METRICS_API_SOURCE_DIR}/SynchronousInstrumentProxyFactory.cpp ${METRICS_API_SOURCE_DIR}/MeasurementFetcher.cpp ${METRICS_API_SOURCE_DIR}/AsynchronousInstrumentProxy.cpp + ${METRICS_API_SOURCE_DIR}/AsynchronousInstrumentProxyFactory.cpp ${CONTEXT_API_SOURCE_DIR}/TextMapPropagatorProxy.cpp ${CONTEXT_API_SOURCE_DIR}/CompositePropagatorProxy.cpp ${CONTEXT_API_SOURCE_DIR}/TextMapCarrierProxy.cpp diff --git a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h new file mode 100644 index 0000000..73f5553 --- /dev/null +++ b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h @@ -0,0 +1,28 @@ +// Copyright 2023 The MathWorks, Inc. + +#pragma once + +#include "libmexclass/proxy/Proxy.h" + +#include "opentelemetry/metrics/meter.h" + +namespace metrics_api = opentelemetry::metrics; +namespace nostd = opentelemetry::nostd; + +namespace libmexclass::opentelemetry { + +enum class AsynchronousInstrumentType {ObservableCounter, ObservableUpDownCounter, ObservableGauge}; + +class AsynchronousInstrumentProxyFactory { + public: + AsynchronousInstrumentProxyFactory(nostd::shared_ptr mt) : CppMeter(mt) {} + + std::shared_ptr create(AsynchronousInstrumentType type, + const std::string& callback, const std::string& name, const std::string& description, + const std::string& unit); + + private: + + nostd::shared_ptr CppMeter; +}; +} // namespace libmexclass::opentelemetry diff --git a/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h index baf7583..d96951e 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h @@ -12,6 +12,7 @@ #include "opentelemetry-matlab/metrics/ObservableUpDownCounterProxy.h" #include "opentelemetry-matlab/metrics/ObservableGaugeProxy.h" #include "opentelemetry-matlab/metrics/SynchronousInstrumentProxyFactory.h" +#include "opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h" #include "opentelemetry/metrics/meter.h" @@ -41,7 +42,9 @@ class MeterProxy : public libmexclass::proxy::Proxy { void createObservableUpDownCounter(libmexclass::proxy::method::Context& context); void createObservableGauge(libmexclass::proxy::method::Context& context); + private: + void createAsynchronous(libmexclass::proxy::method::Context& context, AsynchronousInstrumentType type); void createSynchronous(libmexclass::proxy::method::Context& context, SynchronousInstrumentType type); diff --git a/api/metrics/src/AsynchronousInstrumentProxyFactory.cpp b/api/metrics/src/AsynchronousInstrumentProxyFactory.cpp new file mode 100644 index 0000000..ec753c3 --- /dev/null +++ b/api/metrics/src/AsynchronousInstrumentProxyFactory.cpp @@ -0,0 +1,39 @@ +// Copyright 2023 The MathWorks, Inc. + +#include "opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h" +#include "opentelemetry-matlab/metrics/ObservableCounterProxy.h" +#include "opentelemetry-matlab/metrics/ObservableUpDownCounterProxy.h" +#include "opentelemetry-matlab/metrics/ObservableGaugeProxy.h" + +namespace libmexclass::opentelemetry { +std::shared_ptr AsynchronousInstrumentProxyFactory::create(AsynchronousInstrumentType type, + const std::string& callback, const std::string& name, const std::string& description, const std::string& unit) { + std::shared_ptr proxy; + switch(type) { + case AsynchronousInstrumentType::ObservableCounter: + { + nostd::shared_ptr ct = std::move(CppMeter->CreateDoubleObservableCounter(name, description, unit)); + proxy = std::shared_ptr(new ObservableCounterProxy(ct)); + } + break; + case AsynchronousInstrumentType::ObservableUpDownCounter: + { + nostd::shared_ptr udct = std::move(CppMeter->CreateDoubleObservableUpDownCounter(name, description, unit)); + proxy = std::shared_ptr(new ObservableUpDownCounterProxy(udct)); + } + break; + case AsynchronousInstrumentType::ObservableGauge: + { + nostd::shared_ptr g = std::move(CppMeter->CreateDoubleObservableGauge(name, description, unit)); + proxy = std::shared_ptr(new ObservableGaugeProxy(g)); + } + break; + } + // add callback + if (!callback.empty()) { + std::static_pointer_cast(proxy)->addCallback_helper(callback); + } + return proxy; +} + +} // namespace libmexclass::opentelemetry diff --git a/api/metrics/src/MeterProxy.cpp b/api/metrics/src/MeterProxy.cpp index e4c2c1c..6dbbf57 100644 --- a/api/metrics/src/MeterProxy.cpp +++ b/api/metrics/src/MeterProxy.cpp @@ -46,7 +46,7 @@ void MeterProxy::createHistogram(libmexclass::proxy::method::Context& context) { createSynchronous(context, SynchronousInstrumentType::Histogram); } -void MeterProxy::createObservableCounter(libmexclass::proxy::method::Context& context) { +void MeterProxy::createAsynchronous(libmexclass::proxy::method::Context& context, AsynchronousInstrumentType type) { // Always assumes 4 inputs matlab::data::StringArray name_mda = context.inputs[0]; std::string name = static_cast(name_mda[0]); @@ -57,20 +57,14 @@ void MeterProxy::createObservableCounter(libmexclass::proxy::method::Context& co matlab::data::StringArray callback_mda = context.inputs[3]; std::string callback = static_cast(callback_mda[0]); - nostd::shared_ptr ct = CppMeter->CreateDoubleObservableCounter(name, description, unit); - - // instantiate a ObservableCounterProxy instance - ObservableCounterProxy* newproxy = new ObservableCounterProxy(ct); - auto proxy = std::shared_ptr(newproxy); - + // initialize MATLAB mex engine the first time an asynchronous instrument is created if (MeasurementFetcher::mlptr == nullptr) { MeasurementFetcher::mlptr = static_cast >(context.matlab); } - - if (!callback.empty()) { - newproxy->addCallback_helper(callback); - } + AsynchronousInstrumentProxyFactory proxyfactory(CppMeter); + auto proxy = proxyfactory.create(type, callback, name, description, unit); + // obtain a proxy ID libmexclass::proxy::ID proxyid = libmexclass::proxy::ProxyManager::manageProxy(proxy); @@ -80,71 +74,15 @@ void MeterProxy::createObservableCounter(libmexclass::proxy::method::Context& co context.outputs[0] = proxyid_mda; } -void MeterProxy::createObservableUpDownCounter(libmexclass::proxy::method::Context& context) { - // Always assumes 4 inputs - matlab::data::StringArray name_mda = context.inputs[0]; - std::string name = static_cast(name_mda[0]); - matlab::data::StringArray description_mda = context.inputs[1]; - std::string description= static_cast(description_mda[0]); - matlab::data::StringArray unit_mda = context.inputs[2]; - std::string unit = static_cast(unit_mda[0]); - matlab::data::StringArray callback_mda = context.inputs[3]; - std::string callback = static_cast(callback_mda[0]); - - nostd::shared_ptr ct = CppMeter->CreateDoubleObservableUpDownCounter(name, description, unit); - - // instantiate a ObservableUpDownCounterProxy instance - ObservableUpDownCounterProxy* newproxy = new ObservableUpDownCounterProxy(ct); - auto proxy = std::shared_ptr(newproxy); - - if (MeasurementFetcher::mlptr == nullptr) { - MeasurementFetcher::mlptr = static_cast >(context.matlab); - } - - if (!callback.empty()) { - newproxy->addCallback_helper(callback); - } - - // obtain a proxy ID - libmexclass::proxy::ID proxyid = libmexclass::proxy::ProxyManager::manageProxy(proxy); +void MeterProxy::createObservableCounter(libmexclass::proxy::method::Context& context) { + createAsynchronous(context, AsynchronousInstrumentType::ObservableCounter); +} - // return the ID - matlab::data::ArrayFactory factory; - auto proxyid_mda = factory.createScalar(proxyid); - context.outputs[0] = proxyid_mda; +void MeterProxy::createObservableUpDownCounter(libmexclass::proxy::method::Context& context) { + createAsynchronous(context, AsynchronousInstrumentType::ObservableUpDownCounter); } void MeterProxy::createObservableGauge(libmexclass::proxy::method::Context& context) { - // Always assumes 4 inputs - matlab::data::StringArray name_mda = context.inputs[0]; - std::string name = static_cast(name_mda[0]); - matlab::data::StringArray description_mda = context.inputs[1]; - std::string description= static_cast(description_mda[0]); - matlab::data::StringArray unit_mda = context.inputs[2]; - std::string unit = static_cast(unit_mda[0]); - matlab::data::StringArray callback_mda = context.inputs[3]; - std::string callback = static_cast(callback_mda[0]); - - nostd::shared_ptr gauge = CppMeter->CreateDoubleObservableGauge(name, description, unit); - - // instantiate an ObservableGaugeProxy instance - ObservableGaugeProxy* newproxy = new ObservableGaugeProxy(gauge); - auto proxy = std::shared_ptr(newproxy); - - if (MeasurementFetcher::mlptr == nullptr) { - MeasurementFetcher::mlptr = static_cast >(context.matlab); - } - - if (!callback.empty()) { - newproxy->addCallback_helper(callback); - } - - // obtain a proxy ID - libmexclass::proxy::ID proxyid = libmexclass::proxy::ProxyManager::manageProxy(proxy); - - // return the ID - matlab::data::ArrayFactory factory; - auto proxyid_mda = factory.createScalar(proxyid); - context.outputs[0] = proxyid_mda; + createAsynchronous(context, AsynchronousInstrumentType::ObservableGauge); } } // namespace libmexclass::opentelemetry From 117c0e676cca54e31e8fce06eaa692a718bb935e Mon Sep 17 00:00:00 2001 From: duncanpo Date: Mon, 22 Jan 2024 13:46:37 -0500 Subject: [PATCH 04/11] Help texts for asynchronous instruments --- .../+metrics/AsynchronousInstrument.m | 22 +++++++-- api/metrics/+opentelemetry/+metrics/Meter.m | 48 +++++++++++++++++-- .../+metrics/ObservableResult.m | 15 +++++- .../+metrics/SynchronousInstrument.m | 10 ++-- sdk/metrics/src/ViewProxy.cpp | 12 +++-- 5 files changed, 89 insertions(+), 18 deletions(-) diff --git a/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m b/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m index f2e80e3..d03ec5e 100644 --- a/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m +++ b/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m @@ -1,16 +1,16 @@ classdef AsynchronousInstrument < handle % Base class inherited by all asynchronous instruments - % Copyright 2023 The MathWorks, Inc. + % Copyright 2023-2024 The MathWorks, Inc. properties (SetAccess=immutable) - Name (1,1) string - Description (1,1) string - Unit (1,1) string + Name (1,1) string % Instrument name + Description (1,1) string % Description of instrument + Unit (1,1) string % Measurement unit end properties (SetAccess=private) - Callbacks + Callbacks % Callback function, called at each data export end properties (Access=private) @@ -30,6 +30,13 @@ methods function addCallback(obj, callback) + % ADDCALLBACK Add a callback function + % ADDCALLBACK(INST, CALLBACK) adds a callback function to + % collect metrics at every export. CALLBACK is specified as a + % function handle, and must accept no input and return one + % output of type opentelemetry.metrics.ObservableResult. + % + % See also REMOVECALLBACK, OPENTELEMETRY.METRICS.OBSERVABLERESULT if isa(callback, "function_handle") callbackstr = string(func2str(callback)); if ~startsWith(callbackstr, '@') % do not allow anonymous functions for now @@ -47,6 +54,11 @@ function addCallback(obj, callback) end function removeCallback(obj, callback) + % REMOVECALLBACK Remove a callback function + % REMOVECALLBACK(INST, CALLBACK) removes a callback function + % CALLBACK specified as a function handle. + % + % See also ADDCALLBACK if isa(callback, "function_handle") && ~isempty(obj.Callbacks) callbackstr = string(func2str(callback)); if iscell(obj.Callbacks) diff --git a/api/metrics/+opentelemetry/+metrics/Meter.m b/api/metrics/+opentelemetry/+metrics/Meter.m index 5562ec0..5091272 100644 --- a/api/metrics/+opentelemetry/+metrics/Meter.m +++ b/api/metrics/+opentelemetry/+metrics/Meter.m @@ -2,7 +2,7 @@ % A Meter creates metric instruments, capturing measurements about a service at runtime. % Meters are created from Meter Providers. - % Copyright 2023 The MathWorks, Inc. + % Copyright 2023-2024 The MathWorks, Inc. properties (SetAccess=immutable) Name (1,1) string % Meter name @@ -38,7 +38,8 @@ % C = CREATECOUNTER(M, NAME, DESCRIPTION, UNIT) also % specifies a description and a unit. % - % See also CREATEUPDOWNCOUNTER, CREATEHISTOGRAM + % See also CREATEUPDOWNCOUNTER, CREATEHISTOGRAM, + % CREATEOBSERVABLECOUNTER arguments obj name @@ -63,7 +64,8 @@ % C = CREATEUPDOWNCOUNTER(M, NAME, DESCRIPTION, UNIT) also % specifies a description and a unit. % - % See also CREATECOUNTER, CREATEHISTOGRAM + % See also CREATECOUNTER, CREATEHISTOGRAM, + % CREATEOBSERVABLEUPDOWNCOUNTER arguments obj name @@ -107,6 +109,19 @@ end function obscounter = createObservableCounter(obj, callback, name, description, unit) + % CREATEOBSERVABLECOUNTER Create an observable counter + % C = CREATEOBSERVABLECOUNTER(M, CALLBACK, NAME) creates an + % observable counter with the specified callback function + % and name. The callback function, specified as a + % function handle, must accept no input and return one + % output of type opentelemetry.metrics.ObservableResult. + % The counter's value can only increase but not decrease. + % + % C = CREATEOBSERVABLECOUNTER(M, CALLBACK NAME, DESCRIPTION, UNIT) + % also specifies a description and a unit. + % + % See also OPENTELEMETRY.METRICS.OBSERVABLERESULT, + % CREATEOBSERVABLEUPDOWNCOUNTER, CREATEOBSERVABLEGAUGE, CREATECOUNTER arguments obj callback @@ -124,6 +139,19 @@ end function obsudcounter = createObservableUpDownCounter(obj, callback, name, description, unit) + % CREATEOBSERVABLEUPDOWNCOUNTER Create an observable UpDownCounter + % C = CREATEOBSERVABLEUPDOWNCOUNTER(M, CALLBACK, NAME) + % creates an observable UpDownCounter with the specified + % callback function and name. The callback function, + % specified as a function handle, must accept no input and + % return one output of type opentelemetry.metrics.ObservableResult. + % The UpDownCounter's value can increase or decrease. + % + % C = CREATEOBSERVABLEUPDOWNCOUNTER(M, CALLBACK, NAME, DESCRIPTION, UNIT) + % also specifies a description and a unit. + % + % See also OPENTELEMETRY.METRICS.OBSERVABLERESULT, + % CREATEOBSERVABLECOUNTER, CREATEOBSERVABLEGAUGE, CREATEUPDOWNCOUNTER arguments obj callback @@ -142,6 +170,20 @@ end function obsgauge = createObservableGauge(obj, callback, name, description, unit) + % CREATEOBSERVABLEGAUGE Create an observable gauge + % C = CREATEOBSERVABLEGAUGE(M, CALLBACK, NAME) creates an + % observable gauge with the specified callback function + % and name. The callback function, specified as a + % function handle, must accept no input and return one + % output of type opentelemetry.metrics.ObservableResult. + % A gauge's value can increase or decrease but it should + % never be summed in aggregation. + % + % C = CREATEOBSERVABLEGAUGE(M, CALLBACK NAME, DESCRIPTION, UNIT) + % also specifies a description and a unit. + % + % See also OPENTELEMETRY.METRICS.OBSERVABLERESULT, + % CREATEOBSERVABLECOUNTER, CREATEOBSERVABLEUPDOWNCOUNTER arguments obj callback diff --git a/api/metrics/+opentelemetry/+metrics/ObservableResult.m b/api/metrics/+opentelemetry/+metrics/ObservableResult.m index 0dcacb9..f681ed8 100644 --- a/api/metrics/+opentelemetry/+metrics/ObservableResult.m +++ b/api/metrics/+opentelemetry/+metrics/ObservableResult.m @@ -1,13 +1,24 @@ classdef ObservableResult % Object to record results from observable instrument callbacks - % Copyright 2023 The MathWorks, Inc + % Copyright 2023-2024 The MathWorks, Inc properties (SetAccess=private, Hidden) - Results = cell(1,0) + Results = cell(1,0) % observed results. Each observation in a cell end methods function obj = observe(obj, value, varargin) + % OBSERVE Record a new metric value + % R = OBSERVE(R, VAL) records a new metric in VAL. VAL must + % be a real numeric scalar that can be converted to a + % double. + % + % R = OBSERVE(R, VAL, ATTRIBUTES) also specifies attributes + % as a dictionary. + % + % R = OBSERVE(R, VAL, ATTRNAME1, ATTRVALUE1, ATTRNAME2, + % ATTRVALUE2, ...) specifies attributes as trailing + % name-value pairs. if isnumeric(value) && isscalar(value) && isreal(value) value = double(value); if nargin == 2 diff --git a/api/metrics/+opentelemetry/+metrics/SynchronousInstrument.m b/api/metrics/+opentelemetry/+metrics/SynchronousInstrument.m index a6d6b0b..91f92e7 100644 --- a/api/metrics/+opentelemetry/+metrics/SynchronousInstrument.m +++ b/api/metrics/+opentelemetry/+metrics/SynchronousInstrument.m @@ -1,12 +1,12 @@ classdef SynchronousInstrument < handle % Base class inherited by all synchronous instruments - % Copyright 2023 The MathWorks, Inc. + % Copyright 2023-2024 The MathWorks, Inc. properties (SetAccess=immutable) - Name (1,1) string - Description (1,1) string - Unit (1,1) string + Name (1,1) string % Instrument name + Description (1,1) string % Description of instrument + Unit (1,1) string % Measurement unit end properties (Access=private) @@ -41,4 +41,4 @@ function processValue(obj, value, varargin) end end end -end \ No newline at end of file +end diff --git a/sdk/metrics/src/ViewProxy.cpp b/sdk/metrics/src/ViewProxy.cpp index 7124ad0..c3ec4b4 100644 --- a/sdk/metrics/src/ViewProxy.cpp +++ b/sdk/metrics/src/ViewProxy.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #include "opentelemetry-matlab/sdk/metrics/ViewProxy.h" @@ -28,9 +28,15 @@ libmexclass::proxy::MakeResult ViewProxy::make(const libmexclass::proxy::Functio instrument_type = metrics_sdk::InstrumentType::kCounter; } else if (instrument_type_str.compare(u"updowncounter") == 0) { instrument_type = metrics_sdk::InstrumentType::kUpDownCounter; - } else { - assert(instrument_type_str.compare(u"histogram") == 0); + } else if (instrument_type_str.compare(u"histogram") == 0) { instrument_type = metrics_sdk::InstrumentType::kHistogram; + } else if (instrument_type_str.compare(u"observablecounter") == 0) { + instrument_type = metrics_sdk::InstrumentType::kObservableCounter; + } else if (instrument_type_str.compare(u"observableupdowncounter") == 0) { + instrument_type = metrics_sdk::InstrumentType::kObservableUpDownCounter; + } else { + assert(instrument_type_str.compare(u"observablegauge") == 0); + instrument_type = metrics_sdk::InstrumentType::kObservableGauge; } // InstrumentUnit From 47db7bd592840fca065efdccba641f490f7c3edb Mon Sep 17 00:00:00 2001 From: duncanpo Date: Wed, 24 Jan 2024 14:07:43 -0500 Subject: [PATCH 05/11] Asynchronous instruments --- README.md | 2 +- .../+metrics/AsynchronousInstrument.m | 29 +- api/metrics/+opentelemetry/+metrics/Meter.m | 24 +- .../+metrics/collectObservableMetrics.m | 7 + .../metrics/AsynchronousInstrumentProxy.h | 6 +- .../AsynchronousInstrumentProxyFactory.h | 4 +- .../src/AsynchronousInstrumentProxy.cpp | 22 +- .../AsynchronousInstrumentProxyFactory.cpp | 6 +- api/metrics/src/MeasurementFetcher.cpp | 6 +- api/metrics/src/MeterProxy.cpp | 7 +- test/callbacks/callbackNoAttributes.m | 8 + test/callbacks/callbackOneInput.m | 8 + test/callbacks/callbackWithAttributes.m | 10 + test/callbacks/callbackWithAttributes2.m | 8 + test/tmetrics.m | 247 ++++++++++++++---- test/tmetrics_sdk.m | 24 +- 16 files changed, 300 insertions(+), 118 deletions(-) create mode 100644 api/metrics/+opentelemetry/+metrics/collectObservableMetrics.m create mode 100644 test/callbacks/callbackNoAttributes.m create mode 100644 test/callbacks/callbackOneInput.m create mode 100644 test/callbacks/callbackWithAttributes.m create mode 100644 test/callbacks/callbackWithAttributes2.m diff --git a/README.md b/README.md index b4b950a..ae64fc1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ MATLAB® interface to [OpenTelemetry™](https://opentelemetry.io/), based on the [OpenTelemetry Specification](https://opentelemetry.io/docs/reference/specification/). OpenTelemetry is an observability framework for creating and managing telemetry data, such as traces, metrics, and logs. This data can then be sent to an observability back-end for monitoring, alerts, and analysis. ### Status -- Currently only tracing and metrics (synchronous instruments) are supported. Asynchronous metrics instruments and logs will be in the future. +- Currently only tracing and metrics are supported. Logs will be in the future. - View class in metrics is only partially supported. The properties **Aggregation** and **AllowedAttributes** are not yet supported. - This package is supported and has been tested on Windows®, Linux®, and macOS. diff --git a/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m b/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m index d03ec5e..f88c526 100644 --- a/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m +++ b/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m @@ -38,17 +38,14 @@ function addCallback(obj, callback) % % See also REMOVECALLBACK, OPENTELEMETRY.METRICS.OBSERVABLERESULT if isa(callback, "function_handle") - callbackstr = string(func2str(callback)); - if ~startsWith(callbackstr, '@') % do not allow anonymous functions for now - obj.Proxy.addCallback(callbackstr); - % append to Callbacks property - if isempty(obj.Callbacks) - obj.Callbacks = callback; - elseif isa(obj.Callbacks, "function_handle") - obj.Callbacks = {obj.Callbacks, callback}; - else - obj.Callbacks = [obj.Callbacks, {callback}]; - end + obj.Proxy.addCallback(callback); + % append to Callbacks property + if isempty(obj.Callbacks) + obj.Callbacks = callback; + elseif isa(obj.Callbacks, "function_handle") + obj.Callbacks = {obj.Callbacks, callback}; + else + obj.Callbacks = [obj.Callbacks, {callback}]; end end end @@ -60,19 +57,19 @@ function removeCallback(obj, callback) % % See also ADDCALLBACK if isa(callback, "function_handle") && ~isempty(obj.Callbacks) - callbackstr = string(func2str(callback)); if iscell(obj.Callbacks) - found = strcmp(cellfun(@func2str, obj.Callbacks, 'UniformOutput', false), callbackstr); + found = cellfun(@(x)isequal(x,callback), obj.Callbacks); else % scalar function handle - found = strcmp(func2str(obj.Callbacks), callbackstr); + found = isequal(obj.Callbacks, callback); end if sum(found) > 0 - obj.Proxy.removeCallback(callbackstr); + idx = find(found,1); % remove only the first match + obj.Proxy.removeCallback(callback, idx); % update Callback property if isa(obj.Callbacks, "function_handle") obj.Callbacks = []; else - obj.Callbacks(find(found,1)) = []; % remove only the first match + obj.Callbacks(idx) = []; if isscalar(obj.Callbacks) % if there is only one left, remove the cell obj.Callbacks = obj.Callbacks{1}; end diff --git a/api/metrics/+opentelemetry/+metrics/Meter.m b/api/metrics/+opentelemetry/+metrics/Meter.m index 5091272..a9509c4 100644 --- a/api/metrics/+opentelemetry/+metrics/Meter.m +++ b/api/metrics/+opentelemetry/+metrics/Meter.m @@ -130,9 +130,9 @@ unit = "" end - [callback, callbackstr, name, description, unit] = processAsynchronousInputs(... + [callback, name, description, unit] = processAsynchronousInputs(... callback, name, description, unit); - id = obj.Proxy.createObservableCounter(name, description, unit, callbackstr); + id = obj.Proxy.createObservableCounter(name, description, unit, callback); ObservableCounterproxy = libmexclass.proxy.Proxy("Name", ... "libmexclass.opentelemetry.ObservableCounterProxy", "ID", id); obscounter = opentelemetry.metrics.ObservableCounter(ObservableCounterproxy, name, description, unit, callback); @@ -160,9 +160,9 @@ unit = "" end - [callback, callbackstr, name, description, unit] = processAsynchronousInputs(... + [callback, name, description, unit] = processAsynchronousInputs(... callback, name, description, unit); - id = obj.Proxy.createObservableUpDownCounter(name, description, unit, callbackstr); + id = obj.Proxy.createObservableUpDownCounter(name, description, unit, callback); ObservableUpDownCounterproxy = libmexclass.proxy.Proxy("Name", ... "libmexclass.opentelemetry.ObservableUpDownCounterProxy", "ID", id); obsudcounter = opentelemetry.metrics.ObservableUpDownCounter(... @@ -192,9 +192,9 @@ unit = "" end - [callback, callbackstr, name, description, unit] = processAsynchronousInputs(... + [callback, name, description, unit] = processAsynchronousInputs(... callback, name, description, unit); - id = obj.Proxy.createObservableGauge(name, description, unit, callbackstr); + id = obj.Proxy.createObservableGauge(name, description, unit, callback); ObservableGaugeproxy = libmexclass.proxy.Proxy("Name", ... "libmexclass.opentelemetry.ObservableGaugeProxy", "ID", id); obsgauge = opentelemetry.metrics.ObservableGauge(... @@ -211,16 +211,10 @@ unit = mustBeScalarString(unit); end -function [callback, callbackstr, name, description, unit] = processAsynchronousInputs(... +function [callback, name, description, unit] = processAsynchronousInputs(... callback, name, description, unit) [name, description, unit] = processSynchronousInputs(name, description, unit); -if isa(callback, "function_handle") - callbackstr = string(func2str(callback)); - if ~startsWith(callbackstr, '@') % do not allow anonymous functions for now - return - end +if ~isa(callback, "function_handle") + callback = []; % callback is invalid, set to empty double end -% if we get here, callback is invalid -callback = []; -callbackstr = ""; end diff --git a/api/metrics/+opentelemetry/+metrics/collectObservableMetrics.m b/api/metrics/+opentelemetry/+metrics/collectObservableMetrics.m new file mode 100644 index 0000000..43ec812 --- /dev/null +++ b/api/metrics/+opentelemetry/+metrics/collectObservableMetrics.m @@ -0,0 +1,7 @@ +function result = collectObservableMetrics(fh) +% Internal function used to call callback functions for asynchronous +% instruments + +% Copyright 2024 The MathWorks, Inc. + +result = feval(fh); diff --git a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h index faac52c..c7a2300 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #pragma once @@ -20,14 +20,14 @@ class AsynchronousInstrumentProxy : public libmexclass::proxy::Proxy { // This method should ideally be an overloaded version of addCallback. However, addCallback is a registered // method and REGISTER_METHOD macro doesn't like overloaded methods. Rename to avoid overloading. - void addCallback_helper(const std::string& callback); + void addCallback_helper(const matlab::data::Array& callback); void removeCallback(libmexclass::proxy::method::Context& context); private: nostd::shared_ptr CppInstrument; - std::list CallbackFunctions; + std::list CallbackFunctions; }; } // namespace libmexclass::opentelemetry diff --git a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h index 73f5553..a2a559a 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #pragma once @@ -18,7 +18,7 @@ class AsynchronousInstrumentProxyFactory { AsynchronousInstrumentProxyFactory(nostd::shared_ptr mt) : CppMeter(mt) {} std::shared_ptr create(AsynchronousInstrumentType type, - const std::string& callback, const std::string& name, const std::string& description, + const matlab::data::Array& callback, const std::string& name, const std::string& description, const std::string& unit); private: diff --git a/api/metrics/src/AsynchronousInstrumentProxy.cpp b/api/metrics/src/AsynchronousInstrumentProxy.cpp index f4fb1bf..8613339 100644 --- a/api/metrics/src/AsynchronousInstrumentProxy.cpp +++ b/api/metrics/src/AsynchronousInstrumentProxy.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #include "opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h" #include "opentelemetry-matlab/metrics/MeasurementFetcher.h" @@ -10,24 +10,22 @@ namespace libmexclass::opentelemetry { void AsynchronousInstrumentProxy::addCallback(libmexclass::proxy::method::Context& context){ - matlab::data::StringArray callback_mda = context.inputs[0]; - std::string callback = static_cast(callback_mda[0]); - addCallback_helper(callback); + addCallback_helper(context.inputs[0]); } -void AsynchronousInstrumentProxy::addCallback_helper(const std::string& callback){ +void AsynchronousInstrumentProxy::addCallback_helper(const matlab::data::Array& callback){ CallbackFunctions.push_back(callback); CppInstrument->AddCallback(MeasurementFetcher::Fetcher, static_cast(&CallbackFunctions.back())); } void AsynchronousInstrumentProxy::removeCallback(libmexclass::proxy::method::Context& context){ - matlab::data::StringArray callback_mda = context.inputs[0]; - std::string callback = static_cast(callback_mda[0]); - auto iter = std::find(CallbackFunctions.begin(), CallbackFunctions.end(), callback); - if (iter != CallbackFunctions.end()) { // found a match - CallbackFunctions.erase(iter); - CppInstrument->RemoveCallback(MeasurementFetcher::Fetcher, static_cast(&(*iter))); - } + matlab::data::Array callback_mda = context.inputs[0]; + matlab::data::TypedArray idx_mda = context.inputs[1]; + double idx = idx_mda[0] - 1; // adjust index from 1-based in MATLAB to 0-based in C++ + auto iter = CallbackFunctions.begin(); + std::advance(iter, idx); + CallbackFunctions.erase(iter); + CppInstrument->RemoveCallback(MeasurementFetcher::Fetcher, static_cast(&(*iter))); } } // namespace libmexclass::opentelemetry diff --git a/api/metrics/src/AsynchronousInstrumentProxyFactory.cpp b/api/metrics/src/AsynchronousInstrumentProxyFactory.cpp index ec753c3..7ad353b 100644 --- a/api/metrics/src/AsynchronousInstrumentProxyFactory.cpp +++ b/api/metrics/src/AsynchronousInstrumentProxyFactory.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #include "opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h" #include "opentelemetry-matlab/metrics/ObservableCounterProxy.h" @@ -7,7 +7,7 @@ namespace libmexclass::opentelemetry { std::shared_ptr AsynchronousInstrumentProxyFactory::create(AsynchronousInstrumentType type, - const std::string& callback, const std::string& name, const std::string& description, const std::string& unit) { + const matlab::data::Array& callback, const std::string& name, const std::string& description, const std::string& unit) { std::shared_ptr proxy; switch(type) { case AsynchronousInstrumentType::ObservableCounter: @@ -30,7 +30,7 @@ std::shared_ptr AsynchronousInstrumentProxyFactory::c break; } // add callback - if (!callback.empty()) { + if (!callback.isEmpty()) { std::static_pointer_cast(proxy)->addCallback_helper(callback); } return proxy; diff --git a/api/metrics/src/MeasurementFetcher.cpp b/api/metrics/src/MeasurementFetcher.cpp index 962bbd3..eb4cb22 100644 --- a/api/metrics/src/MeasurementFetcher.cpp +++ b/api/metrics/src/MeasurementFetcher.cpp @@ -1,3 +1,4 @@ +// Copyright 2023-2024 The MathWorks, Inc. #include "MatlabDataArray.hpp" #include "mex.hpp" @@ -22,12 +23,13 @@ namespace metrics_api = opentelemetry::metrics; namespace nostd = opentelemetry::nostd; namespace libmexclass::opentelemetry { -void MeasurementFetcher::Fetcher(metrics_api::ObserverResult observer_result, void * fcn_name_ptr) +void MeasurementFetcher::Fetcher(metrics_api::ObserverResult observer_result, void * fh) { if (nostd::holds_alternative< nostd::shared_ptr>>(observer_result)) { - auto future = mlptr->fevalAsync(static_cast(fcn_name_ptr)->c_str(), std::vector()); + auto future = mlptr->fevalAsync(u"opentelemetry.metrics.collectObservableMetrics", + *(static_cast(fh))); try { matlab::data::ObjectArray resultobj = future.get(); auto futureresult = mlptr->getPropertyAsync(resultobj, 0, u"Results"); diff --git a/api/metrics/src/MeterProxy.cpp b/api/metrics/src/MeterProxy.cpp index 6dbbf57..6492b80 100644 --- a/api/metrics/src/MeterProxy.cpp +++ b/api/metrics/src/MeterProxy.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #include "opentelemetry-matlab/metrics/MeterProxy.h" #include "opentelemetry-matlab/metrics/MeasurementFetcher.h" @@ -54,8 +54,7 @@ void MeterProxy::createAsynchronous(libmexclass::proxy::method::Context& context std::string description= static_cast(description_mda[0]); matlab::data::StringArray unit_mda = context.inputs[2]; std::string unit = static_cast(unit_mda[0]); - matlab::data::StringArray callback_mda = context.inputs[3]; - std::string callback = static_cast(callback_mda[0]); + matlab::data::Array callback_mda = context.inputs[3]; // initialize MATLAB mex engine the first time an asynchronous instrument is created if (MeasurementFetcher::mlptr == nullptr) { @@ -63,7 +62,7 @@ void MeterProxy::createAsynchronous(libmexclass::proxy::method::Context& context } AsynchronousInstrumentProxyFactory proxyfactory(CppMeter); - auto proxy = proxyfactory.create(type, callback, name, description, unit); + auto proxy = proxyfactory.create(type, callback_mda, name, description, unit); // obtain a proxy ID libmexclass::proxy::ID proxyid = libmexclass::proxy::ProxyManager::manageProxy(proxy); diff --git a/test/callbacks/callbackNoAttributes.m b/test/callbacks/callbackNoAttributes.m new file mode 100644 index 0000000..9b4e799 --- /dev/null +++ b/test/callbacks/callbackNoAttributes.m @@ -0,0 +1,8 @@ +function result = callbackNoAttributes() +% Test callback function for asynchronous instruments +% +% Copyright 2024 The MathWorks, Inc. + +value = 5; +result = opentelemetry.metrics.ObservableResult; +result = result.observe(value); \ No newline at end of file diff --git a/test/callbacks/callbackOneInput.m b/test/callbacks/callbackOneInput.m new file mode 100644 index 0000000..11bff4b --- /dev/null +++ b/test/callbacks/callbackOneInput.m @@ -0,0 +1,8 @@ +function result = callbackOneInput(addvalue) +% Test callback function for asynchronous instruments +% +% Copyright 2024 The MathWorks, Inc. + +value = 5 + addvalue; +result = opentelemetry.metrics.ObservableResult; +result = result.observe(value); \ No newline at end of file diff --git a/test/callbacks/callbackWithAttributes.m b/test/callbacks/callbackWithAttributes.m new file mode 100644 index 0000000..570f5e5 --- /dev/null +++ b/test/callbacks/callbackWithAttributes.m @@ -0,0 +1,10 @@ +function result = callbackWithAttributes() +% Test callback function for asynchronous instruments that uses attributes +% +% Copyright 2024 The MathWorks, Inc. + +value1 = 5; +value2 = 10; +result = opentelemetry.metrics.ObservableResult; +result = result.observe(value1, "Level", "A"); +result = result.observe(value2, "Level", "B"); \ No newline at end of file diff --git a/test/callbacks/callbackWithAttributes2.m b/test/callbacks/callbackWithAttributes2.m new file mode 100644 index 0000000..7b92b0e --- /dev/null +++ b/test/callbacks/callbackWithAttributes2.m @@ -0,0 +1,8 @@ +function result = callbackWithAttributes2() +% Test callback function for asynchronous instruments that uses attributes +% +% Copyright 2024 The MathWorks, Inc. + +value = 20; +result = opentelemetry.metrics.ObservableResult; +result = result.observe(value, "Level", "C"); \ No newline at end of file diff --git a/test/tmetrics.m b/test/tmetrics.m index 96adc99..5e75d2b 100644 --- a/test/tmetrics.m +++ b/test/tmetrics.m @@ -1,7 +1,7 @@ classdef tmetrics < matlab.unittest.TestCase - % tests for traces and spans + % tests for metrics - % Copyright 2023 The MathWorks, Inc. + % Copyright 2023-2024 The MathWorks, Inc. properties OtelConfigFile @@ -17,11 +17,17 @@ Sigterm ShortIntervalReader DeltaAggregationReader + WaitTime end methods (TestClassSetup) function setupOnce(testCase) commonSetupOnce(testCase); + + % add the callbacks folder to the path + callbackfolder = fullfile(fileparts(mfilename('fullpath')), "callbacks"); + testCase.applyFixture(matlab.unittest.fixtures.PathFixture(callbackfolder)); + interval = seconds(2); timeout = seconds(1); testCase.ShortIntervalReader = opentelemetry.sdk.metrics.PeriodicExportingMetricReader(... @@ -31,6 +37,7 @@ function setupOnce(testCase) opentelemetry.exporters.otlp.OtlpHttpMetricExporter(... "PreferredAggregationTemporality", "Delta"), ... "Interval", interval, "Timeout", timeout); + testCase.WaitTime = seconds(interval * 1.25); end end @@ -65,11 +72,11 @@ function testCounterBasic(testCase) % create testing value val = 10; - % add value and attributes + % add value ct.add(val); % wait for collector response - pause(2.5); + pause(testCase.WaitTime); % fetch result clear p; @@ -80,11 +87,8 @@ function testCounterBasic(testCase) verifyEqual(testCase, string(results.resourceMetrics.scopeMetrics.metrics.name), countername); verifyEqual(testCase, string(results.resourceMetrics.scopeMetrics.scope.name), metername); - % fetch datapoint - dp = results.resourceMetrics.scopeMetrics.metrics.sum.dataPoints; - % verify counter value - verifyEqual(testCase, dp.asDouble, val); + verifyEqual(testCase, results.resourceMetrics.scopeMetrics.metrics.sum.dataPoints.asDouble, val); end @@ -97,29 +101,24 @@ function testCounterDelta(testCase) ct = mt.createCounter(countername); % verify MATLAB object properties - verifyEqual(testCase, mt.Name, metername); - verifyEqual(testCase, mt.Version, ""); - verifyEqual(testCase, mt.Schema, ""); verifyEqual(testCase, ct.Name, countername); % create testing value vals = [10, 20]; - % add value and attributes + % add value ct.add(vals(1)); - pause(3); + pause(testCase.WaitTime); ct.add(vals(2)); % fetch results - pause(2.5); + pause(testCase.WaitTime); clear p; results = readJsonResults(testCase); - dp1 = results{1}.resourceMetrics.scopeMetrics.metrics.sum.dataPoints; - dp2 = results{2}.resourceMetrics.scopeMetrics.metrics.sum.dataPoints; % verify counter value - verifyEqual(testCase, dp1.asDouble, vals(1)); - verifyEqual(testCase, dp2.asDouble, vals(2)); + verifyEqual(testCase, results{1}.resourceMetrics.scopeMetrics.metrics.sum.dataPoints.asDouble, vals(1)); + verifyEqual(testCase, results{2}.resourceMetrics.scopeMetrics.metrics.sum.dataPoints.asDouble, vals(2)); end @@ -145,7 +144,7 @@ function testCounterAddAttributes(testCase) ct.add(vals(3),dict_keys(1),dict_vals(1),dict_keys(2),dict_vals(2)); % wait for collector response - pause(2.5); + pause(testCase.WaitTime); % fetch result clear p; @@ -188,17 +187,12 @@ function testCounterInvalidAdd(testCase) ct.add(magic(3)); % add nonnumerics ct.add("foobar"); - pause(2.5); + pause(testCase.WaitTime); % fetch results clear p; results = readJsonResults(testCase); - results = results{end}; - dp = results.resourceMetrics.scopeMetrics.metrics.sum.dataPoints; - - % verify that the counter value is still 0 - verifyEqual(testCase, dp.asDouble, 0); - + verifyEmpty(testCase, results); % results should be empty since all adds were invalid end @@ -213,19 +207,16 @@ function testUpDownCounterBasic(testCase) ct = mt.createUpDownCounter(countername); % verify MATLAB object properties - verifyEqual(testCase, mt.Name, metername); - verifyEqual(testCase, mt.Version, ""); - verifyEqual(testCase, mt.Schema, ""); verifyEqual(testCase, ct.Name, countername); % create testing value val = -10; - % add value and attributes + % add value ct.add(val); - % wait for collector response time (2s) - pause(5); + % wait for collector response time + pause(testCase.WaitTime); % fetch result clear p; @@ -236,11 +227,8 @@ function testUpDownCounterBasic(testCase) verifyEqual(testCase, string(results.resourceMetrics.scopeMetrics.metrics.name), countername); verifyEqual(testCase, string(results.resourceMetrics.scopeMetrics.scope.name), metername); - % fetch datapoint - dp = results.resourceMetrics.scopeMetrics.metrics.sum.dataPoints; - % verify counter value - verifyEqual(testCase, dp.asDouble, val); + verifyEqual(testCase, results.resourceMetrics.scopeMetrics.metrics.sum.dataPoints.asDouble, val); end @@ -263,7 +251,7 @@ function testUpDownCounterAddAttributes(testCase) ct.add(vals(3),dict_keys(1),dict_vals(1),dict_keys(2),dict_vals(2)); % wait for collector response - pause(5); + pause(testCase.WaitTime); % fetch result clear p; @@ -297,12 +285,12 @@ function testUpDownCounterInvalidAdd(testCase) ct.add(magic(3)); % add nonnumerics ct.add("foobar"); - pause(2.5); + pause(testCase.WaitTime); % fetch results clear p; results = readJsonResults(testCase); - verifyEmpty(testCase, results); + verifyEmpty(testCase, results); % results should be empty since all adds were invalid end @@ -317,9 +305,6 @@ function testHistogramBasic(testCase) hist = mt.createHistogram(histname); % verify MATLAB object properties - verifyEqual(testCase, mt.Name, metername); - verifyEqual(testCase, mt.Version, ""); - verifyEqual(testCase, mt.Schema, ""); verifyEqual(testCase, hist.Name, histname); % create value for histogram @@ -329,7 +314,7 @@ function testHistogramBasic(testCase) hist.record(val); % wait for collector response - pause(10); + pause(testCase.WaitTime); % fetch results clear p; @@ -380,7 +365,7 @@ function testHistogramRecordAttributes(testCase) hist.record(vals(3),dict_keys(1),dict_vals(1),dict_keys(2),dict_vals(2)); % wait for collector response - pause(10); + pause(testCase.WaitTime); % fetch results clear p; @@ -428,12 +413,12 @@ function testHistogramInvalidValue(testCase) h.record(magic(3)); % record nonnumerics h.record("foobar"); - pause(2.5); + pause(testCase.WaitTime); % fetch results clear p; results = readJsonResults(testCase); - verifyEmpty(testCase, results); + verifyEmpty(testCase, results); % results should be empty since all adds were invalid end function testHistogramDelta(testCase) @@ -441,15 +426,15 @@ function testHistogramDelta(testCase) mt = p.getMeter("foo"); hist = mt.createHistogram("bar"); - % record value and attributes + % record values rawvals = [1 6]; vals = {[rawvals(1)], [rawvals(2)]}; hist.record(rawvals(1)); - pause(2.5); + pause(testCase.WaitTime); hist.record(rawvals(2)); % wait for collector response - pause(2.5); + pause(testCase.WaitTime); % fetch results clear p; @@ -479,6 +464,7 @@ function testHistogramDelta(testCase) end end + function testGetSetMeterProvider(testCase) % testGetSetMeterProvider: setting and getting global instance of MeterProvider mp = opentelemetry.sdk.metrics.MeterProvider(testCase.ShortIntervalReader); @@ -495,7 +481,7 @@ function testGetSetMeterProvider(testCase) % add value and attributes c.add(val); - pause(2.5); + pause(testCase.WaitTime); %Shutdown the Meter Provider verifyTrue(testCase, mp.shutdown()); @@ -510,6 +496,167 @@ function testGetSetMeterProvider(testCase) verifyEqual(testCase, string(results.resourceMetrics.scopeMetrics.metrics.name), countername); verifyEqual(testCase, string(results.resourceMetrics.scopeMetrics.scope.name), metername); end + end + + % parameters for asynchronous instruments + properties (TestParameter) + create_async = {@createObservableCounter, ... + @createObservableUpDownCounter, @createObservableGauge}; + datapoint_name = {'sum', 'sum', 'gauge'}; + end + + methods (Test, ParameterCombination="sequential") + function testAsynchronousInstrumentBasic(testCase, create_async, datapoint_name) + % test basic functionalities of an observable counter + countername = "bar"; + callback = @callbackNoAttributes; + + p = opentelemetry.sdk.metrics.MeterProvider(testCase.ShortIntervalReader); + mt = p.getMeter("foo"); + %ct = mt.createObservableCounter(callback, countername); + ct = create_async(mt, callback, countername); + + % verify MATLAB object properties + verifyEqual(testCase, ct.Name, countername); + + % wait for collector response + pause(testCase.WaitTime); + + % fetch result + clear p; + results = readJsonResults(testCase); + results = results{end}; + + % verify counter name + verifyEqual(testCase, string(results.resourceMetrics.scopeMetrics.metrics.name), countername); + + % verify counter value + verifyEqual(testCase, ... + results.resourceMetrics.scopeMetrics.metrics.(datapoint_name).dataPoints.asDouble, 5); + end + + function testAsynchronousInstrumentAttributes(testCase, create_async, datapoint_name) + % test for attributes when observing metrics for an observable counter + countername = "bar"; + callback = @callbackWithAttributes; + + p = opentelemetry.sdk.metrics.MeterProvider(testCase.ShortIntervalReader); + mt = p.getMeter("foo"); + ct = create_async(mt, callback, countername); %#ok + + % wait for collector response + pause(testCase.WaitTime); + + % fetch result + clear p; + results = readJsonResults(testCase); + results = results{end}; + + % verify counter name + verifyEqual(testCase, string(results.resourceMetrics.scopeMetrics.metrics.name), countername); + + % verify counter values and attributes + dp = results.resourceMetrics.scopeMetrics.metrics.(datapoint_name).dataPoints; + attrvals = arrayfun(@(x)string(x.attributes.value.stringValue), dp); + idxA = (attrvals == "A"); + idxB = (attrvals == "B"); + verifyEqual(testCase, dp(idxA).asDouble, 5); + verifyEqual(testCase, string(dp(idxA).attributes.key), "Level"); + verifyEqual(testCase, string(dp(idxA).attributes.value.stringValue), "A"); + verifyEqual(testCase, dp(idxB).asDouble, 10); + verifyEqual(testCase, string(dp(idxB).attributes.key), "Level"); + verifyEqual(testCase, string(dp(idxB).attributes.value.stringValue), "B"); + end + + function testAsynchronousInstrumentAnonymousCallback(testCase, create_async, datapoint_name) + % use an anonymous function as callback + countername = "bar"; + addvalue = 20; + callback = @(x)callbackOneInput(addvalue); + + p = opentelemetry.sdk.metrics.MeterProvider(testCase.ShortIntervalReader); + mt = p.getMeter("foo"); + ct = create_async(mt, callback, countername); %#ok + + % wait for collector response + pause(testCase.WaitTime); + + % fetch result + clear p; + results = readJsonResults(testCase); + results = results{end}; + + % verify counter name + verifyEqual(testCase, string(results.resourceMetrics.scopeMetrics.metrics.name), countername); + + % verify counter value + verifyEqual(testCase, ... + results.resourceMetrics.scopeMetrics.metrics.(datapoint_name).dataPoints.asDouble, 5 + addvalue); + end + + function testAsynchronousInstrumentMultipleCallbacks(testCase, create_async, datapoint_name) + % Observable counter with more than one callbacks + + testCase.assumeTrue(false, ... + "Disabled due to sporadic failures."); + + countername = "bar"; + + p = opentelemetry.sdk.metrics.MeterProvider(testCase.ShortIntervalReader); + mt = p.getMeter("foo"); + ct = create_async(mt, @callbackWithAttributes, countername); + addCallback(ct, @callbackWithAttributes2) + + % wait for collector response + pause(testCase.WaitTime); + + % fetch result + clear p; + results = readJsonResults(testCase); + results = results{end}; + + % verify counter name + verifyEqual(testCase, string(results.resourceMetrics.scopeMetrics.metrics.name), countername); + + % verify counter values and attributes + dp = results.resourceMetrics.scopeMetrics.metrics.(datapoint_name).dataPoints; + attrvals = arrayfun(@(x)string(x.attributes.value.stringValue), dp); + idxA = (attrvals == "A"); + idxB = (attrvals == "B"); + idxC = (attrvals == "C"); + verifyEqual(testCase, dp(idxA).asDouble, 5); + verifyEqual(testCase, string(dp(idxA).attributes.key), "Level"); + verifyEqual(testCase, string(dp(idxA).attributes.value.stringValue), "A"); + verifyEqual(testCase, dp(idxB).asDouble, 10); + verifyEqual(testCase, string(dp(idxB).attributes.key), "Level"); + verifyEqual(testCase, string(dp(idxB).attributes.value.stringValue), "B"); + verifyEqual(testCase, dp(idxC).asDouble, 20); + verifyEqual(testCase, string(dp(idxC).attributes.key), "Level"); + verifyEqual(testCase, string(dp(idxC).attributes.value.stringValue), "C"); + end + + function testAsynchronousInstrumentRemoveCallback(testCase, create_async) + % removeCallback method + + testCase.assumeTrue(false, ... + "Disabled due to sporadic failures."); + + callback = @callbackNoAttributes; + + p = opentelemetry.sdk.metrics.MeterProvider(testCase.ShortIntervalReader); + mt = p.getMeter("foo"); + ct = create_async(mt, callback, "foo2"); + removeCallback(ct, callback); + + % wait for collector response + pause(testCase.WaitTime); + + % fetch result + clear p; + results = readJsonResults(testCase); + + verifyEmpty(testCase, results); % expect empty result due to lack of callback + end end diff --git a/test/tmetrics_sdk.m b/test/tmetrics_sdk.m index 9fc82ba..29af901 100644 --- a/test/tmetrics_sdk.m +++ b/test/tmetrics_sdk.m @@ -1,7 +1,7 @@ classdef tmetrics_sdk < matlab.unittest.TestCase % tests for metrics SDK - % Copyright 2023 The MathWorks, Inc. + % Copyright 2023-2024 The MathWorks, Inc. properties OtelConfigFile @@ -15,14 +15,18 @@ Sigint Sigterm ShortIntervalReader + WaitTime end methods (TestClassSetup) function setupOnce(testCase) commonSetupOnce(testCase); + interval = seconds(2); + timeout = seconds(1); testCase.ShortIntervalReader = opentelemetry.sdk.metrics.PeriodicExportingMetricReader(... opentelemetry.exporters.otlp.OtlpHttpMetricExporter(), ... - "Interval", seconds(2), "Timeout", seconds(1)); + "Interval", interval, "Timeout", timeout); + testCase.WaitTime = seconds(interval * 1.25); end end @@ -106,7 +110,7 @@ function testAddMetricReader(testCase) % verify if the json results has two exported instances after % adding a single value ct.add(1); - pause(2.5); + pause(testCase.WaitTime); clear p; results = readJsonResults(testCase); result_count = numel(results); @@ -130,7 +134,7 @@ function testCustomResource(testCase) % add value and attributes c.add(val); - pause(2.5); + pause(testCase.WaitTime); clear mp; @@ -163,7 +167,7 @@ function testViewBasic(testCase) val = 10; c.add(val); - pause(2.5); + pause(testCase.WaitTime); clear m; results = readJsonResults(testCase); @@ -208,7 +212,7 @@ function testViewHistogram(testCase) hist.record(402); % wait for collector response - pause(2.5); + pause(testCase.WaitTime); clear m; results = readJsonResults(testCase); @@ -265,7 +269,7 @@ function testMultipleViews(testCase) cbar.add(valbar); cquux.add(valquux); - pause(2.5); + pause(testCase.WaitTime); clear mxyz mabc; results = readJsonResults(testCase); @@ -308,7 +312,7 @@ function testShutdown(testCase) % wait a little and then gather results, verify no metrics are % generated - pause(2.5); + pause(testCase.WaitTime); clear mp; results = readJsonResults(testCase); verifyEmpty(testCase, results); @@ -330,7 +334,7 @@ function testCleanupSdk(testCase) % wait a little and then gather results, verify no metrics are % generated - pause(2.5); + pause(testCase.WaitTime); clear mp; results = readJsonResults(testCase); verifyEmpty(testCase, results); @@ -355,7 +359,7 @@ function testCleanupApi(testCase) % wait a little and then gather results, verify no metrics are % generated - pause(2.5); + pause(testCase.WaitTime); clear("mp_api"); results = readJsonResults(testCase); verifyEmpty(testCase, results); From 76a137b5ba6a1bcb2adb2292214b5e815e41f3e3 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Wed, 24 Jan 2024 15:20:17 -0500 Subject: [PATCH 06/11] Asynchronous instruments --- .../include/opentelemetry-matlab/metrics/MeasurementFetcher.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h b/api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h index 632824f..ea1d7de 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h @@ -8,7 +8,7 @@ namespace libmexclass::opentelemetry { class MeasurementFetcher { public: - __declspec(dllexport) static void Fetcher(metrics_api::ObserverResult observer_result, void * /* state */); + static void Fetcher(metrics_api::ObserverResult observer_result, void * /* state */); static std::shared_ptr mlptr; }; } // namespace libmexclass::opentelemetry From e24aa64d5a5bac1a92e00c8f02f64111e85d29cc Mon Sep 17 00:00:00 2001 From: duncanpo Date: Wed, 24 Jan 2024 15:39:12 -0500 Subject: [PATCH 07/11] Asynchronous instruments --- .../opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h index c7a2300..fc86c53 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h @@ -2,6 +2,8 @@ #pragma once +#include + #include "libmexclass/proxy/Proxy.h" #include "libmexclass/proxy/method/Context.h" From 7e1872b61618f0d81b07c88d95d4902497f0a6c5 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Thu, 25 Jan 2024 09:34:31 -0500 Subject: [PATCH 08/11] fix a test failure in tmetrics --- test/tmetrics.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/tmetrics.m b/test/tmetrics.m index 5e75d2b..d40c54d 100644 --- a/test/tmetrics.m +++ b/test/tmetrics.m @@ -192,7 +192,11 @@ function testCounterInvalidAdd(testCase) % fetch results clear p; results = readJsonResults(testCase); - verifyEmpty(testCase, results); % results should be empty since all adds were invalid + results = results{end}; + + % verify that the counter value is still 0 + verifyEqual(testCase, ... + results.resourceMetrics.scopeMetrics.metrics.sum.dataPoints.asDouble, 0); end From 0c04eea914983779ed99bce73eaf32cb1bf9916e Mon Sep 17 00:00:00 2001 From: duncanpo Date: Thu, 25 Jan 2024 10:06:39 -0500 Subject: [PATCH 09/11] fix a problem in tmetrics_sdk --- test/tmetrics_sdk.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/tmetrics_sdk.m b/test/tmetrics_sdk.m index 29af901..bc06864 100644 --- a/test/tmetrics_sdk.m +++ b/test/tmetrics_sdk.m @@ -169,7 +169,7 @@ function testViewBasic(testCase) pause(testCase.WaitTime); - clear m; + clear mp; results = readJsonResults(testCase); results = results{end}; @@ -214,7 +214,7 @@ function testViewHistogram(testCase) % wait for collector response pause(testCase.WaitTime); - clear m; + clear mp; results = readJsonResults(testCase); results = results{end}; @@ -271,7 +271,7 @@ function testMultipleViews(testCase) pause(testCase.WaitTime); - clear mxyz mabc; + clear mp; results = readJsonResults(testCase); results = vertcat(results{end}.resourceMetrics.scopeMetrics.metrics); From b6f19b71ba70e8e20a2558e199f388c8862363d5 Mon Sep 17 00:00:00 2001 From: Duncan Po Date: Fri, 26 Jan 2024 15:16:31 -0500 Subject: [PATCH 10/11] move mex engine pointer from static variable to nonstatic --- .../+metrics/AsynchronousInstrument.m | 2 +- .../metrics/AsynchronousCallbackInput.h | 20 ++++++++++++++++++ .../metrics/AsynchronousInstrumentProxy.h | 8 +++++-- .../AsynchronousInstrumentProxyFactory.h | 5 ++++- .../metrics/MeasurementFetcher.h | 3 +-- .../metrics/MeterProviderProxy.h | 5 +++-- .../opentelemetry-matlab/metrics/MeterProxy.h | 7 ++++-- .../metrics/ObservableCounterProxy.h | 6 ++++-- .../metrics/ObservableGaugeProxy.h | 6 ++++-- .../metrics/ObservableUpDownCounterProxy.h | 6 ++++-- .../src/AsynchronousInstrumentProxy.cpp | 12 +++++------ .../AsynchronousInstrumentProxyFactory.cpp | 6 +++--- api/metrics/src/MeasurementFetcher.cpp | 12 +++++------ api/metrics/src/MeterProviderProxy.cpp | 8 +++++-- api/metrics/src/MeterProxy.cpp | 7 +----- test/._commonSetupOnce.m | Bin 0 -> 4096 bytes test/._terminateCollector.m | Bin 0 -> 4096 bytes test/tmetrics.m | 12 +++++++++++ 18 files changed, 86 insertions(+), 39 deletions(-) create mode 100644 api/metrics/include/opentelemetry-matlab/metrics/AsynchronousCallbackInput.h create mode 100644 test/._commonSetupOnce.m create mode 100644 test/._terminateCollector.m diff --git a/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m b/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m index f88c526..8c0479e 100644 --- a/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m +++ b/api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m @@ -64,7 +64,7 @@ function removeCallback(obj, callback) end if sum(found) > 0 idx = find(found,1); % remove only the first match - obj.Proxy.removeCallback(callback, idx); + obj.Proxy.removeCallback(idx); % update Callback property if isa(obj.Callbacks, "function_handle") obj.Callbacks = []; diff --git a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousCallbackInput.h b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousCallbackInput.h new file mode 100644 index 0000000..cee5716 --- /dev/null +++ b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousCallbackInput.h @@ -0,0 +1,20 @@ +// Copyright 2024 The MathWorks, Inc. + +#pragma once + +#include "MatlabDataArray.hpp" +#include "mex.hpp" + +namespace libmexclass::opentelemetry { +struct AsynchronousCallbackInput +{ + AsynchronousCallbackInput(const matlab::data::Array& fh, + const std::shared_ptr eng) + : FunctionHandle(fh), MexEngine(eng) {} + + matlab::data::Array FunctionHandle; + const std::shared_ptr MexEngine; +}; +} // namespace libmexclass::opentelemetry + + diff --git a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h index fc86c53..968199c 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxy.h @@ -4,6 +4,8 @@ #include +#include "opentelemetry-matlab/metrics/AsynchronousCallbackInput.h" + #include "libmexclass/proxy/Proxy.h" #include "libmexclass/proxy/method/Context.h" @@ -15,7 +17,8 @@ namespace nostd = opentelemetry::nostd; namespace libmexclass::opentelemetry { class AsynchronousInstrumentProxy : public libmexclass::proxy::Proxy { protected: - AsynchronousInstrumentProxy(nostd::shared_ptr inst) : CppInstrument(inst) {} + AsynchronousInstrumentProxy(nostd::shared_ptr inst, + const std::shared_ptr eng) : CppInstrument(inst), MexEngine(eng) {} public: void addCallback(libmexclass::proxy::method::Context& context); @@ -29,8 +32,9 @@ class AsynchronousInstrumentProxy : public libmexclass::proxy::Proxy { private: nostd::shared_ptr CppInstrument; - std::list CallbackFunctions; + std::list CallbackInputs; + const std::shared_ptr MexEngine; // used for feval on callbacks }; } // namespace libmexclass::opentelemetry diff --git a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h index a2a559a..68d8c6f 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/AsynchronousInstrumentProxyFactory.h @@ -15,7 +15,9 @@ enum class AsynchronousInstrumentType {ObservableCounter, ObservableUpDownCounte class AsynchronousInstrumentProxyFactory { public: - AsynchronousInstrumentProxyFactory(nostd::shared_ptr mt) : CppMeter(mt) {} + AsynchronousInstrumentProxyFactory(nostd::shared_ptr mt, + const std::shared_ptr eng) + : CppMeter(mt), MexEngine(eng) {} std::shared_ptr create(AsynchronousInstrumentType type, const matlab::data::Array& callback, const std::string& name, const std::string& description, @@ -24,5 +26,6 @@ class AsynchronousInstrumentProxyFactory { private: nostd::shared_ptr CppMeter; + const std::shared_ptr MexEngine; // used for feval on callbacks }; } // namespace libmexclass::opentelemetry diff --git a/api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h b/api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h index ea1d7de..94aabf0 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/MeasurementFetcher.h @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #pragma once @@ -9,7 +9,6 @@ class MeasurementFetcher { public: static void Fetcher(metrics_api::ObserverResult observer_result, void * /* state */); - static std::shared_ptr mlptr; }; } // namespace libmexclass::opentelemetry diff --git a/api/metrics/include/opentelemetry-matlab/metrics/MeterProviderProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/MeterProviderProxy.h index 0e00b1e..f24224a 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/MeterProviderProxy.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/MeterProviderProxy.h @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #pragma once @@ -18,7 +18,7 @@ namespace nostd = opentelemetry::nostd; namespace libmexclass::opentelemetry { class MeterProviderProxy : public libmexclass::proxy::Proxy { public: - MeterProviderProxy(nostd::shared_ptr mp) : CppMeterProvider(mp) { + MeterProviderProxy(nostd::shared_ptr mp) : CppMeterProvider(mp), MexEngine(nullptr) { REGISTER_METHOD(MeterProviderProxy, getMeter); REGISTER_METHOD(MeterProviderProxy, setMeterProvider); REGISTER_METHOD(MeterProviderProxy, postShutdown); @@ -46,5 +46,6 @@ class MeterProviderProxy : public libmexclass::proxy::Proxy { protected: nostd::shared_ptr CppMeterProvider; + std::shared_ptr MexEngine; // mex engine pointer used by asynchronous instruments for feval }; } // namespace libmexclass::opentelemetry diff --git a/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h index d96951e..204337c 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/MeterProxy.h @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #pragma once @@ -22,7 +22,8 @@ namespace nostd = opentelemetry::nostd; namespace libmexclass::opentelemetry { class MeterProxy : public libmexclass::proxy::Proxy { public: - MeterProxy(nostd::shared_ptr mt) : CppMeter(mt) { + MeterProxy(nostd::shared_ptr mt, const std::shared_ptr eng) + : CppMeter(mt), MexEngine(eng) { REGISTER_METHOD(MeterProxy, createCounter); REGISTER_METHOD(MeterProxy, createUpDownCounter); REGISTER_METHOD(MeterProxy, createHistogram); @@ -49,5 +50,7 @@ class MeterProxy : public libmexclass::proxy::Proxy { void createSynchronous(libmexclass::proxy::method::Context& context, SynchronousInstrumentType type); nostd::shared_ptr CppMeter; + + const std::shared_ptr MexEngine; // mex engine pointer used by asynchronous instruments for feval }; } // namespace libmexclass::opentelemetry diff --git a/api/metrics/include/opentelemetry-matlab/metrics/ObservableCounterProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/ObservableCounterProxy.h index 21681c4..75549bd 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/ObservableCounterProxy.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/ObservableCounterProxy.h @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #pragma once @@ -10,7 +10,9 @@ namespace nostd = opentelemetry::nostd; namespace libmexclass::opentelemetry { class ObservableCounterProxy : public AsynchronousInstrumentProxy { public: - ObservableCounterProxy(nostd::shared_ptr ct) : AsynchronousInstrumentProxy(ct) { + ObservableCounterProxy(nostd::shared_ptr ct, + const std::shared_ptr eng) + : AsynchronousInstrumentProxy(ct, eng) { REGISTER_METHOD(ObservableCounterProxy, addCallback); REGISTER_METHOD(ObservableCounterProxy, removeCallback); } diff --git a/api/metrics/include/opentelemetry-matlab/metrics/ObservableGaugeProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/ObservableGaugeProxy.h index 314b22b..7a2bf3e 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/ObservableGaugeProxy.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/ObservableGaugeProxy.h @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #pragma once @@ -10,7 +10,9 @@ namespace nostd = opentelemetry::nostd; namespace libmexclass::opentelemetry { class ObservableGaugeProxy : public AsynchronousInstrumentProxy { public: - ObservableGaugeProxy(nostd::shared_ptr g) : AsynchronousInstrumentProxy(g) { + ObservableGaugeProxy(nostd::shared_ptr g, + const std::shared_ptr eng) + : AsynchronousInstrumentProxy(g, eng) { REGISTER_METHOD(ObservableGaugeProxy, addCallback); REGISTER_METHOD(ObservableGaugeProxy, removeCallback); } diff --git a/api/metrics/include/opentelemetry-matlab/metrics/ObservableUpDownCounterProxy.h b/api/metrics/include/opentelemetry-matlab/metrics/ObservableUpDownCounterProxy.h index 9f7c20c..6016b91 100644 --- a/api/metrics/include/opentelemetry-matlab/metrics/ObservableUpDownCounterProxy.h +++ b/api/metrics/include/opentelemetry-matlab/metrics/ObservableUpDownCounterProxy.h @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #pragma once @@ -10,7 +10,9 @@ namespace nostd = opentelemetry::nostd; namespace libmexclass::opentelemetry { class ObservableUpDownCounterProxy : public AsynchronousInstrumentProxy { public: - ObservableUpDownCounterProxy(nostd::shared_ptr ct) : AsynchronousInstrumentProxy(ct) { + ObservableUpDownCounterProxy(nostd::shared_ptr ct, + const std::shared_ptr eng) + : AsynchronousInstrumentProxy(ct, eng) { REGISTER_METHOD(ObservableUpDownCounterProxy, addCallback); REGISTER_METHOD(ObservableUpDownCounterProxy, removeCallback); } diff --git a/api/metrics/src/AsynchronousInstrumentProxy.cpp b/api/metrics/src/AsynchronousInstrumentProxy.cpp index 8613339..be6f4df 100644 --- a/api/metrics/src/AsynchronousInstrumentProxy.cpp +++ b/api/metrics/src/AsynchronousInstrumentProxy.cpp @@ -14,17 +14,17 @@ void AsynchronousInstrumentProxy::addCallback(libmexclass::proxy::method::Contex } void AsynchronousInstrumentProxy::addCallback_helper(const matlab::data::Array& callback){ - CallbackFunctions.push_back(callback); - CppInstrument->AddCallback(MeasurementFetcher::Fetcher, static_cast(&CallbackFunctions.back())); + AsynchronousCallbackInput arg(callback, MexEngine); + CallbackInputs.push_back(arg); + CppInstrument->AddCallback(MeasurementFetcher::Fetcher, static_cast(&CallbackInputs.back())); } void AsynchronousInstrumentProxy::removeCallback(libmexclass::proxy::method::Context& context){ - matlab::data::Array callback_mda = context.inputs[0]; - matlab::data::TypedArray idx_mda = context.inputs[1]; + matlab::data::TypedArray idx_mda = context.inputs[0]; double idx = idx_mda[0] - 1; // adjust index from 1-based in MATLAB to 0-based in C++ - auto iter = CallbackFunctions.begin(); + auto iter = CallbackInputs.begin(); std::advance(iter, idx); - CallbackFunctions.erase(iter); + CallbackInputs.erase(iter); CppInstrument->RemoveCallback(MeasurementFetcher::Fetcher, static_cast(&(*iter))); } diff --git a/api/metrics/src/AsynchronousInstrumentProxyFactory.cpp b/api/metrics/src/AsynchronousInstrumentProxyFactory.cpp index 7ad353b..b760646 100644 --- a/api/metrics/src/AsynchronousInstrumentProxyFactory.cpp +++ b/api/metrics/src/AsynchronousInstrumentProxyFactory.cpp @@ -13,19 +13,19 @@ std::shared_ptr AsynchronousInstrumentProxyFactory::c case AsynchronousInstrumentType::ObservableCounter: { nostd::shared_ptr ct = std::move(CppMeter->CreateDoubleObservableCounter(name, description, unit)); - proxy = std::shared_ptr(new ObservableCounterProxy(ct)); + proxy = std::shared_ptr(new ObservableCounterProxy(ct, MexEngine)); } break; case AsynchronousInstrumentType::ObservableUpDownCounter: { nostd::shared_ptr udct = std::move(CppMeter->CreateDoubleObservableUpDownCounter(name, description, unit)); - proxy = std::shared_ptr(new ObservableUpDownCounterProxy(udct)); + proxy = std::shared_ptr(new ObservableUpDownCounterProxy(udct, MexEngine)); } break; case AsynchronousInstrumentType::ObservableGauge: { nostd::shared_ptr g = std::move(CppMeter->CreateDoubleObservableGauge(name, description, unit)); - proxy = std::shared_ptr(new ObservableGaugeProxy(g)); + proxy = std::shared_ptr(new ObservableGaugeProxy(g, MexEngine)); } break; } diff --git a/api/metrics/src/MeasurementFetcher.cpp b/api/metrics/src/MeasurementFetcher.cpp index eb4cb22..a368d0c 100644 --- a/api/metrics/src/MeasurementFetcher.cpp +++ b/api/metrics/src/MeasurementFetcher.cpp @@ -18,21 +18,23 @@ #include "opentelemetry-matlab/metrics/MeasurementFetcher.h" #include "opentelemetry-matlab/common/attribute.h" +#include "opentelemetry-matlab/metrics/AsynchronousCallbackInput.h" namespace metrics_api = opentelemetry::metrics; namespace nostd = opentelemetry::nostd; namespace libmexclass::opentelemetry { -void MeasurementFetcher::Fetcher(metrics_api::ObserverResult observer_result, void * fh) +void MeasurementFetcher::Fetcher(metrics_api::ObserverResult observer_result, void * in) { if (nostd::holds_alternative< nostd::shared_ptr>>(observer_result)) { - auto future = mlptr->fevalAsync(u"opentelemetry.metrics.collectObservableMetrics", - *(static_cast(fh))); + auto arg = static_cast(in); + auto future = arg->MexEngine->fevalAsync(u"opentelemetry.metrics.collectObservableMetrics", + arg->FunctionHandle); try { matlab::data::ObjectArray resultobj = future.get(); - auto futureresult = mlptr->getPropertyAsync(resultobj, 0, u"Results"); + auto futureresult = arg->MexEngine->getPropertyAsync(resultobj, 0, u"Results"); matlab::data::CellArray resultdata = futureresult.get(); size_t n = resultdata.getNumberOfElements(); size_t i = 0; @@ -60,6 +62,4 @@ void MeasurementFetcher::Fetcher(metrics_api::ObserverResult observer_result, vo } } } - -std::shared_ptr MeasurementFetcher::mlptr = nullptr; } // namespace diff --git a/api/metrics/src/MeterProviderProxy.cpp b/api/metrics/src/MeterProviderProxy.cpp index 4263170..188286a 100644 --- a/api/metrics/src/MeterProviderProxy.cpp +++ b/api/metrics/src/MeterProviderProxy.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #include "opentelemetry-matlab/metrics/MeterProviderProxy.h" #include "opentelemetry-matlab/metrics/MeterProxy.h" @@ -20,7 +20,11 @@ void MeterProviderProxy::getMeter(libmexclass::proxy::method::Context& context) auto mt = CppMeterProvider->GetMeter(name, version, schema); // instantiate a MeterProxy instance - MeterProxy* newproxy = new MeterProxy(mt); + // initialize MATLAB mex engine the first time + if (MexEngine == nullptr) { + MexEngine = context.matlab; + } + MeterProxy* newproxy = new MeterProxy(mt, MexEngine); auto mtproxy = std::shared_ptr(newproxy); // obtain a proxy ID diff --git a/api/metrics/src/MeterProxy.cpp b/api/metrics/src/MeterProxy.cpp index 6492b80..7cd3ea3 100644 --- a/api/metrics/src/MeterProxy.cpp +++ b/api/metrics/src/MeterProxy.cpp @@ -56,12 +56,7 @@ void MeterProxy::createAsynchronous(libmexclass::proxy::method::Context& context std::string unit = static_cast(unit_mda[0]); matlab::data::Array callback_mda = context.inputs[3]; - // initialize MATLAB mex engine the first time an asynchronous instrument is created - if (MeasurementFetcher::mlptr == nullptr) { - MeasurementFetcher::mlptr = static_cast >(context.matlab); - } - - AsynchronousInstrumentProxyFactory proxyfactory(CppMeter); + AsynchronousInstrumentProxyFactory proxyfactory(CppMeter, MexEngine); auto proxy = proxyfactory.create(type, callback_mda, name, description, unit); // obtain a proxy ID diff --git a/test/._commonSetupOnce.m b/test/._commonSetupOnce.m new file mode 100644 index 0000000000000000000000000000000000000000..5d9f1ac26d194f699174a0e34864202b761a4bae GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIbX*RHz4)_is1l` zAt6BwtPy)5Iv6T&C>s?X4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu;ss)hg~ z&@>PR1G$il%wmP2)Z+ZoqU2PCwEUuMh0MH?)Vz|+{Jg}RoJxh9)U*$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIbX*RHz4)_is1l` zAt6BwtdZOh9SjvXl#L3HhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kgRRYQOg zXc`EEfm}#NX0bw1YH@yPQF5w6T7FTsLS|k`YF Date: Fri, 26 Jan 2024 15:17:47 -0500 Subject: [PATCH 11/11] remove unnecessary files --- test/._commonSetupOnce.m | Bin 4096 -> 0 bytes test/._terminateCollector.m | Bin 4096 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/._commonSetupOnce.m delete mode 100644 test/._terminateCollector.m diff --git a/test/._commonSetupOnce.m b/test/._commonSetupOnce.m deleted file mode 100644 index 5d9f1ac26d194f699174a0e34864202b761a4bae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIbX*RHz4)_is1l` zAt6BwtPy)5Iv6T&C>s?X4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu;ss)hg~ z&@>PR1G$il%wmP2)Z+ZoqU2PCwEUuMh0MH?)Vz|+{Jg}RoJxh9)U*$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIbX*RHz4)_is1l` zAt6BwtdZOh9SjvXl#L3HhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kgRRYQOg zXc`EEfm}#NX0bw1YH@yPQF5w6T7FTsLS|k`YF