Skip to content

Commit 4a483da

Browse files
authored
Merge pull request #80 from mathworks/metrics
Asynchronous metric instruments
2 parents 9a0eb89 + 9e080d5 commit 4a483da

31 files changed

+987
-113
lines changed

CMakeLists.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ endif()
165165

166166
set(OPENTELEMETRY_PROXY_LIBRARY_NAME "OtelMatlabProxy")
167167

168+
find_package(Matlab REQUIRED)
168169
find_package(Protobuf REQUIRED)
169170
find_package(nlohmann_json REQUIRED)
170171
if(WIN32)
@@ -196,7 +197,7 @@ set(TRACE_SDK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/trace/include)
196197
set(METRICS_SDK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/metrics/include)
197198
set(COMMON_SDK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/common/include)
198199
set(OTLP_EXPORTER_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/include)
199-
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)
200+
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})
200201

201202
set(OPENTELEMETRY_PROXY_FACTORY_CLASS_NAME OtelMatlabProxyFactory)
202203
set(OPENTELEMETRY_PROXY_FACTORY_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR})
@@ -222,6 +223,9 @@ set(OPENTELEMETRY_PROXY_SOURCES
222223
${METRICS_API_SOURCE_DIR}/UpDownCounterProxy.cpp
223224
${METRICS_API_SOURCE_DIR}/HistogramProxy.cpp
224225
${METRICS_API_SOURCE_DIR}/SynchronousInstrumentProxyFactory.cpp
226+
${METRICS_API_SOURCE_DIR}/MeasurementFetcher.cpp
227+
${METRICS_API_SOURCE_DIR}/AsynchronousInstrumentProxy.cpp
228+
${METRICS_API_SOURCE_DIR}/AsynchronousInstrumentProxyFactory.cpp
225229
${CONTEXT_API_SOURCE_DIR}/TextMapPropagatorProxy.cpp
226230
${CONTEXT_API_SOURCE_DIR}/CompositePropagatorProxy.cpp
227231
${CONTEXT_API_SOURCE_DIR}/TextMapCarrierProxy.cpp
@@ -295,7 +299,7 @@ set(OTEL_CPP_LINK_LIBRARIES ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX
295299
${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_version${CMAKE_STATIC_LIBRARY_SUFFIX}
296300
${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_logs${CMAKE_STATIC_LIBRARY_SUFFIX}
297301
${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_metrics${CMAKE_STATIC_LIBRARY_SUFFIX}
298-
${Protobuf_LIBRARIES})
302+
${Protobuf_LIBRARIES} ${Matlab_MEX_LIBRARY})
299303
if(WITH_OTLP_HTTP)
300304
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}
301305
${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_exporter_otlp_http_client${CMAKE_STATIC_LIBRARY_SUFFIX}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
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.
55

66
### Status
7-
- Currently only tracing and metrics (synchronous instruments) are supported. Asynchronous metrics instruments and logs will be in the future.
7+
- Currently only tracing and metrics are supported. Logs will be in the future.
88
- View class in metrics is only partially supported. The properties **Aggregation** and **AllowedAttributes** are not yet supported.
99
- This package is supported and has been tested on Windows®, Linux®, and macOS.
1010

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
classdef AsynchronousInstrument < handle
2+
% Base class inherited by all asynchronous instruments
3+
4+
% Copyright 2023-2024 The MathWorks, Inc.
5+
6+
properties (SetAccess=immutable)
7+
Name (1,1) string % Instrument name
8+
Description (1,1) string % Description of instrument
9+
Unit (1,1) string % Measurement unit
10+
end
11+
12+
properties (SetAccess=private)
13+
Callbacks % Callback function, called at each data export
14+
end
15+
16+
properties (Access=private)
17+
Proxy % Proxy object to interface C++ code
18+
end
19+
20+
methods (Access=protected)
21+
function obj = AsynchronousInstrument(proxy, name, description, unit, callback)
22+
obj.Proxy = proxy;
23+
obj.Name = name;
24+
obj.Description = description;
25+
obj.Unit = unit;
26+
obj.Callbacks = callback;
27+
end
28+
29+
end
30+
31+
methods
32+
function addCallback(obj, callback)
33+
% ADDCALLBACK Add a callback function
34+
% ADDCALLBACK(INST, CALLBACK) adds a callback function to
35+
% collect metrics at every export. CALLBACK is specified as a
36+
% function handle, and must accept no input and return one
37+
% output of type opentelemetry.metrics.ObservableResult.
38+
%
39+
% See also REMOVECALLBACK, OPENTELEMETRY.METRICS.OBSERVABLERESULT
40+
if isa(callback, "function_handle")
41+
obj.Proxy.addCallback(callback);
42+
% append to Callbacks property
43+
if isempty(obj.Callbacks)
44+
obj.Callbacks = callback;
45+
elseif isa(obj.Callbacks, "function_handle")
46+
obj.Callbacks = {obj.Callbacks, callback};
47+
else
48+
obj.Callbacks = [obj.Callbacks, {callback}];
49+
end
50+
end
51+
end
52+
53+
function removeCallback(obj, callback)
54+
% REMOVECALLBACK Remove a callback function
55+
% REMOVECALLBACK(INST, CALLBACK) removes a callback function
56+
% CALLBACK specified as a function handle.
57+
%
58+
% See also ADDCALLBACK
59+
if isa(callback, "function_handle") && ~isempty(obj.Callbacks)
60+
if iscell(obj.Callbacks)
61+
found = cellfun(@(x)isequal(x,callback), obj.Callbacks);
62+
else % scalar function handle
63+
found = isequal(obj.Callbacks, callback);
64+
end
65+
if sum(found) > 0
66+
idx = find(found,1); % remove only the first match
67+
obj.Proxy.removeCallback(idx);
68+
% update Callback property
69+
if isa(obj.Callbacks, "function_handle")
70+
obj.Callbacks = [];
71+
else
72+
obj.Callbacks(idx) = [];
73+
if isscalar(obj.Callbacks) % if there is only one left, remove the cell
74+
obj.Callbacks = obj.Callbacks{1};
75+
end
76+
end
77+
end
78+
end
79+
end
80+
end
81+
end

api/metrics/+opentelemetry/+metrics/Meter.m

Lines changed: 138 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
% A Meter creates metric instruments, capturing measurements about a service at runtime.
33
% Meters are created from Meter Providers.
44

5-
% Copyright 2023 The MathWorks, Inc.
5+
% Copyright 2023-2024 The MathWorks, Inc.
66

77
properties (SetAccess=immutable)
88
Name (1,1) string % Meter name
@@ -29,7 +29,7 @@
2929

3030
methods
3131

32-
function counter = createCounter(obj, ctname, ctdescription, ctunit)
32+
function counter = createCounter(obj, name, description, unit)
3333
% CREATECOUNTER Create a counter
3434
% C = CREATECOUNTER(M, NAME) creates a counter with the specified
3535
% name. A counter's value can only increase but not
@@ -38,25 +38,24 @@
3838
% C = CREATECOUNTER(M, NAME, DESCRIPTION, UNIT) also
3939
% specifies a description and a unit.
4040
%
41-
% See also CREATEUPDOWNCOUNTER, CREATEHISTOGRAM
41+
% See also CREATEUPDOWNCOUNTER, CREATEHISTOGRAM,
42+
% CREATEOBSERVABLECOUNTER
4243
arguments
4344
obj
44-
ctname
45-
ctdescription = ""
46-
ctunit = ""
45+
name
46+
description = ""
47+
unit = ""
4748
end
48-
import opentelemetry.common.mustBeScalarString
49-
ctname = mustBeScalarString(ctname);
50-
ctdescription = mustBeScalarString(ctdescription);
51-
ctunit = mustBeScalarString(ctunit);
52-
id = obj.Proxy.createCounter(ctname, ctdescription, ctunit);
49+
[name, description, unit] = processSynchronousInputs(name, ...
50+
description, unit);
51+
id = obj.Proxy.createCounter(name, description, unit);
5352
CounterProxy = libmexclass.proxy.Proxy("Name", ...
5453
"libmexclass.opentelemetry.CounterProxy", "ID", id);
55-
counter = opentelemetry.metrics.Counter(CounterProxy, ctname, ctdescription, ctunit);
54+
counter = opentelemetry.metrics.Counter(CounterProxy, name, description, unit);
5655
end
5756

5857

59-
function updowncounter = createUpDownCounter(obj, ctname, ctdescription, ctunit)
58+
function updowncounter = createUpDownCounter(obj, name, description, unit)
6059
% CREATEUPDOWNCOUNTER Create an UpDownCounter
6160
% C = CREATEUPDOWNCOUNTER(M, NAME) creates an UpDownCounter
6261
% with the specified name. An UpDownCounter's value can
@@ -65,26 +64,25 @@
6564
% C = CREATEUPDOWNCOUNTER(M, NAME, DESCRIPTION, UNIT) also
6665
% specifies a description and a unit.
6766
%
68-
% See also CREATECOUNTER, CREATEHISTOGRAM
67+
% See also CREATECOUNTER, CREATEHISTOGRAM,
68+
% CREATEOBSERVABLEUPDOWNCOUNTER
6969
arguments
7070
obj
71-
ctname
72-
ctdescription = ""
73-
ctunit = ""
71+
name
72+
description = ""
73+
unit = ""
7474
end
7575

76-
import opentelemetry.common.mustBeScalarString
77-
ctname = mustBeScalarString(ctname);
78-
ctdescription = mustBeScalarString(ctdescription);
79-
ctunit = mustBeScalarString(ctunit);
80-
id = obj.Proxy.createUpDownCounter(ctname, ctdescription, ctunit);
76+
[name, description, unit] = processSynchronousInputs(name, ...
77+
description, unit);
78+
id = obj.Proxy.createUpDownCounter(name, description, unit);
8179
UpDownCounterProxy = libmexclass.proxy.Proxy("Name", ...
8280
"libmexclass.opentelemetry.UpDownCounterProxy", "ID", id);
83-
updowncounter = opentelemetry.metrics.UpDownCounter(UpDownCounterProxy, ctname, ctdescription, ctunit);
81+
updowncounter = opentelemetry.metrics.UpDownCounter(UpDownCounterProxy, name, description, unit);
8482
end
8583

8684

87-
function histogram = createHistogram(obj, hiname, hidescription, hiunit)
85+
function histogram = createHistogram(obj, name, description, unit)
8886
% CREATEHISTOGRAM Create a histogram
8987
% H = CREATEHISTOGRAM(M, NAME) creates a histogram with the specified
9088
% name. A histogram aggregates values into bins. Bins can be
@@ -97,21 +95,126 @@
9795
% OPENTELEMETRY.SDK.METRICS.VIEW
9896
arguments
9997
obj
100-
hiname
101-
hidescription = ""
102-
hiunit = ""
98+
name
99+
description = ""
100+
unit = ""
103101
end
104102

105-
import opentelemetry.common.mustBeScalarString
106-
hiname = mustBeScalarString(hiname);
107-
hidescription = mustBeScalarString(hidescription);
108-
hiunit = mustBeScalarString(hiunit);
109-
id = obj.Proxy.createHistogram(hiname, hidescription, hiunit);
103+
[name, description, unit] = processSynchronousInputs(name, ...
104+
description, unit);
105+
id = obj.Proxy.createHistogram(name, description, unit);
110106
HistogramProxy = libmexclass.proxy.Proxy("Name", ...
111107
"libmexclass.opentelemetry.HistogramProxy", "ID", id);
112-
histogram = opentelemetry.metrics.Histogram(HistogramProxy, hiname, hidescription, hiunit);
108+
histogram = opentelemetry.metrics.Histogram(HistogramProxy, name, description, unit);
113109
end
114110

115-
end
116-
111+
function obscounter = createObservableCounter(obj, callback, name, description, unit)
112+
% CREATEOBSERVABLECOUNTER Create an observable counter
113+
% C = CREATEOBSERVABLECOUNTER(M, CALLBACK, NAME) creates an
114+
% observable counter with the specified callback function
115+
% and name. The callback function, specified as a
116+
% function handle, must accept no input and return one
117+
% output of type opentelemetry.metrics.ObservableResult.
118+
% The counter's value can only increase but not decrease.
119+
%
120+
% C = CREATEOBSERVABLECOUNTER(M, CALLBACK NAME, DESCRIPTION, UNIT)
121+
% also specifies a description and a unit.
122+
%
123+
% See also OPENTELEMETRY.METRICS.OBSERVABLERESULT,
124+
% CREATEOBSERVABLEUPDOWNCOUNTER, CREATEOBSERVABLEGAUGE, CREATECOUNTER
125+
arguments
126+
obj
127+
callback
128+
name
129+
description = ""
130+
unit = ""
131+
end
132+
133+
[callback, name, description, unit] = processAsynchronousInputs(...
134+
callback, name, description, unit);
135+
id = obj.Proxy.createObservableCounter(name, description, unit, callback);
136+
ObservableCounterproxy = libmexclass.proxy.Proxy("Name", ...
137+
"libmexclass.opentelemetry.ObservableCounterProxy", "ID", id);
138+
obscounter = opentelemetry.metrics.ObservableCounter(ObservableCounterproxy, name, description, unit, callback);
139+
end
140+
141+
function obsudcounter = createObservableUpDownCounter(obj, callback, name, description, unit)
142+
% CREATEOBSERVABLEUPDOWNCOUNTER Create an observable UpDownCounter
143+
% C = CREATEOBSERVABLEUPDOWNCOUNTER(M, CALLBACK, NAME)
144+
% creates an observable UpDownCounter with the specified
145+
% callback function and name. The callback function,
146+
% specified as a function handle, must accept no input and
147+
% return one output of type opentelemetry.metrics.ObservableResult.
148+
% The UpDownCounter's value can increase or decrease.
149+
%
150+
% C = CREATEOBSERVABLEUPDOWNCOUNTER(M, CALLBACK, NAME, DESCRIPTION, UNIT)
151+
% also specifies a description and a unit.
152+
%
153+
% See also OPENTELEMETRY.METRICS.OBSERVABLERESULT,
154+
% CREATEOBSERVABLECOUNTER, CREATEOBSERVABLEGAUGE, CREATEUPDOWNCOUNTER
155+
arguments
156+
obj
157+
callback
158+
name
159+
description = ""
160+
unit = ""
161+
end
162+
163+
[callback, name, description, unit] = processAsynchronousInputs(...
164+
callback, name, description, unit);
165+
id = obj.Proxy.createObservableUpDownCounter(name, description, unit, callback);
166+
ObservableUpDownCounterproxy = libmexclass.proxy.Proxy("Name", ...
167+
"libmexclass.opentelemetry.ObservableUpDownCounterProxy", "ID", id);
168+
obsudcounter = opentelemetry.metrics.ObservableUpDownCounter(...
169+
ObservableUpDownCounterproxy, name, description, unit, callback);
170+
end
171+
172+
function obsgauge = createObservableGauge(obj, callback, name, description, unit)
173+
% CREATEOBSERVABLEGAUGE Create an observable gauge
174+
% C = CREATEOBSERVABLEGAUGE(M, CALLBACK, NAME) creates an
175+
% observable gauge with the specified callback function
176+
% and name. The callback function, specified as a
177+
% function handle, must accept no input and return one
178+
% output of type opentelemetry.metrics.ObservableResult.
179+
% A gauge's value can increase or decrease but it should
180+
% never be summed in aggregation.
181+
%
182+
% C = CREATEOBSERVABLEGAUGE(M, CALLBACK NAME, DESCRIPTION, UNIT)
183+
% also specifies a description and a unit.
184+
%
185+
% See also OPENTELEMETRY.METRICS.OBSERVABLERESULT,
186+
% CREATEOBSERVABLECOUNTER, CREATEOBSERVABLEUPDOWNCOUNTER
187+
arguments
188+
obj
189+
callback
190+
name
191+
description = ""
192+
unit = ""
193+
end
194+
195+
[callback, name, description, unit] = processAsynchronousInputs(...
196+
callback, name, description, unit);
197+
id = obj.Proxy.createObservableGauge(name, description, unit, callback);
198+
ObservableGaugeproxy = libmexclass.proxy.Proxy("Name", ...
199+
"libmexclass.opentelemetry.ObservableGaugeProxy", "ID", id);
200+
obsgauge = opentelemetry.metrics.ObservableGauge(...
201+
ObservableGaugeproxy, name, description, unit, callback);
202+
end
203+
end
204+
end
205+
206+
function [name, description, unit] = processSynchronousInputs(name, ...
207+
description, unit)
208+
import opentelemetry.common.mustBeScalarString
209+
name = mustBeScalarString(name);
210+
description = mustBeScalarString(description);
211+
unit = mustBeScalarString(unit);
212+
end
213+
214+
function [callback, name, description, unit] = processAsynchronousInputs(...
215+
callback, name, description, unit)
216+
[name, description, unit] = processSynchronousInputs(name, description, unit);
217+
if ~isa(callback, "function_handle")
218+
callback = []; % callback is invalid, set to empty double
219+
end
117220
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
classdef ObservableCounter < opentelemetry.metrics.AsynchronousInstrument
2+
% ObservableCounter is an asynchronous counter that records its value
3+
% via a callback and its value can only increase but not decrease
4+
5+
% Copyright 2023 The MathWorks, Inc.
6+
7+
methods (Access={?opentelemetry.metrics.Meter})
8+
9+
function obj = ObservableCounter(proxy, name, description, unit, callback)
10+
% Private constructor. Use getObservableCounter method of Meter
11+
% to create observable counters.
12+
[email protected](proxy, name, ...
13+
description, unit, callback);
14+
end
15+
16+
end
17+
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
classdef ObservableGauge < opentelemetry.metrics.AsynchronousInstrument
2+
% ObservableGauge is an asynchronous gauge that report its values via a
3+
% callback and its value cannot be summed in aggregation.
4+
5+
% Copyright 2023 The MathWorks, Inc.
6+
7+
methods (Access={?opentelemetry.metrics.Meter})
8+
9+
function obj = ObservableGauge(proxy, name, description, unit, callback)
10+
% Private constructor. Use getObservableGauge method of Meter
11+
% to create observable gauges.
12+
[email protected](proxy, name, ...
13+
description, unit, callback);
14+
end
15+
16+
end
17+
end

0 commit comments

Comments
 (0)