diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c68d4e..e539110 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,7 +98,7 @@ set(LIBMEXCLASS_FETCH_CONTENT_NAME libmexclass) set(LIBMEXCLASS_FETCH_CONTENT_GIT_REPOSITORY "https://github.com/mathworks/libmexclass.git") -set(LIBMEXCLASS_FETCH_CONTENT_GIT_TAG "77f3d72") +set(LIBMEXCLASS_FETCH_CONTENT_GIT_TAG "bf16a65") set(LIBMEXCLASS_FETCH_CONTENT_SOURCE_SUBDIR "libmexclass/cpp") diff --git a/README.md b/README.md index b6c1225..b4b950a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ 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 is supported. Metrics and logs will be in the future. +- Currently only tracing and metrics (synchronous instruments) are supported. Asynchronous metrics instruments and 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. ### MathWorks Products (https://www.mathworks.com) @@ -43,6 +44,7 @@ otelcol --config >> addpath ``` ## Examples +### Tracing 1. Create a default tracer provider and save it. ``` >> p = opentelemetry.sdk.trace.TracerProvider(); @@ -58,6 +60,22 @@ otelcol --config >> sp.endSpan(); ``` 4. If your collector is configured to display the data, you should see your span displayed. +### Metrics +1. Create a default meter provider and save it. +``` +>> p = opentelemetry.sdk.metrics.MeterProvider(); +>> setMeterProvider(p); +``` +2. Create a counter +``` +>> m = opentelemetry.metrics.getMeter("First Meter"); +>> c = m.createCounter("FirstCounter"); +``` +3. Increment the counter +``` +>> c.add(10); +``` +4. If your collector is configured to display the data, you should see your counter displayed after 1 minute. For more examples, see the "examples" folder. diff --git a/api/metrics/+opentelemetry/+metrics/Counter.m b/api/metrics/+opentelemetry/+metrics/Counter.m index f189b90..a851740 100644 --- a/api/metrics/+opentelemetry/+metrics/Counter.m +++ b/api/metrics/+opentelemetry/+metrics/Counter.m @@ -14,6 +14,16 @@ methods function add(obj, value, varargin) + % ADD Add to counter value + % ADD(C, VALUE) adds a nonnegative scalar numeric value to + % the counter. + % + % ADD(C, VALUE, ATTRIBUTES) also specifies attributes as a + % dictionary + % + % ADD(C, VALUE, ATTRNAME1, ATTRVALUE1, ATTRNAME2, + % ATTRVALUE2, ...) specifies attributes as trailing + % name-value pairs. obj.processValue(value, varargin{:}); end end diff --git a/api/metrics/+opentelemetry/+metrics/Histogram.m b/api/metrics/+opentelemetry/+metrics/Histogram.m index 439b636..c81cfa6 100644 --- a/api/metrics/+opentelemetry/+metrics/Histogram.m +++ b/api/metrics/+opentelemetry/+metrics/Histogram.m @@ -13,6 +13,16 @@ methods function record(obj, value, varargin) + % RECORD Aggregate a value into a histogram bin + % RECORD(H, VALUE) determine which bin VALUE falls into and + % increment that bin by 1. + % + % RECORD(H, VALUE, ATTRIBUTES) also specifies attributes as a + % dictionary + % + % RECORD(H, VALUE, ATTRNAME1, ATTRVALUE1, ATTRNAME2, + % ATTRVALUE2, ...) specifies attributes as trailing + % name-value pairs. obj.processValue(value, varargin{:}); end end diff --git a/api/metrics/+opentelemetry/+metrics/Meter.m b/api/metrics/+opentelemetry/+metrics/Meter.m index 45c2486..17e2eda 100644 --- a/api/metrics/+opentelemetry/+metrics/Meter.m +++ b/api/metrics/+opentelemetry/+metrics/Meter.m @@ -30,6 +30,15 @@ methods function counter = createCounter(obj, ctname, ctdescription, ctunit) + % CREATECOUNTER Create a counter + % C = CREATECOUNTER(M, NAME) creates a counter with the specified + % name. A counter's value can only increase but not + % decrease. + % + % C = CREATECOUNTER(M, NAME, DESCRIPTION, UNIT) also + % specifies a description and a unit. + % + % See also CREATEUPDOWNCOUNTER, CREATEHISTOGRAM arguments obj ctname @@ -38,8 +47,6 @@ end import opentelemetry.common.mustBeScalarString ctname = mustBeScalarString(ctname); - % cpp-opentelemetry end does not allow string input with spaces, - % replace any spaces with underscores as a temporary fix ctdescription = mustBeScalarString(ctdescription); ctunit = mustBeScalarString(ctunit); id = obj.Proxy.createCounter(ctname, ctdescription, ctunit); @@ -50,6 +57,15 @@ function updowncounter = createUpDownCounter(obj, ctname, ctdescription, ctunit) + % CREATEUPDOWNCOUNTER Create an UpDownCounter + % C = CREATEUPDOWNCOUNTER(M, NAME) creates an UpDownCounter + % with the specified name. An UpDownCounter's value can + % increase or decrease. + % + % C = CREATEUPDOWNCOUNTER(M, NAME, DESCRIPTION, UNIT) also + % specifies a description and a unit. + % + % See also CREATECOUNTER, CREATEHISTOGRAM arguments obj ctname @@ -59,8 +75,6 @@ import opentelemetry.common.mustBeScalarString ctname = mustBeScalarString(ctname); - % cpp-opentelemetry end does not allow string input with spaces, - % replace any spaces with underscores as a temporary fix ctdescription = mustBeScalarString(ctdescription); ctunit = mustBeScalarString(ctunit); id = obj.Proxy.createUpDownCounter(ctname, ctdescription, ctunit); @@ -71,6 +85,16 @@ function histogram = createHistogram(obj, hiname, hidescription, hiunit) + % CREATEHISTOGRAM Create a histogram + % H = CREATEHISTOGRAM(M, NAME) creates a histogram with the specified + % name. A histogram aggregates values into bins. Bins can be + % customized using a View object. + % + % H = CREATEHISTOGRAM(M, NAME, DESCRIPTION, UNIT) also + % specifies a description and a unit. + % + % See also CREATECOUNTER, CREATEUPDOWNCOUNTER, + % OPENTELEMETRY.SDK.METRICS.VIEW arguments obj hiname @@ -80,8 +104,6 @@ import opentelemetry.common.mustBeScalarString hiname = mustBeScalarString(hiname); - % cpp-opentelemetry end does not allow string input with spaces, - % replace any spaces with underscores as a temporary fix hidescription = mustBeScalarString(hidescription); hiunit = mustBeScalarString(hiunit); id = obj.Proxy.createHistogram(hiname, hidescription, hiunit); diff --git a/api/metrics/+opentelemetry/+metrics/UpDownCounter.m b/api/metrics/+opentelemetry/+metrics/UpDownCounter.m index 073c3d7..cf32bde 100644 --- a/api/metrics/+opentelemetry/+metrics/UpDownCounter.m +++ b/api/metrics/+opentelemetry/+metrics/UpDownCounter.m @@ -13,6 +13,16 @@ methods function add(obj, value, varargin) + % ADD Add to UpDownCounter value + % ADD(C, VALUE) adds scalar numeric value to the + % UpDownCounter. VALUE can be positive or negative. + % + % ADD(C, VALUE, ATTRIBUTES) also specifies attributes as a + % dictionary + % + % ADD(C, VALUE, ATTRNAME1, ATTRVALUE1, ATTRNAME2, + % ATTRVALUE2, ...) specifies attributes as trailing + % name-value pairs. obj.processValue(value, varargin{:}); end end diff --git a/examples/metrics/README.md b/examples/metrics/README.md new file mode 100644 index 0000000..3b65069 --- /dev/null +++ b/examples/metrics/README.md @@ -0,0 +1,10 @@ +# Metrics Example +This example shows how to emit OpenTelemetry metrics from MATLAB. It uses all 3 synchronous instruments counter, updowncounter, and histogram. +* At the beginning of the first run, initialization is necessary to create and store a global meter provider. +* The example then enters a loop and at each iteration updates all 3 instruments. The metrics will then be exported periodically at a fixed time interval. + +## Running the Example +1. Start an instance of [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector). +2. Start MATLAB. +3. Ensure the installation directory of OpenTelemetry-matlab is on the MATLAB path. +4. Run metrics_example. diff --git a/examples/metrics/metrics_example.m b/examples/metrics/metrics_example.m new file mode 100644 index 0000000..43835b3 --- /dev/null +++ b/examples/metrics/metrics_example.m @@ -0,0 +1,45 @@ +function metrics_example +% This example creates 3 metric instruments including a counter, an +% updowncounter, and a histogram. It then enters a loop and updates the +% value of the instruments at each iteration. + +% Copyright 2023 The MathWorks, Inc. + +% initialize meter provider during first run +runOnce(@initMetrics); + +% create meter and instruments +m = opentelemetry.metrics.getMeter("metrics_example"); +c = createCounter(m, "counter"); +u = createUpDownCounter(m, "updowncounter"); +h = createHistogram(m, "histogram"); +iterations = 20; % number of iterations + +for i = 1:iterations + c.add(randi(10)); + u.add(randi([-10 10])); + h.record(50 + 15*randn); % normal distribution with mean 50 and std 15 + pause(5); +end +end + + +function initMetrics +% set up global MeterProvider +exp = opentelemetry.exporters.otlp.defaultMetricExporter(); +reader = opentelemetry.sdk.metrics.PeriodicExportingMetricReader(exp, ... + "Interval", seconds(5), "Timeout", seconds(2.5)); % exports every 5 seconds +% Use custom histogram bins +v = opentelemetry.sdk.metrics.View(InstrumentType="histogram", HistogramBinEdges=0:10:100); +mp = opentelemetry.sdk.metrics.MeterProvider(reader, View=v); +setMeterProvider(mp); +end + +% This helper ensures the input function is only run once +function runOnce(fh) +persistent hasrun +if isempty(hasrun) + feval(fh); + hasrun = 1; +end +end diff --git a/sdk/metrics/+opentelemetry/+sdk/+metrics/MeterProvider.m b/sdk/metrics/+opentelemetry/+sdk/+metrics/MeterProvider.m index 268802d..2fe30c8 100644 --- a/sdk/metrics/+opentelemetry/+sdk/+metrics/MeterProvider.m +++ b/sdk/metrics/+opentelemetry/+sdk/+metrics/MeterProvider.m @@ -8,23 +8,23 @@ isShutdown (1,1) logical = false end - properties (Access=public) - MetricReader - View - Resource + properties (SetAccess=private) + MetricReader % Metric reader controls how often metrics are exported + View % View object used to customize collected metrics + Resource % Attributes attached to all metrics end methods function obj = MeterProvider(reader, optionnames, optionvalues) % SDK implementation of meter provider - % MP = OPENTELEMETRY.SDK.METRICS.METERPROVIDER creates a meter + % MP = OPENTELEMETRY.SDK.METRICS.METERPROVIDER creates a meter % provider that uses a periodic exporting metric reader and default configurations. % % MP = OPENTELEMETRY.SDK.METRICS.METERPROVIDER(R) uses metric - % reader R. Currently, the only supported metric reader is the periodic - % exporting metric reader. + % reader R. Currently, the only supported metric reader is the periodic + % exporting metric reader. % - % TP = OPENTELEMETRY.SDK.METRICS.METERPROVIDER(R, PARAM1, VALUE1, + % TP = OPENTELEMETRY.SDK.METRICS.METERPROVIDER(R, PARAM1, VALUE1, % PARAM2, VALUE2, ...) specifies optional parameter name/value pairs. % Parameters are: % "View" - View object used to customize collected metrics. @@ -59,10 +59,12 @@ "ConstructorArguments", {mpproxy.ID}); % leave other properties unassigned, they won't be used else - validnames = ["Resource"]; + validnames = ["Resource", "View"]; resourcekeys = string.empty(); resourcevalues = {}; resource = dictionary(resourcekeys, resourcevalues); + suppliedview = false; + viewid = 0; for i = 1:length(optionnames) namei = validatestring(optionnames{i}, validnames); valuei = optionvalues{i}; @@ -79,31 +81,53 @@ if all(cellfun(@iscell, resourcevalues)) resourcevalues = [resourcevalues{:}]; end + elseif strcmp(namei, "View") + suppliedview = true; + view = valuei; + if ~isa(view, "opentelemetry.sdk.metrics.View") + error("opentelemetry:sdk:metrics:MeterProvider:InvalidViewType", ... + "View input must be a opentelemetry.sdk.metrics.View object."); + end + viewid = view.Proxy.ID; end end obj.Proxy = libmexclass.proxy.Proxy("Name", ... "libmexclass.opentelemetry.sdk.MeterProviderProxy", ... - "ConstructorArguments", {reader.Proxy.ID, resourcekeys, resourcevalues}); + "ConstructorArguments", {reader.Proxy.ID, resourcekeys, ... + resourcevalues, suppliedview, viewid}); obj.MetricReader = reader; obj.Resource = resource; + if suppliedview + obj.View = view; + end end end - + function addMetricReader(obj, reader) - arguments - obj - reader (1,1) {mustBeA(reader, "opentelemetry.sdk.metrics.PeriodicExportingMetricReader")} - end + % ADDMETRICREADER Add an additional metric reader + % ADDMETRICREADER(MP, R) adds an additional metric reader + % R to the list of metric readers used by meter provider + % MP. + % + % See also ADDVIEW, OPENTELEMETRY.SDK.METRICS.PERIODICEXPORTINGMETRICREADER + arguments + obj + reader (1,1) {mustBeA(reader, "opentelemetry.sdk.metrics.PeriodicExportingMetricReader")} + end obj.Proxy.addMetricReader(reader.Proxy.ID); obj.MetricReader = [obj.MetricReader, reader]; end function addView(obj, view) - arguments - obj - view (1,1) {mustBeA(view, "opentelemetry.sdk.metrics.View")} - end + % ADDVIEW Add an additional view + % ADDVIEW(MP, V) adds an additional view V. + % + % See also ADDMETRICREADER, OPENTELEMETRY.SDK.METRICS.VIEW + arguments + obj + view (1,1) {mustBeA(view, "opentelemetry.sdk.metrics.View")} + end obj.Proxy.addView(view.Proxy.ID); obj.View = [obj.View, view]; end diff --git a/sdk/metrics/+opentelemetry/+sdk/+metrics/MetricExporter.m b/sdk/metrics/+opentelemetry/+sdk/+metrics/MetricExporter.m index 8b3de57..7a9da5b 100644 --- a/sdk/metrics/+opentelemetry/+sdk/+metrics/MetricExporter.m +++ b/sdk/metrics/+opentelemetry/+sdk/+metrics/MetricExporter.m @@ -24,7 +24,7 @@ methods function obj = set.PreferredAggregationTemporality(obj, temporality) temporality = validatestring(temporality, ["cumulative", "delta"]); - obj.Proxy.setTemporality(temporality); + obj.Proxy.setTemporality(temporality); %#ok obj.PreferredAggregationTemporality = temporality; end diff --git a/sdk/metrics/+opentelemetry/+sdk/+metrics/PeriodicExportingMetricReader.m b/sdk/metrics/+opentelemetry/+sdk/+metrics/PeriodicExportingMetricReader.m index 1b7aefc..545e39f 100644 --- a/sdk/metrics/+opentelemetry/+sdk/+metrics/PeriodicExportingMetricReader.m +++ b/sdk/metrics/+opentelemetry/+sdk/+metrics/PeriodicExportingMetricReader.m @@ -1,5 +1,6 @@ classdef PeriodicExportingMetricReader < matlab.mixin.Heterogeneous -% Base class of metric reader +% Periodic exporting metric reader passes collected metrics to an exporter +% periodically at a fixed time interval. % Copyright 2023 The MathWorks, Inc. @@ -12,13 +13,35 @@ end properties - Interval (1,1) duration = minutes(1) - Timeout (1,1) duration = seconds(30) + Interval (1,1) duration = minutes(1) % Time interval between exports + Timeout (1,1) duration = seconds(30) % Maximum time before export is timed out and gets aborted end - methods %(Access=?opentelemetry.sdk.metrics.MeterProvider) + methods function obj = PeriodicExportingMetricReader(metricexporter, optionnames, optionvalues) - + % Periodic exporting metric reader passes collected metrics to + % an exporter periodically at a fixed time interval. + % R = OPENTELEMETRY.SDK.METRICS.PERIODICEXPORTINGMETRICREADER + % creates a periodic exporting metric reader that exports + % every minute using an OTLP HTTP exporter, which exports in + % OpenTelemetry Protocol (OTLP) format through HTTP. + % + % R = OPENTELEMETRY.SDK.METRICS.PERIODICEXPORTINGMETRICREADER(EXP) + % specifies the metric exporter. Supported metric exporters + % are OTLP HTTP exporter and OTLP gRPC exporter. + % + % R = OPENTELEMETRY.SDK.METRICS.PERIODICEXPORTINGMETRICREADER( + % EXP, PARAM1, VALUE1, PARAM2, VALUE2, ...) specifies + % optional parameter name/value pairs. Parameters are: + % "Interval" - Time interval between exports specified as + % a duration. Default is 1 minute. + % "Timeout" - Maximum time before export is timed out + % and gets aborted, specified as a duration. + % Default is 30 seconds. + % + % See also OPENTELEMETRY.EXPORTERS.OTLP.OTLPHTTPMETRICEXPORTER, + % OPENTELEMETRY.EXPORTERS.OTLP.OTLPGRPCMETRICEXPORTER, + % OPENTELEMETRY.SDK.METRICS.METERPROVIDER arguments metricexporter {mustBeA(metricexporter, "opentelemetry.sdk.metrics.MetricExporter")} = ... opentelemetry.exporters.otlp.defaultMetricExporter() diff --git a/sdk/metrics/+opentelemetry/+sdk/+metrics/View.m b/sdk/metrics/+opentelemetry/+sdk/+metrics/View.m index 61e03b1..c6998d8 100644 --- a/sdk/metrics/+opentelemetry/+sdk/+metrics/View.m +++ b/sdk/metrics/+opentelemetry/+sdk/+metrics/View.m @@ -1,65 +1,134 @@ classdef View + % View enables customization of output metrics. Supported customization + % includes: + % * Metric name + % * Aggregation type + % * Histogram bins + % * Ignore unwanted instruments + % * Ignore unwanted attributes -% Copyright 2023 The MathWorks, Inc. + % Copyright 2023 The MathWorks, Inc. properties (GetAccess={?opentelemetry.sdk.metrics.MeterProvider}) Proxy % Proxy object to interface C++ code end properties (SetAccess=immutable) - Name - Description - Unit - InstrumentName - InstrumentType - MeterName - MeterVersion - MeterSchemaURL - AttributeKeys - Aggregation - HistogramBinEdges + Name (1,1) string % View name + Description (1,1) string % Description of view + InstrumentName (1,1) string % Name of the instrument this view applies to + InstrumentType (1,1) string % Type of instrument this view applies to + InstrumentUnit (1,1) string % Unit of instrument this view applies to + MeterName (1,1) string % Name of the meter this view applies to + MeterVersion (1,1) string % Version of the meter this view applies to + MeterSchema (1,1) string % Schema URL of the meter this view applies to + AllowedAttributes (1,:) string % List of attribute keys that are kept. All other attributes are ignored. + Aggregation (1,1) string % Customized aggregation type + HistogramBinEdges (1,:) double % Vector of customized bin edges for histogram end methods function obj = View(options) + % View enables customization of output metrics + % V = OPENTELEMETRY.SDK.METRICS.VIEW(PARAM1, VALUE1, PARAM2, + % VALUE2, ...) creates a view object and specifies its + % behavior using parameter name/value pairs. Parameters are: + % "Name" - Name of view. Any metric this view + % applies to will be renamed to this name. + % "Description" - Description of view. + % "InstrumentName" - Specifies an instrument name. This + % view will be applied to all metrics + % generated from instruments with + % this name. + % "InstrumentType" - Specifies an instrument type. This + % view will be applied to all metrics + % generated from all instruments of + % this type. + % "InstrumentUnit" - Specifies an instrument unit. This + % view will be applied to all metrics + % generated from all instruments with + % this unit. + % "MeterName" - Specifies a meter name. This view + % will be applied to all metrics + % generated from all instruments created + % by meters with this name. + % "MeterVersion" - Specifies a meter version. This view + % will be applied to all metrics + % generated from all instruments created + % by meters with this version. + % "MeterSchema" - Specifies a meter schema URL. This view + % will be applied to all metrics + % generated from all instruments created + % by meters with this schema URL. + % "AllowedAttributes" - Specifies a list of attributes + % that will be kept. All other + % attributes will be dropped. + % "Aggregation" - Change instruments to use a + % different aggregation beahvior. + % "HistogramBinEdges" - Use a different set of bins + % in all histograms this view + % applies to + % + % Examples: + % import opentelemetry.sdk.metrics + % + % % Change bin edges of all histograms created by any + % % meter named "Meter1" + % v = view(InstrumentType="histogram", MeterName="Meter1", ... + % HistogramBinEdges = 0:100:500); + % + % % Ignore all counters created by any meter named "xyz" + % v = view(MeterName="xyz", InstrumentType="counter", ... + % Aggregation="drop"); + % + % See also OPENTELEMETRY.SDK.METRICS.METERPROVIDER arguments - options.Name="" - options.Description="" - options.Unit="" - options.InstrumentName="" - options.InstrumentType="" - options.MeterName="" - options.MeterVersion="" - options.MeterSchemaURL="" - options.AttributeKeys="" - options.Aggregation="" - options.HistogramBinEdges=[] + options.Name {mustBeTextScalar} = "" + options.Description {mustBeTextScalar} = "" + options.InstrumentName {mustBeTextScalar} = "*" + options.InstrumentType {mustBeTextScalar} = "counter" + options.InstrumentUnit {mustBeTextScalar} = "" + options.MeterName {mustBeTextScalar} = "" + options.MeterVersion {mustBeTextScalar} = "" + options.MeterSchema {mustBeTextScalar} = "" + %options.AllowedAttributes {mustBeText, mustBeVector} % no default here + %options.Aggregation {mustBeTextScalar} = "default" + options.HistogramBinEdges {mustBeNumeric, mustBeVector} = zeros(1,0) end - instrument_types = ["Counter", "Histogram", "UpDownCounter", "ObservableCounter", "ObservableGauge", "ObservableUpDownCounter"]; + % Aggregation and AllowedAttributes are not yet supported + options.Aggregation = "default"; + + instrument_types = ["counter", "histogram", "updowncounter"]; instrument_type = validatestring(options.InstrumentType, instrument_types); - instrumentTypeCategory = find(instrument_type==instrument_types)-1; - aggregation_types = ["Drop", "Histogram", "LastValue", "Sum", "Default"]; + aggregation_types = ["drop", "histogram", "lastvalue", "sum", "default"]; aggregation_type = validatestring(options.Aggregation, aggregation_types); - aggregationCategory = find(aggregation_type==aggregation_types)-1; + % check whether AllowedAttributes is defined + filter_attributes = isfield(options, "AllowedAttributes"); + if ~filter_attributes + % put some defaults here, which will be ignored since filter_attributes is false + options.AllowedAttributes = strings(1,0); + end + obj.Proxy = libmexclass.proxy.Proxy("Name", "libmexclass.opentelemetry.sdk.ViewProxy", ... - "ConstructorArguments", {options.Name, options.Description, options.Unit, options.InstrumentName, ... - instrumentTypeCategory, options.MeterName, options.MeterVersion, options.MeterSchemaURL, ... - options.AttributeKeys, aggregationCategory, options.HistogramBinEdges}); + "ConstructorArguments", {options.Name, options.Description, options.InstrumentName, ... + instrument_type, options.InstrumentUnit, options.MeterName, ... + options.MeterVersion, options.MeterSchema, filter_attributes,... + options.AllowedAttributes, aggregation_type, options.HistogramBinEdges}); - obj.Name = options.Name; - obj.Description = options.Description; - obj.Unit = options.Unit; - obj.InstrumentName = options.InstrumentName; - obj.InstrumentType = options.InstrumentType; - obj.MeterName = options.MeterName; - obj.MeterVersion = options.MeterVersion; - obj.MeterSchemaURL = options.MeterSchemaURL; - obj.AttributeKeys = options.AttributeKeys; - obj.Aggregation = options.Aggregation; - obj.HistogramBinEdges = options.HistogramBinEdges; + obj.Name = string(options.Name); + obj.Description = string(options.Description); + obj.InstrumentName = string(options.InstrumentName); + obj.InstrumentType = instrument_type; + obj.InstrumentUnit = string(options.InstrumentUnit); + obj.MeterName = string(options.MeterName); + obj.MeterVersion = string(options.MeterVersion); + obj.MeterSchema = string(options.MeterSchema); + obj.AllowedAttributes = reshape(string(options.AllowedAttributes),1,[]); + obj.Aggregation = aggregation_type; + obj.HistogramBinEdges = reshape(double(options.HistogramBinEdges),1,[]); end end end \ No newline at end of file diff --git a/sdk/metrics/include/opentelemetry-matlab/sdk/metrics/ViewProxy.h b/sdk/metrics/include/opentelemetry-matlab/sdk/metrics/ViewProxy.h index 1bf18b6..bbde3af 100644 --- a/sdk/metrics/include/opentelemetry-matlab/sdk/metrics/ViewProxy.h +++ b/sdk/metrics/include/opentelemetry-matlab/sdk/metrics/ViewProxy.h @@ -28,23 +28,40 @@ namespace nostd = opentelemetry::nostd; namespace libmexclass::opentelemetry::sdk { class ViewProxy : public libmexclass::proxy::Proxy { public: - ViewProxy(std::unique_ptr view, std::unique_ptr instrumentSelector, std::unique_ptr meterSelector); + ViewProxy(std::string name, std::string description, std::string instrumentName, + metrics_sdk::InstrumentType instrumentType, std::string instrumentUnit, std::string meterName, + std::string meterVersion, std::string meterSchema, std::unordered_map allowedAttributes, + bool filterAttributes, metrics_sdk::AggregationType aggregationType, std::vector histogramBinEdges) + : Name(std::move(name)), Description(std::move(description)), InstrumentName(std::move(instrumentName)), InstrumentType(instrumentType), + InstrumentUnit(std::move(instrumentUnit)), MeterName(std::move(meterName)), MeterVersion(std::move(meterVersion)), MeterSchema(std::move(meterSchema)), + AllowedAttributes(std::move(allowedAttributes)), FilterAttributes(filterAttributes), Aggregation(aggregationType), HistogramBinEdges(std::move(histogramBinEdges)) {} static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments); - // void processView(libmexclass::proxy::method::Context& context); + std::unique_ptr getView(); - std::unique_ptr getView(libmexclass::proxy::method::Context& context); + std::unique_ptr getInstrumentSelector(); - std::unique_ptr getInstrumentSelector(libmexclass::proxy::method::Context& context); - - std::unique_ptr getMeterSelector(libmexclass::proxy::method::Context& context); + std::unique_ptr getMeterSelector(); private: std::unique_ptr View; std::unique_ptr InstrumentSelector; - std::unique_ptr MeterSelector; + std::string InstrumentName; + metrics_sdk::InstrumentType InstrumentType; + std::string InstrumentUnit; + + std::string MeterName; + std::string MeterVersion; + std::string MeterSchema; + + std::string Name; + std::string Description; + metrics_sdk::AggregationType Aggregation; + std::vector HistogramBinEdges; + std::unordered_map AllowedAttributes; + bool FilterAttributes; }; } diff --git a/sdk/metrics/src/MeterProviderProxy.cpp b/sdk/metrics/src/MeterProviderProxy.cpp index df2b535..9360d9f 100644 --- a/sdk/metrics/src/MeterProviderProxy.cpp +++ b/sdk/metrics/src/MeterProviderProxy.cpp @@ -38,11 +38,20 @@ libmexclass::proxy::MakeResult MeterProviderProxy::make(const libmexclass::proxy auto reader = std::static_pointer_cast( libmexclass::proxy::ProxyManager::getProxy(readerid))->getInstance(); - auto view = metrics_sdk::ViewRegistryFactory::Create(); - auto p = metrics_sdk::MeterProviderFactory::Create(std::move(view), resource_custom); + auto view_registry = metrics_sdk::ViewRegistryFactory::Create(); + auto p = metrics_sdk::MeterProviderFactory::Create(std::move(view_registry), resource_custom); auto *p_sdk = static_cast(p.get()); p_sdk->AddMetricReader(std::move(reader)); + // View + matlab::data::TypedArray supplied_view_mda = constructor_arguments[3]; + if (supplied_view_mda[0]) { // process the supplied View + matlab::data::TypedArray viewid_mda = constructor_arguments[4]; + libmexclass::proxy::ID viewid = viewid_mda[0]; + auto view = std::static_pointer_cast(libmexclass::proxy::ProxyManager::getProxy(viewid)); + p_sdk->AddView(view->getInstrumentSelector(), view->getMeterSelector(), view->getView()); + } + auto p_out = nostd::shared_ptr(std::move(p)); out = std::make_shared(p_out); } @@ -61,12 +70,9 @@ void MeterProviderProxy::addMetricReader(libmexclass::proxy::method::Context& co void MeterProviderProxy::addView(libmexclass::proxy::method::Context& context) { matlab::data::TypedArray viewid_mda = context.inputs[0]; - libmexclass::proxy::ID viewid = viewid_mda[0]; - + std::shared_ptr view = std::static_pointer_cast(libmexclass::proxy::ProxyManager::getProxy(viewid_mda[0])); static_cast(*CppMeterProvider).AddView( - std::static_pointer_cast(libmexclass::proxy::ProxyManager::getProxy(viewid))->getInstrumentSelector(context), - std::static_pointer_cast(libmexclass::proxy::ProxyManager::getProxy(viewid))->getMeterSelector(context), - std::static_pointer_cast(libmexclass::proxy::ProxyManager::getProxy(viewid))->getView(context)); + view->getInstrumentSelector(), view->getMeterSelector(), view->getView()); return; } diff --git a/sdk/metrics/src/ViewProxy.cpp b/sdk/metrics/src/ViewProxy.cpp index 3450edf..7124ad0 100644 --- a/sdk/metrics/src/ViewProxy.cpp +++ b/sdk/metrics/src/ViewProxy.cpp @@ -4,101 +4,126 @@ #include "libmexclass/proxy/ProxyManager.h" -#include - namespace libmexclass::opentelemetry::sdk { -ViewProxy::ViewProxy(std::unique_ptr view, std::unique_ptr instrumentSelector, std::unique_ptr meterSelector){ - View = std::move(view); - InstrumentSelector = std::move(instrumentSelector); - MeterSelector = std::move(meterSelector); - REGISTER_METHOD(ViewProxy, getView); - REGISTER_METHOD(ViewProxy, getInstrumentSelector); - REGISTER_METHOD(ViewProxy, getMeterSelector); -} - libmexclass::proxy::MakeResult ViewProxy::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { libmexclass::proxy::MakeResult out; - //Create View + // Name matlab::data::StringArray name_mda = constructor_arguments[0]; - auto name = name_mda[0]; + std::string name = static_cast(name_mda[0]); + // Description matlab::data::StringArray description_mda = constructor_arguments[1]; - auto description = description_mda[0]; - - matlab::data::StringArray unit_mda = constructor_arguments[2]; - auto unit = unit_mda[0]; - - matlab::data::TypedArray aggregation_type_mda = constructor_arguments[9]; - metrics_sdk::AggregationType aggregation_type = static_cast(static_cast(aggregation_type_mda[0])); - - std::shared_ptr aggregation_config = std::shared_ptr(new metrics_sdk::HistogramAggregationConfig()); - if(aggregation_type == metrics_sdk::AggregationType::kHistogram){ - matlab::data::TypedArray histogramBinEdges_mda = constructor_arguments[10]; - std::vector histogramBinEdges; - for (auto h : histogramBinEdges_mda) { - histogramBinEdges.push_back(h); - } - aggregation_config->boundaries_ = histogramBinEdges; - } - - std::unique_ptr attributes_processor; - matlab::data::TypedArray attributes_mda = constructor_arguments[8]; - if(attributes_mda.getNumberOfElements()==1 && attributes_mda[0]==""){ - attributes_processor = std::unique_ptr(new metrics_sdk::DefaultAttributesProcessor()); - }else{ - std::unordered_map allowed_attribute_keys; - for (size_t a=0; a(new metrics_sdk::FilteringAttributesProcessor(allowed_attribute_keys)); - } - - auto view = metrics_sdk::ViewFactory::Create(name, description, - unit, aggregation_type, aggregation_config, std::move(attributes_processor)); - + std::string description = static_cast(description_mda[0]); - // Create Instrument Selector - matlab::data::TypedArray instrument_type_mda = constructor_arguments[4]; - metrics_sdk::InstrumentType instrument_type = static_cast(static_cast(instrument_type_mda[0])); - - matlab::data::StringArray instrument_name_mda = constructor_arguments[3]; + // InstrumentName + matlab::data::StringArray instrument_name_mda = constructor_arguments[2]; auto instrument_name = static_cast(instrument_name_mda[0]); - auto unit_str = static_cast(unit); + // InstrumentType + matlab::data::StringArray instrument_type_mda = constructor_arguments[3]; + matlab::data::String instrument_type_str = instrument_type_mda[0]; + metrics_sdk::InstrumentType instrument_type; + if (instrument_type_str.compare(u"counter") == 0) { + 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); + instrument_type = metrics_sdk::InstrumentType::kHistogram; + } - auto instrumentSelector = metrics_sdk::InstrumentSelectorFactory::Create(instrument_type, - instrument_name, unit_str); + // InstrumentUnit + matlab::data::StringArray unit_mda = constructor_arguments[4]; + auto instrument_unit = static_cast(unit_mda[0]); - // Create Meter Selector + // MeterName matlab::data::StringArray meter_name_mda = constructor_arguments[5]; auto meter_name = static_cast(meter_name_mda[0]); + // MeterVersion matlab::data::StringArray meter_version_mda = constructor_arguments[6]; auto meter_version = static_cast(meter_version_mda[0]); + // MeterSchema matlab::data::StringArray meter_schema_mda = constructor_arguments[7]; auto meter_schema = static_cast(meter_schema_mda[0]); - auto meterSelector = metrics_sdk::MeterSelectorFactory::Create(meter_name, - meter_version, meter_schema); + // FilterAttributes (a boolean indicating whether AllowedAttributes has been specified) + matlab::data::TypedArray filter_attributes_mda = constructor_arguments[8]; + bool filter_attributes = filter_attributes_mda[0]; + + // AllowedAttributes + std::unique_ptr attributes_processor; + matlab::data::StringArray attributes_mda = constructor_arguments[9]; + std::unordered_map allowed_attribute_keys; + for (size_t a=0; a(attributes_mda[a]); + if (!attr.empty()) { + allowed_attribute_keys[attr] = true; + } + } + // Aggregation + matlab::data::StringArray aggregation_type_mda = constructor_arguments[10]; + matlab::data::String aggregation_type_str = aggregation_type_mda[0]; + metrics_sdk::AggregationType aggregation_type; + if (aggregation_type_str.compare(u"sum") == 0) { + aggregation_type = metrics_sdk::AggregationType::kSum; + } else if (aggregation_type_str.compare(u"drop") == 0) { + aggregation_type = metrics_sdk::AggregationType::kDrop; + } else if (aggregation_type_str.compare(u"lastvalue") == 0) { + aggregation_type = metrics_sdk::AggregationType::kLastValue; + } else if (aggregation_type_str.compare(u"histogram") == 0) { + aggregation_type = metrics_sdk::AggregationType::kHistogram; + } else { + assert(aggregation_type_str.compare(u"default") == 0); + aggregation_type = metrics_sdk::AggregationType::kDefault; + } + + // HistogramBinEdges + std::vector histogramBinEdges; + if(aggregation_type == metrics_sdk::AggregationType::kHistogram || + (aggregation_type == metrics_sdk::AggregationType::kDefault && instrument_type == metrics_sdk::InstrumentType::kHistogram)){ + matlab::data::TypedArray histogramBinEdges_mda = constructor_arguments[11]; + for (auto h : histogramBinEdges_mda) { + histogramBinEdges.push_back(h); + } + } // Call View Proxy Constructor - return std::make_shared(std::move(view), std::move(instrumentSelector), std::move(meterSelector)); + return std::make_shared(std::move(name), std::move(description), std::move(instrument_name), instrument_type, + std::move(instrument_unit), std::move(meter_name), std::move(meter_version), std::move(meter_schema), + std::move(allowed_attribute_keys), filter_attributes, aggregation_type, std::move(histogramBinEdges)); } -std::unique_ptr ViewProxy::getView(libmexclass::proxy::method::Context& context){ - return std::move(View); +std::unique_ptr ViewProxy::getView(){ + // AttributesProcessor + std::unique_ptr attributes_processor; + if(FilterAttributes){ + attributes_processor = std::unique_ptr(new metrics_sdk::FilteringAttributesProcessor(AllowedAttributes)); + }else{ + attributes_processor = std::unique_ptr(new metrics_sdk::DefaultAttributesProcessor()); + } + + // HistogramAggregationConfig + auto aggregation_config = std::shared_ptr(new metrics_sdk::HistogramAggregationConfig()); + if(Aggregation == metrics_sdk::AggregationType::kHistogram || + (Aggregation == metrics_sdk::AggregationType::kDefault && InstrumentType == metrics_sdk::InstrumentType::kHistogram)){ + aggregation_config->boundaries_ = HistogramBinEdges; + } + + // View + return metrics_sdk::ViewFactory::Create(Name, Description, "", Aggregation, aggregation_config, std::move(attributes_processor)); } -std::unique_ptr ViewProxy::getInstrumentSelector(libmexclass::proxy::method::Context& context){ - return std::move(InstrumentSelector); +std::unique_ptr ViewProxy::getInstrumentSelector(){ + return metrics_sdk::InstrumentSelectorFactory::Create(InstrumentType, InstrumentName, InstrumentUnit); } -std::unique_ptr ViewProxy::getMeterSelector(libmexclass::proxy::method::Context& context){ - return std::move(MeterSelector); +std::unique_ptr ViewProxy::getMeterSelector(){ + return metrics_sdk::MeterSelectorFactory::Create(MeterName, MeterVersion, MeterSchema); } } diff --git a/test/tmetrics_sdk.m b/test/tmetrics_sdk.m index fc98925..9fc82ba 100644 --- a/test/tmetrics_sdk.m +++ b/test/tmetrics_sdk.m @@ -147,15 +147,16 @@ function testCustomResource(testCase) end function testViewBasic(testCase) - mp = opentelemetry.sdk.metrics.MeterProvider(testCase.ShortIntervalReader); - + % testViewBasic: check view object changes the name and + % description of output metrics view_name = "counter_view"; view_description = "view_description"; - view = opentelemetry.sdk.metrics.View(Name="counter_view", Description="view_description", InstrumentName="mycounter", InstrumentType="Counter", MeterName="mymeter", MeterVersion="1.2.0", MeterSchemaURL="", Aggregation="Sum"); - - addView(mp, view); - - m = getMeter(mp, "mymeter", "1.2.0", ""); + view = opentelemetry.sdk.metrics.View(Name=view_name, .... + Description=view_description, InstrumentType="Counter"); + mp = opentelemetry.sdk.metrics.MeterProvider(... + testCase.ShortIntervalReader, View=view); + + m = getMeter(mp, "mymeter", "1.0.0", "http://schema.org"); c = createCounter(m, "mycounter"); % add value and attributes @@ -181,17 +182,23 @@ function testViewBasic(testCase) function testViewHistogram(testCase) + % testViewHistogram: Change histogram bins mp = opentelemetry.sdk.metrics.MeterProvider(testCase.ShortIntervalReader); view_name = "histogram_view"; view_description = "view_description"; + meter_name = "mymeter"; + histogram_name = "myhistogram"; bin_edges = [0; 100; 200; 300; 400; 500]; - view = opentelemetry.sdk.metrics.View(Name="histogram_view", Description="view_description", InstrumentName="myhistogram", InstrumentType="Histogram", MeterName="mymeter", Aggregation="Histogram", HistogramBinEdges=bin_edges); + view = opentelemetry.sdk.metrics.View(Name=view_name, ... + Description=view_description, InstrumentName=histogram_name, ... + InstrumentType="Histogram", MeterName=meter_name, ... + HistogramBinEdges=bin_edges); addView(mp, view); - m = mp.getMeter("mymeter"); - hist = m.createHistogram("myhistogram"); + m = mp.getMeter(meter_name); + hist = m.createHistogram(histogram_name); % record values hist.record(0); @@ -226,6 +233,66 @@ function testViewHistogram(testCase) verifyEqual(testCase, dp.bucketCounts, expected_buckets); end + function testMultipleViews(testCase) + % testMultipleView: Applying multiple views to a meter provider + + % match instrument name + instmatch_name = "match_instrument_name"; + instmatch = opentelemetry.sdk.metrics.View(Name=instmatch_name, .... + InstrumentType="Counter", Instrumentname="foo(.*)"); + + % match meter name + metermatch_name = "match_meter_name"; + metermatch = opentelemetry.sdk.metrics.View(Name=metermatch_name, .... + InstrumentType="Counter", MeterName = "abc"); + mp = opentelemetry.sdk.metrics.MeterProvider(... + testCase.ShortIntervalReader, View=instmatch); + addView(mp, metermatch); + + mxyz = getMeter(mp, "xyz"); + foo_name = "foo1"; + bar_name = "bar1"; + cfoo = createCounter(mxyz, foo_name); + cbar = createCounter(mxyz, bar_name); + mabc = getMeter(mp, "abc"); + quux_name = "quux1"; + cquux = createCounter(mabc, quux_name); + + valfoo = 10; + valbar = 25; + valquux = 40; + cfoo.add(valfoo); + cbar.add(valbar); + cquux.add(valquux); + + pause(2.5); + + clear mxyz mabc; + results = readJsonResults(testCase); + results = vertcat(results{end}.resourceMetrics.scopeMetrics.metrics); + + % verify view name only applied to matched metric + metricnames = {results.name}; + baridx = find(strcmp(metricnames, bar_name)); + fooidx = find(strcmp(metricnames, foo_name)); + quuxidx = find(strcmp(metricnames, quux_name)); + instmatchidx = find(strcmp(metricnames, instmatch_name)); + metermatchidx = find(strcmp(metricnames, metermatch_name)); + verifyNotEmpty(testCase, baridx); + verifyEmpty(testCase, fooidx); + verifyEmpty(testCase, quuxidx); + verifyNotEmpty(testCase, instmatchidx); + verifyNotEmpty(testCase, metermatchidx); + + % verify count value + barcount = results(baridx).sum.dataPoints; + verifyEqual(testCase, barcount.asDouble, valbar); + instmatchcount = results(instmatchidx).sum.dataPoints; + verifyEqual(testCase, instmatchcount.asDouble, valfoo); + metermatchcount = results(metermatchidx).sum.dataPoints; + verifyEqual(testCase, metermatchcount.asDouble, valquux); + end + function testShutdown(testCase) % testShutdown: shutdown method should stop exporting % of metrics