Skip to content
8 changes: 6 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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})
Expand All @@ -222,6 +223,9 @@ 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
${METRICS_API_SOURCE_DIR}/AsynchronousInstrumentProxyFactory.cpp
${CONTEXT_API_SOURCE_DIR}/TextMapPropagatorProxy.cpp
${CONTEXT_API_SOURCE_DIR}/CompositePropagatorProxy.cpp
${CONTEXT_API_SOURCE_DIR}/TextMapCarrierProxy.cpp
Expand Down Expand Up @@ -295,7 +299,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}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
81 changes: 81 additions & 0 deletions api/metrics/+opentelemetry/+metrics/AsynchronousInstrument.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
classdef AsynchronousInstrument < handle
% Base class inherited by all asynchronous instruments

% Copyright 2023-2024 The MathWorks, Inc.

properties (SetAccess=immutable)
Name (1,1) string % Instrument name
Description (1,1) string % Description of instrument
Unit (1,1) string % Measurement unit
end

properties (SetAccess=private)
Callbacks % Callback function, called at each data export
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)
% 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")
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

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)
if iscell(obj.Callbacks)
found = cellfun(@(x)isequal(x,callback), obj.Callbacks);
else % scalar function handle
found = isequal(obj.Callbacks, callback);
end
if sum(found) > 0
idx = find(found,1); % remove only the first match
obj.Proxy.removeCallback(idx);
% update Callback property
if isa(obj.Callbacks, "function_handle")
obj.Callbacks = [];
else
obj.Callbacks(idx) = [];
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
173 changes: 138 additions & 35 deletions api/metrics/+opentelemetry/+metrics/Meter.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -38,25 +38,24 @@
% C = CREATECOUNTER(M, NAME, DESCRIPTION, UNIT) also
% specifies a description and a unit.
%
% See also CREATEUPDOWNCOUNTER, CREATEHISTOGRAM
% See also CREATEUPDOWNCOUNTER, CREATEHISTOGRAM,
% CREATEOBSERVABLECOUNTER
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
Expand All @@ -65,26 +64,25 @@
% C = CREATEUPDOWNCOUNTER(M, NAME, DESCRIPTION, UNIT) also
% specifies a description and a unit.
%
% See also CREATECOUNTER, CREATEHISTOGRAM
% See also CREATECOUNTER, CREATEHISTOGRAM,
% CREATEOBSERVABLEUPDOWNCOUNTER
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
Expand All @@ -97,21 +95,126 @@
% 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

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
name
description = ""
unit = ""
end

[callback, name, description, unit] = processAsynchronousInputs(...
callback, name, description, unit);
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);
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
name
description = ""
unit = ""
end

[callback, name, description, unit] = processAsynchronousInputs(...
callback, name, description, unit);
id = obj.Proxy.createObservableUpDownCounter(name, description, unit, callback);
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)
% 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
name
description = ""
unit = ""
end

[callback, name, description, unit] = processAsynchronousInputs(...
callback, name, description, unit);
id = obj.Proxy.createObservableGauge(name, description, unit, callback);
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, name, description, unit] = processAsynchronousInputs(...
callback, name, description, unit)
[name, description, unit] = processSynchronousInputs(name, description, unit);
if ~isa(callback, "function_handle")
callback = []; % callback is invalid, set to empty double
end
end
17 changes: 17 additions & 0 deletions api/metrics/+opentelemetry/+metrics/ObservableCounter.m
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions api/metrics/+opentelemetry/+metrics/ObservableGauge.m
Original file line number Diff line number Diff line change
@@ -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
Loading