From f3884c379818b6a2e23dbe90d714a2d8fc44d77d Mon Sep 17 00:00:00 2001 From: duncanpo Date: Thu, 2 Nov 2023 12:14:00 -0400 Subject: [PATCH] Make metric reader and exporter properties editable --- .../+exporters/+otlp/OtlpGrpcMetricExporter.m | 159 ++++++++---------- .../+exporters/+otlp/OtlpHttpMetricExporter.m | 151 +++++++---------- .../otlp/OtlpGrpcMetricExporterProxy.h | 16 +- .../otlp/OtlpHttpMetricExporterProxy.h | 16 +- .../otlp/src/OtlpGrpcMetricExporterProxy.cpp | 126 +++++++------- .../otlp/src/OtlpHttpMetricExporterProxy.cpp | 141 ++++++---------- .../+sdk/+metrics/MetricExporter.m | 4 +- .../+metrics/PeriodicExportingMetricReader.m | 61 +++---- .../PeriodicExportingMetricReaderProxy.h | 6 +- .../PeriodicExportingMetricReaderProxy.cpp | 37 ++-- test/tmetrics.m | 10 +- 11 files changed, 323 insertions(+), 404 deletions(-) diff --git a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcMetricExporter.m b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcMetricExporter.m index edc7078..5d195fd 100644 --- a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcMetricExporter.m +++ b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcMetricExporter.m @@ -5,14 +5,14 @@ % Copyright 2023 The MathWorks, Inc. - properties (SetAccess=immutable) - Endpoint (1,1) string % Export destination - UseCredentials (1,1) logical % Whether to use SSL credentials - CertificatePath (1,1) string % Path to .pem file for SSL encryption - CertificateString (1,1) string % In-memory string representation of .pem file for SSL encryption - Timeout (1,1) duration % Maximum time above which exports will abort - HttpHeaders (1,1) dictionary % Additional HTTP headers - PreferredAggregationTemporality (1,1) string % Preferred Aggregation Temporality + properties + Endpoint (1,1) string = "http://localhost:4317" % Export destination + UseCredentials (1,1) logical = false % Whether to use SSL credentials + CertificatePath (1,1) string = "" % Path to .pem file for SSL encryption + CertificateString (1,1) string = "" % In-memory string representation of .pem file for SSL encryption + Timeout (1,1) duration = seconds(10) % Maximum time above which exports will abort + HttpHeaders (1,1) dictionary = dictionary(string.empty, string.empty) % Additional HTTP headers + PreferredAggregationTemporality (1,1) string = "cumulative" % Preferred Aggregation Temporality end methods @@ -47,100 +47,79 @@ optionvalues end + obj = obj@opentelemetry.sdk.metrics.MetricExporter(... + "libmexclass.opentelemetry.exporters.OtlpGrpcMetricExporterProxy"); + validnames = ["Endpoint", "UseCredentials ", "CertificatePath", ... "CertificateString", "Timeout", "HttpHeaders", "PreferredAggregationTemporality"]; - % set default values to empty or negative - endpoint = ""; - usessl = false; - certificatepath = ""; - certificatestring = ""; - timeout_millis = -1; - headerkeys = string.empty(); - headervalues = string.empty(); - temporality = ""; for i = 1:length(optionnames) namei = validatestring(optionnames{i}, validnames); valuei = optionvalues{i}; - if strcmp(namei, "Endpoint") - if ~(isStringScalar(valuei) || (ischar(valuei) && isrow(valuei))) - error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:EndpointNotScalarText", "Endpoint must be a scalar string."); - end - endpoint = string(valuei); - elseif strcmp(namei, "UseCredentials ") - if ~((islogical(valuei) || isnumeric(valuei)) && isscalar(valuei)) - error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:UseCredentialsNotScalarLogical", "UseCredentials must be a scalar logical.") - end - usessl = logical(valuei); - elseif strcmp(namei, "CertificatePath") - if ~(isStringScalar(valuei) || (ischar(valuei) && isrow(valuei))) - error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:CertificatePathNotScalarText", "CertificatePath must be a scalar string."); - end - certificatepath = string(valuei); - elseif strcmp(namei, "CertificateString") - if ~(isStringScalar(valuei) || (ischar(valuei) && isrow(valuei))) - error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:CertificateStringNotScalarText", "CertificateString must be a scalar string."); - end - certificatestring = string(valuei); - elseif strcmp(namei, "Timeout") - if ~(isduration(valuei) && isscalar(valuei)) - error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:TimeoutNotScalarDuration", "Timeout must be a scalar duration."); - end - timeout = valuei; - timeout_millis = milliseconds(timeout); - elseif strcmp(namei, "HttpHeaders") % HttpHeaders - if ~isa(valuei, "dictionary") - error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:HttpHeadersNotDictionary", "HttpHeaders input must be a dictionary."); - end - httpheaders = valuei; - headerkeys = keys(valuei); - headervalues = values(valuei); - if ~isstring(headervalues) - error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:HttpHeadersNonStringValues", "HttpHeaders dictionary values must be strings.") - end - elseif strcmp(namei, "PreferredAggregationTemporality") - temporality = validatestring(valuei, ["Cumulative", "Delta"]); - end + obj.(namei) = valuei; end - - obj = obj@opentelemetry.sdk.metrics.MetricExporter(... - "libmexclass.opentelemetry.exporters.OtlpGrpcMetricExporterProxy", ... - endpoint, usessl, certificatepath, certificatestring, ... - timeout_millis, headerkeys, headervalues, temporality); + end + + function obj = set.Endpoint(obj, ep) + if ~(isStringScalar(ep) || (ischar(ep) && isrow(ep))) + error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:EndpointNotScalarText", "Endpoint must be a scalar string."); + end + ep = string(ep); + obj.Proxy.setEndpoint(ep); + obj.Endpoint = ep; + end + + function obj = set.UseCredentials(obj, uc) + if ~((islogical(uc) || isnumeric(uc)) && isscalar(uc)) + error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:UseCredentialsNotScalarLogical", "UseCredentials must be a scalar logical.") + end + uc = logical(uc); + obj.Proxy.setUseCredentials(uc); + obj.UseCredentials = uc; + end - % populate immutable properties - [defaultendpoint, defaultcertpath, defaultcertstring, defaultmillis, defaulttemporality] = ... - getDefaultOptionValues(obj); - if endpoint == "" % not specified, use default value - obj.Endpoint = defaultendpoint; - else - obj.Endpoint = endpoint; - end - obj.UseCredentials = usessl; - if certificatepath == "" % not specified, use default value - obj.CertificatePath = defaultcertpath; - else - obj.CertificatePath = certificatepath; + function obj = set.CertificatePath(obj, certpath) + if ~(isStringScalar(certpath) || (ischar(certpath) && isrow(certpath))) + error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:CertificatePathNotScalarText", "CertificatePath must be a scalar string."); end - if certificatestring == "" % not specified, use default value - obj.CertificateString = defaultcertstring; - else - obj.CertificateString = certificatestring; + certpath = string(certpath); + obj.Proxy.setCertificatePath(certpath); + obj.CertificatePath = certpath; + end + + function obj = set.CertificateString(obj, certstr) + if ~(isStringScalar(certstr) || (ischar(certstr) && isrow(certstr))) + error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:CertificateStringNotScalarText", "CertificateString must be a scalar string."); end - if timeout_millis < 0 % not specified, use default value - obj.Timeout = milliseconds(defaultmillis); - else - obj.Timeout = timeout; + certstr = string(certstr); + obj.Proxy.setCertificateString(certstr); + obj.CertificateString = certstr; + end + + function obj = set.Timeout(obj, timeout) + if ~(isduration(timeout) && isscalar(timeout)) + error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:TimeoutNotScalarDuration", "Timeout must be a scalar duration."); end - if isempty(headerkeys) % not specified, return empty dictionary - obj.HttpHeaders = dictionary(headerkeys, headervalues); - else - obj.HttpHeaders = httpheaders; + obj.Proxy.setTimeout(milliseconds(timeout)); + obj.Timeout = timeout; + end + + function obj = set.HttpHeaders(obj, httpheaders) + if ~isa(httpheaders, "dictionary") + error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:HttpHeadersNotDictionary", "HttpHeaders input must be a dictionary."); end - if temporality == "" - obj.PreferredAggregationTemporality = defaulttemporality; - else - obj.PreferredAggregationTemporality = temporality; + headerkeys = keys(httpheaders); + headervalues = values(httpheaders); + if ~isstring(headervalues) + error("opentelemetry:exporters:otlp:OtlpGrpcMetricExporter:HttpHeadersNonStringValues", "HttpHeaders dictionary values must be strings.") end + obj.Proxy.setHttpHeaders(headerkeys, headervalues); + obj.HttpHeaders = httpheaders; + end + + function obj = set.PreferredAggregationTemporality(obj, temporality) + temporality = validatestring(temporality, ["cumulative", "delta"]); + obj.Proxy.setTemporality(temporality); + obj.PreferredAggregationTemporality = temporality; end end end diff --git a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpMetricExporter.m b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpMetricExporter.m index 10e1f3b..490c468 100644 --- a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpMetricExporter.m +++ b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpMetricExporter.m @@ -5,14 +5,14 @@ % Copyright 2023 The MathWorks, Inc. - properties (SetAccess=immutable) - Endpoint (1,1) string % Export destination - Format (1,1) string % Data format, JSON or binary - JsonBytesMapping (1,1) string % What to convert JSON bytes to - UseJsonName (1,1) logical % Whether to use JSON name of protobuf field to set the key of JSON - Timeout (1,1) duration % Maximum time above which exports will abort - HttpHeaders (1,1) dictionary % Additional HTTP headers - PreferredAggregationTemporality (1,1) string % Preferred Aggregation Temporality + properties + Endpoint (1,1) string = "http://localhost:4318/v1/metrics" % Export destination + Format (1,1) string = "JSON" % Data format, JSON or binary + JsonBytesMapping (1,1) string = "hexId" % What to convert JSON bytes to + UseJsonName (1,1) logical = false % Whether to use JSON name of protobuf field to set the key of JSON + Timeout (1,1) duration = seconds(10) % Maximum time above which exports will abort + HttpHeaders (1,1) dictionary = dictionary(string.empty, string.empty) % Additional HTTP headers + PreferredAggregationTemporality (1,1) string = "cumulative" % Preferred Aggregation Temporality end methods @@ -47,98 +47,73 @@ optionvalues end + obj = obj@opentelemetry.sdk.metrics.MetricExporter(... + "libmexclass.opentelemetry.exporters.OtlpHttpMetricExporterProxy"); + validnames = ["Endpoint", "Format", "JsonBytesMapping", ... "UseJsonName", "Timeout", "HttpHeaders", "PreferredAggregationTemporality"]; - % set default values to empty or negative - endpoint = ""; - dataformat = ""; - jsonbytesmapping = ""; - usejsonname = false; - timeout_millis = -1; - headerkeys = string.empty(); - headervalues = string.empty(); - temporality = ""; for i = 1:length(optionnames) namei = validatestring(optionnames{i}, validnames); valuei = optionvalues{i}; - if strcmp(namei, "Endpoint") - if ~(isStringScalar(valuei) || (ischar(valuei) && isrow(valuei))) - error("opentelemetry:exporters:otlp:OtlpHttpMetricExporter:EndpointNotScalarText", "Endpoint must be a scalar string."); - end - endpoint = string(valuei); - elseif strcmp(namei, "Format") - dataformat = validatestring(valuei, ["JSON", "binary"]); - elseif strcmp(namei, "JsonBytesMapping") - jsonbytesmapping = validatestring(valuei, ["hex", "hexId", "base64"]); - elseif strcmp(namei, "UseJsonName") - if ~((islogical(valuei) || isnumeric(valuei)) && isscalar(valuei)) - error("opentelemetry:exporters:otlp:OtlpHttpMetricExporter:UseJsonNameNotScalarLogical", "UseJsonName must be a scalar logical.") - end - usejsonname = logical(valuei); - elseif strcmp(namei, "Timeout") - if ~(isduration(valuei) && isscalar(valuei)) - error("opentelemetry:exporters:otlp:OtlpHttpMetricExporter:TimeoutNotScalarDuration", "Timeout must be a scalar duration."); - end - timeout = valuei; - timeout_millis = milliseconds(timeout); - elseif strcmp(namei, "HttpHeaders") - if ~isa(valuei, "dictionary") - error("opentelemetry:exporters:otlp:OtlpHttpMetricExporter:HttpHeadersNotDictionary", "HttpHeaders input must be a dictionary."); - end - httpheaders = valuei; - headerkeys = keys(valuei); - headervalues = values(valuei); - if ~isstring(headervalues) - error("opentelemetry:exporters:otlp:OtlpHttpMetricExporter:HttpHeadersNonStringValues", "HttpHeaders dictionary values must be strings.") - end - elseif strcmp(namei, "PreferredAggregationTemporality") - temporality = validatestring(valuei, ["Cumulative", "Delta"]); - end - + obj.(namei) = valuei; end - - obj = obj@opentelemetry.sdk.metrics.MetricExporter(... - "libmexclass.opentelemetry.exporters.OtlpHttpMetricExporterProxy", ... - endpoint, dataformat, jsonbytesmapping, usejsonname, ... - timeout_millis, headerkeys, headervalues, temporality); + end - % populate immutable properties - if endpoint == "" || dataformat == "" || jsonbytesmapping == "" || ... - timeout_millis < 0 - [defaultendpoint, defaultformat, defaultmapping, defaultmillis, defaulttemporality] = ... - getDefaultOptionValues(obj); - end - if endpoint == "" % not specified, use default value - obj.Endpoint = defaultendpoint; - else - obj.Endpoint = endpoint; + function obj = set.Endpoint(obj, ep) + if ~(isStringScalar(ep) || (ischar(ep) && isrow(ep))) + error("opentelemetry:exporters:otlp:OtlpHttpMetricExporter:EndpointNotScalarText", "Endpoint must be a scalar string."); end - if dataformat == "" % not specified, use default value - obj.Format = defaultformat; - else - obj.Format = dataformat; - end - if jsonbytesmapping == "" % not specified, use default value - obj.JsonBytesMapping = defaultmapping; - else - obj.JsonBytesMapping = jsonbytesmapping; + ep = string(ep); + obj.Proxy.setEndpoint(ep); + obj.Endpoint = ep; + end + + function obj = set.Format(obj, newformat) + newformat = validatestring(newformat, ["JSON", "binary"]); + obj.Proxy.setFormat(newformat); + obj.Format = newformat; + end + + function obj = set.JsonBytesMapping(obj, jbm) + jbm = validatestring(jbm, ["hex", "hexId", "base64"]); + obj.Proxy.setJsonBytesMapping(jbm); + obj.JsonBytesMapping = jbm; + end + + function obj = set.UseJsonName(obj, ujn) + if ~((islogical(ujn) || isnumeric(ujn)) && isscalar(ujn)) + error("opentelemetry:exporters:otlp:OtlpHttpMetricExporter:UseJsonNameNotScalarLogical", "UseJsonName must be a scalar logical.") end - obj.UseJsonName = usejsonname; - if timeout_millis < 0 % not specified, use default value - obj.Timeout = milliseconds(defaultmillis); - else - obj.Timeout = timeout; + ujn = logical(ujn); + obj.Proxy.setUseJsonName(ujn); + obj.UseJsonName = ujn; + end + + function obj = set.Timeout(obj, timeout) + if ~(isduration(timeout) && isscalar(timeout)) + error("opentelemetry:exporters:otlp:OtlpHttpMetricExporter:TimeoutNotScalarDuration", "Timeout must be a scalar duration."); end - if isempty(headerkeys) % not specified, return empty dictionary - obj.HttpHeaders = dictionary(headerkeys, headervalues); - else - obj.HttpHeaders = httpheaders; + obj.Proxy.setTimeout(milliseconds(timeout)); + obj.Timeout = timeout; + end + + function obj = set.HttpHeaders(obj, httpheaders) + if ~isa(httpheaders, "dictionary") + error("opentelemetry:exporters:otlp:OtlpHttpMetricExporter:HttpHeadersNotDictionary", "HttpHeaders input must be a dictionary."); end - if temporality == "" - obj.PreferredAggregationTemporality = defaulttemporality; - else - obj.PreferredAggregationTemporality = temporality; + headerkeys = keys(httpheaders); + headervalues = values(httpheaders); + if ~isstring(headervalues) + error("opentelemetry:exporters:otlp:OtlpHttpMetricExporter:HttpHeadersNonStringValues", "HttpHeaders dictionary values must be strings.") end + obj.Proxy.setHttpHeaders(headerkeys, headervalues); + obj.HttpHeaders = httpheaders; + end + + function obj = set.PreferredAggregationTemporality(obj, temporality) + temporality = validatestring(temporality, ["cumulative", "delta"]); + obj.Proxy.setTemporality(temporality); + obj.PreferredAggregationTemporality = temporality; end end end diff --git a/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcMetricExporterProxy.h b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcMetricExporterProxy.h index e372672..4d7afd0 100644 --- a/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcMetricExporterProxy.h +++ b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcMetricExporterProxy.h @@ -17,14 +17,26 @@ namespace libmexclass::opentelemetry::exporters { class OtlpGrpcMetricExporterProxy: public libmexclass::opentelemetry::sdk::MetricExporterProxy { public: OtlpGrpcMetricExporterProxy(otlp_exporter::OtlpGrpcMetricExporterOptions options) : CppOptions(options) { - REGISTER_METHOD(OtlpGrpcMetricExporterProxy, getDefaultOptionValues); + REGISTER_METHOD(OtlpGrpcMetricExporterProxy, setEndpoint); + REGISTER_METHOD(OtlpGrpcMetricExporterProxy, setUseCredentials); + REGISTER_METHOD(OtlpGrpcMetricExporterProxy, setCertificatePath); + REGISTER_METHOD(OtlpGrpcMetricExporterProxy, setCertificateString); + REGISTER_METHOD(OtlpGrpcMetricExporterProxy, setTimeout); + REGISTER_METHOD(OtlpGrpcMetricExporterProxy, setHttpHeaders); + REGISTER_METHOD(OtlpGrpcMetricExporterProxy, setTemporality); } static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments); std::unique_ptr getInstance() override; - void getDefaultOptionValues(libmexclass::proxy::method::Context& context); + void setEndpoint(libmexclass::proxy::method::Context& context); + void setUseCredentials(libmexclass::proxy::method::Context& context); + void setCertificatePath(libmexclass::proxy::method::Context& context); + void setCertificateString(libmexclass::proxy::method::Context& context); + void setTimeout(libmexclass::proxy::method::Context& context); + void setHttpHeaders(libmexclass::proxy::method::Context& context); + void setTemporality(libmexclass::proxy::method::Context& context); private: otlp_exporter::OtlpGrpcMetricExporterOptions CppOptions; diff --git a/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpMetricExporterProxy.h b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpMetricExporterProxy.h index 2dc929b..e5b920d 100644 --- a/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpMetricExporterProxy.h +++ b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpMetricExporterProxy.h @@ -17,14 +17,26 @@ namespace libmexclass::opentelemetry::exporters { class OtlpHttpMetricExporterProxy: public libmexclass::opentelemetry::sdk::MetricExporterProxy { public: OtlpHttpMetricExporterProxy(otlp_exporter::OtlpHttpMetricExporterOptions options) : CppOptions(options) { - REGISTER_METHOD(OtlpHttpMetricExporterProxy, getDefaultOptionValues); + REGISTER_METHOD(OtlpHttpMetricExporterProxy, setEndpoint); + REGISTER_METHOD(OtlpHttpMetricExporterProxy, setFormat); + REGISTER_METHOD(OtlpHttpMetricExporterProxy, setJsonBytesMapping); + REGISTER_METHOD(OtlpHttpMetricExporterProxy, setUseJsonName); + REGISTER_METHOD(OtlpHttpMetricExporterProxy, setTimeout); + REGISTER_METHOD(OtlpHttpMetricExporterProxy, setHttpHeaders); + REGISTER_METHOD(OtlpHttpMetricExporterProxy, setTemporality); } static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments); std::unique_ptr getInstance() override; - void getDefaultOptionValues(libmexclass::proxy::method::Context& context); + void setEndpoint(libmexclass::proxy::method::Context& context); + void setFormat(libmexclass::proxy::method::Context& context); + void setJsonBytesMapping(libmexclass::proxy::method::Context& context); + void setUseJsonName(libmexclass::proxy::method::Context& context); + void setTimeout(libmexclass::proxy::method::Context& context); + void setHttpHeaders(libmexclass::proxy::method::Context& context); + void setTemporality(libmexclass::proxy::method::Context& context); private: otlp_exporter::OtlpHttpMetricExporterOptions CppOptions; diff --git a/exporters/otlp/src/OtlpGrpcMetricExporterProxy.cpp b/exporters/otlp/src/OtlpGrpcMetricExporterProxy.cpp index 0e0749a..6febee9 100644 --- a/exporters/otlp/src/OtlpGrpcMetricExporterProxy.cpp +++ b/exporters/otlp/src/OtlpGrpcMetricExporterProxy.cpp @@ -10,51 +10,7 @@ namespace otlp_exporter = opentelemetry::exporter::otlp; namespace libmexclass::opentelemetry::exporters { libmexclass::proxy::MakeResult OtlpGrpcMetricExporterProxy::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { - matlab::data::StringArray endpoint_mda = constructor_arguments[0]; - std::string endpoint = static_cast(endpoint_mda[0]); - matlab::data::TypedArray use_ssl_mda = constructor_arguments[1]; - bool use_ssl = use_ssl_mda[0]; - matlab::data::StringArray certpath_mda = constructor_arguments[2]; - std::string certpath = static_cast(certpath_mda[0]); - matlab::data::StringArray certstring_mda = constructor_arguments[3]; - std::string certstring = static_cast(certstring_mda[0]); - matlab::data::TypedArray timeout_mda = constructor_arguments[4]; - double timeout = timeout_mda[0]; - matlab::data::Array header_mda = constructor_arguments[5]; - size_t nheaders = header_mda.getNumberOfElements(); - matlab::data::StringArray headernames_mda = constructor_arguments[5]; - matlab::data::StringArray headervalues_mda = constructor_arguments[6]; - matlab::data::StringArray temporality_mda = constructor_arguments[7]; - std::string temporality = static_cast(temporality_mda[0]); - otlp_exporter::OtlpGrpcMetricExporterOptions options; - if (!endpoint.empty()) { - options.endpoint = endpoint; - } - // use_ssl - options.use_ssl_credentials = use_ssl; - if (!certpath.empty()) { - options.ssl_credentials_cacert_path = certpath; - } - if (!certstring.empty()) { - options.ssl_credentials_cacert_as_string = certstring; - } - // timeout - if (timeout >= 0) { - options.timeout = std::chrono::milliseconds(static_cast(timeout)); - } - // http headers - for (size_t i = 0; i < nheaders; ++i) { - options.metadata.insert(std::pair{static_cast(headernames_mda[i]), - static_cast(headervalues_mda[i])}); - } - // Preferred Aggregation Temporality - if (temporality.compare("Cumulative") == 0) { - options.aggregation_temporality = otlp_exporter::PreferredAggregationTemporality::kCumulative; - } - else if (temporality.compare("Delta") == 0) { - options.aggregation_temporality = otlp_exporter::PreferredAggregationTemporality::kDelta; - } return std::make_shared(options); } @@ -62,34 +18,66 @@ std::unique_ptr OtlpGrpcMetricExporterProxy::get return otlp_exporter::OtlpGrpcMetricExporterFactory::Create(CppOptions); } -void OtlpGrpcMetricExporterProxy::getDefaultOptionValues(libmexclass::proxy::method::Context& context) { - otlp_exporter::OtlpGrpcMetricExporterOptions options; - matlab::data::ArrayFactory factory; - auto endpoint_mda = factory.createScalar(options.endpoint); - auto certpath_mda = factory.createScalar(options.ssl_credentials_cacert_path); - auto certstring_mda = factory.createScalar(options.ssl_credentials_cacert_as_string); - auto timeout_millis = std::chrono::duration_cast(options.timeout); - auto timeout_mda = factory.createScalar(static_cast(timeout_millis.count())); - std::string aggregation_temporality; - // Preferred Aggregation Temporality - if (options.aggregation_temporality == otlp_exporter::PreferredAggregationTemporality::kCumulative){ - aggregation_temporality = "Cumulative"; +void OtlpGrpcMetricExporterProxy::setEndpoint(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray endpoint_mda = context.inputs[0]; + std::string endpoint = static_cast(endpoint_mda[0]); + + if (!endpoint.empty()) { + CppOptions.endpoint = endpoint; } - else if (options.aggregation_temporality == otlp_exporter::PreferredAggregationTemporality::kDelta){ - aggregation_temporality = "Delta"; +} + + +void OtlpGrpcMetricExporterProxy::setUseCredentials(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray use_credentials_mda = context.inputs[0]; + CppOptions.use_ssl_credentials = use_credentials_mda[0]; +} + +void OtlpGrpcMetricExporterProxy::setCertificatePath(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray certpath_mda = context.inputs[0]; + std::string certpath = static_cast(certpath_mda[0]); + + if (!certpath.empty()) { + CppOptions.ssl_credentials_cacert_path = certpath; + } +} + +void OtlpGrpcMetricExporterProxy::setCertificateString(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray certstr_mda = context.inputs[0]; + std::string certstr = static_cast(certstr_mda[0]); + + if (!certstr.empty()) { + CppOptions.ssl_credentials_cacert_as_string = certstr; + } +} + +void OtlpGrpcMetricExporterProxy::setTimeout(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray timeout_mda = context.inputs[0]; + double timeout = timeout_mda[0]; + + if (timeout >= 0) { + CppOptions.timeout = std::chrono::milliseconds(static_cast(timeout)); } - else if (options.aggregation_temporality == otlp_exporter::PreferredAggregationTemporality::kLowMemory){ - aggregation_temporality = "LowMemory"; +} + +void OtlpGrpcMetricExporterProxy::setHttpHeaders(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray headernames_mda = context.inputs[0]; + matlab::data::StringArray headervalues_mda = context.inputs[1]; + size_t nheaders = headernames_mda.getNumberOfElements(); + for (size_t i = 0; i < nheaders; ++i) { + CppOptions.metadata.insert(std::pair{static_cast(headernames_mda[i]), + static_cast(headervalues_mda[i])}); } - else { - aggregation_temporality = "Unspecified"; +} + +void OtlpGrpcMetricExporterProxy::setTemporality(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray temporality_mda = context.inputs[0]; + std::string temporality = static_cast(temporality_mda[0]); + + if (temporality.compare("cumulative") == 0) { + CppOptions.aggregation_temporality = otlp_exporter::PreferredAggregationTemporality::kCumulative; + } else if (temporality.compare("delta") == 0) { + CppOptions.aggregation_temporality = otlp_exporter::PreferredAggregationTemporality::kDelta; } - auto aggregation_temporality_mda = factory.createScalar(aggregation_temporality); - context.outputs[0] = endpoint_mda; - context.outputs[1] = certpath_mda; - context.outputs[2] = certstring_mda; - context.outputs[3] = timeout_mda; - context.outputs[4] = aggregation_temporality_mda; - } } // namespace libmexclass::opentelemetry diff --git a/exporters/otlp/src/OtlpHttpMetricExporterProxy.cpp b/exporters/otlp/src/OtlpHttpMetricExporterProxy.cpp index e1636b5..a523dd2 100644 --- a/exporters/otlp/src/OtlpHttpMetricExporterProxy.cpp +++ b/exporters/otlp/src/OtlpHttpMetricExporterProxy.cpp @@ -10,111 +10,80 @@ namespace otlp_exporter = opentelemetry::exporter::otlp; namespace libmexclass::opentelemetry::exporters { libmexclass::proxy::MakeResult OtlpHttpMetricExporterProxy::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { - matlab::data::StringArray endpoint_mda = constructor_arguments[0]; + otlp_exporter::OtlpHttpMetricExporterOptions options; + return std::make_shared(options); +} + +std::unique_ptr OtlpHttpMetricExporterProxy::getInstance() { + return otlp_exporter::OtlpHttpMetricExporterFactory::Create(CppOptions); +} + +void OtlpHttpMetricExporterProxy::setEndpoint(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray endpoint_mda = context.inputs[0]; std::string endpoint = static_cast(endpoint_mda[0]); - matlab::data::StringArray dataformat_mda = constructor_arguments[1]; - std::string dataformat = static_cast(dataformat_mda[0]); - matlab::data::StringArray json_bytes_mapping_mda = constructor_arguments[2]; - std::string json_bytes_mapping = static_cast(json_bytes_mapping_mda[0]); - matlab::data::TypedArray use_json_name_mda = constructor_arguments[3]; - bool use_json_name = use_json_name_mda[0]; - matlab::data::TypedArray timeout_mda = constructor_arguments[4]; - double timeout = timeout_mda[0]; - matlab::data::Array header_mda = constructor_arguments[5]; - size_t nheaders = header_mda.getNumberOfElements(); - matlab::data::StringArray headernames_mda = constructor_arguments[5]; - matlab::data::StringArray headervalues_mda = constructor_arguments[6]; - matlab::data::StringArray temporality_mda = constructor_arguments[7]; - std::string temporality = static_cast(temporality_mda[0]); - otlp_exporter::OtlpHttpMetricExporterOptions options; - if (!endpoint.empty()) { - options.url = endpoint; - } - // TODO: store the relationship between strings and enums in an associative container - // dataformat + CppOptions.url = endpoint; + } +} + +void OtlpHttpMetricExporterProxy::setFormat(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray dataformat_mda = context.inputs[0]; + std::string dataformat = static_cast(dataformat_mda[0]); + if (dataformat.compare("JSON") == 0) { - options.content_type = otlp_exporter::HttpRequestContentType::kJson; + CppOptions.content_type = otlp_exporter::HttpRequestContentType::kJson; } else if (dataformat.compare("binary") == 0) { - options.content_type = otlp_exporter::HttpRequestContentType::kBinary; + CppOptions.content_type = otlp_exporter::HttpRequestContentType::kBinary; } - // json_bytes_mapping +} + +void OtlpHttpMetricExporterProxy::setJsonBytesMapping(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray json_bytes_mapping_mda = context.inputs[0]; + std::string json_bytes_mapping = static_cast(json_bytes_mapping_mda[0]); + if (json_bytes_mapping.compare("hex") == 0) { - options.json_bytes_mapping = otlp_exporter::JsonBytesMappingKind::kHex; + CppOptions.json_bytes_mapping = otlp_exporter::JsonBytesMappingKind::kHex; } else if (json_bytes_mapping.compare("hexId") == 0) { - options.json_bytes_mapping = otlp_exporter::JsonBytesMappingKind::kHexId; + CppOptions.json_bytes_mapping = otlp_exporter::JsonBytesMappingKind::kHexId; } else if (json_bytes_mapping.compare("base64") == 0) { - options.json_bytes_mapping = otlp_exporter::JsonBytesMappingKind::kBase64; + CppOptions.json_bytes_mapping = otlp_exporter::JsonBytesMappingKind::kBase64; } - // use_json_name - options.use_json_name = use_json_name; - // timeout +} + +void OtlpHttpMetricExporterProxy::setUseJsonName(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray use_json_name_mda = context.inputs[0]; + CppOptions.use_json_name = use_json_name_mda[0]; +} + +void OtlpHttpMetricExporterProxy::setTimeout(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray timeout_mda = context.inputs[0]; + double timeout = timeout_mda[0]; + if (timeout >= 0) { - options.timeout = std::chrono::milliseconds(static_cast(timeout)); + CppOptions.timeout = std::chrono::milliseconds(static_cast(timeout)); } - // http headers +} + +void OtlpHttpMetricExporterProxy::setHttpHeaders(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray headernames_mda = context.inputs[0]; + matlab::data::StringArray headervalues_mda = context.inputs[1]; + size_t nheaders = headernames_mda.getNumberOfElements(); for (size_t i = 0; i < nheaders; ++i) { - options.http_headers.insert(std::pair{static_cast(headernames_mda[i]), + CppOptions.http_headers.insert(std::pair{static_cast(headernames_mda[i]), static_cast(headervalues_mda[i])}); } - // Preferred Aggregation Temporality - if (temporality.compare("Cumulative") == 0) { - options.aggregation_temporality = otlp_exporter::PreferredAggregationTemporality::kCumulative; - } - else if (temporality.compare("Delta") == 0) { - options.aggregation_temporality = otlp_exporter::PreferredAggregationTemporality::kDelta; - } - return std::make_shared(options); } -std::unique_ptr OtlpHttpMetricExporterProxy::getInstance() { - return otlp_exporter::OtlpHttpMetricExporterFactory::Create(CppOptions); -} +void OtlpHttpMetricExporterProxy::setTemporality(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray temporality_mda = context.inputs[0]; + std::string temporality = static_cast(temporality_mda[0]); -void OtlpHttpMetricExporterProxy::getDefaultOptionValues(libmexclass::proxy::method::Context& context) { - otlp_exporter::OtlpHttpMetricExporterOptions options; - matlab::data::ArrayFactory factory; - auto endpoint_mda = factory.createScalar(options.url); - std::string dataformat, json_bytes_mapping, aggregation_temporality; - // dataformat - if (options.content_type == otlp_exporter::HttpRequestContentType::kJson) { - dataformat = "JSON"; - } else { - dataformat = "binary"; - } - // json_bytes_mapping - if (options.json_bytes_mapping == otlp_exporter::JsonBytesMappingKind::kHex) { - json_bytes_mapping = "hex"; - } else if (options.json_bytes_mapping == otlp_exporter::JsonBytesMappingKind::kHexId) { - json_bytes_mapping = "hexId"; - } else { // kBase64 - json_bytes_mapping = "base64"; + if (temporality.compare("cumulative") == 0) { + CppOptions.aggregation_temporality = otlp_exporter::PreferredAggregationTemporality::kCumulative; + } else if (temporality.compare("delta") == 0) { + CppOptions.aggregation_temporality = otlp_exporter::PreferredAggregationTemporality::kDelta; } - // Preferred Aggregation Temporality - if (options.aggregation_temporality == otlp_exporter::PreferredAggregationTemporality::kCumulative){ - aggregation_temporality = "Cumulative"; - } - else if (options.aggregation_temporality == otlp_exporter::PreferredAggregationTemporality::kDelta){ - aggregation_temporality = "Delta"; - } - else if (options.aggregation_temporality == otlp_exporter::PreferredAggregationTemporality::kLowMemory){ - aggregation_temporality = "LowMemory"; - } - else { - aggregation_temporality = "Unspecified"; - } - - auto dataformat_mda = factory.createScalar(dataformat); - auto json_bytes_mapping_mda = factory.createScalar(json_bytes_mapping); - auto timeout_millis = std::chrono::duration_cast(options.timeout); - auto timeout_mda = factory.createScalar(static_cast(timeout_millis.count())); - auto aggregation_temporality_mda = factory.createScalar(aggregation_temporality); - context.outputs[0] = endpoint_mda; - context.outputs[1] = dataformat_mda; - context.outputs[2] = json_bytes_mapping_mda; - context.outputs[3] = timeout_mda; - context.outputs[4] = aggregation_temporality_mda; } } // namespace libmexclass::opentelemetry diff --git a/sdk/metrics/+opentelemetry/+sdk/+metrics/MetricExporter.m b/sdk/metrics/+opentelemetry/+sdk/+metrics/MetricExporter.m index c1fbd49..389d035 100644 --- a/sdk/metrics/+opentelemetry/+sdk/+metrics/MetricExporter.m +++ b/sdk/metrics/+opentelemetry/+sdk/+metrics/MetricExporter.m @@ -3,7 +3,9 @@ % Copyright 2023 The MathWorks, Inc. - properties (Access=?opentelemetry.sdk.metrics.PeriodicExportingMetricReader) + properties (Access={?opentelemetry.sdk.metrics.PeriodicExportingMetricReader, ... + ?opentelemetry.exporters.otlp.OtlpHttpMetricExporter, ... + ?opentelemetry.exporters.otlp.OtlpGrpcMetricExporter}) Proxy % Proxy object to interface C++ code end diff --git a/sdk/metrics/+opentelemetry/+sdk/+metrics/PeriodicExportingMetricReader.m b/sdk/metrics/+opentelemetry/+sdk/+metrics/PeriodicExportingMetricReader.m index d79c7bb..1b7aefc 100644 --- a/sdk/metrics/+opentelemetry/+sdk/+metrics/PeriodicExportingMetricReader.m +++ b/sdk/metrics/+opentelemetry/+sdk/+metrics/PeriodicExportingMetricReader.m @@ -9,8 +9,11 @@ properties (SetAccess=immutable) MetricExporter % Metric exporter object responsible for exporting telemetry data to an OpenTelemetry Collector or a compatible backend. - Interval (1,1) duration - Timeout (1,1) duration + end + + properties + Interval (1,1) duration = minutes(1) + Timeout (1,1) duration = seconds(30) end methods %(Access=?opentelemetry.sdk.metrics.MeterProvider) @@ -24,46 +27,36 @@ optionnames (1,:) {mustBeTextScalar} optionvalues end + + obj.Proxy = libmexclass.proxy.Proxy("Name", "libmexclass.opentelemetry.sdk.PeriodicExportingMetricReaderProxy" , ... + "ConstructorArguments", {metricexporter.Proxy.ID}); + obj.MetricExporter = metricexporter; + validnames = ["Interval", "Timeout"]; - % set default values - intervalmillis = -1; - timeoutmillis = -1; for i = 1:length(optionnames) namei = validatestring(optionnames{i}, validnames); valuei = optionvalues{i}; - if strcmp(namei, "Interval") - if ~isduration(valuei) || ~isscalar(valuei) || valuei <= 0 || ... - round(valuei) ~= valuei - error("opentelemetry:sdk:metrics::PeriodicExportingMetricReader::InvalidInterval", ... - "Interval must be a positive duration integer."); - end - intervalmillis = milliseconds(valuei); - elseif strcmp(namei, "Timeout") - if ~isduration(valuei) || ~isscalar(valuei) || valuei <= 0 - error("opentelemetry:sdk:metrics:PeriodicExportingMetricReader:InvalidTimeout", ... - "Timeout must be a positive duration scalar."); - end - timeoutmillis = milliseconds(valuei); - end + obj.(namei) = valuei; end - - obj.MetricExporter = metricexporter; - obj.Proxy = libmexclass.proxy.Proxy("Name", "libmexclass.opentelemetry.sdk.PeriodicExportingMetricReaderProxy" , ... - "ConstructorArguments", {metricexporter.Proxy.ID, intervalmillis, timeoutmillis}); + end - [defaultinterval, defaulttimeout] = obj.Proxy.getDefaultOptionValues(); - if intervalmillis <= 0 - obj.Interval = milliseconds(defaultinterval); - else - obj.Interval = milliseconds(intervalmillis); + function obj = set.Interval(obj, interval) + if ~isduration(interval) || ~isscalar(interval) || interval <= 0 || ... + round(interval) ~= interval + error("opentelemetry:sdk:metrics:PeriodicExportingMetricReader:InvalidInterval", ... + "Interval must be a positive duration integer."); end - if timeoutmillis <= 0 - obj.Timeout = milliseconds(defaulttimeout); - else - obj.Timeout = milliseconds(timeoutmillis); - end - + obj.Proxy.setInterval(milliseconds(interval)); %#ok + obj.Interval = interval; + end + function obj = set.Timeout(obj, timeout) + if ~isduration(timeout) || ~isscalar(timeout) || timeout <= 0 + error("opentelemetry:sdk:metrics:PeriodicExportingMetricReader:InvalidTimeout", ... + "Timeout must be a positive duration scalar."); + end + obj.Proxy.setTimeout(milliseconds(timeout)); %#ok + obj.Timeout = timeout; end end end diff --git a/sdk/metrics/include/opentelemetry-matlab/sdk/metrics/PeriodicExportingMetricReaderProxy.h b/sdk/metrics/include/opentelemetry-matlab/sdk/metrics/PeriodicExportingMetricReaderProxy.h index 21af4dd..bf66ffe 100644 --- a/sdk/metrics/include/opentelemetry-matlab/sdk/metrics/PeriodicExportingMetricReaderProxy.h +++ b/sdk/metrics/include/opentelemetry-matlab/sdk/metrics/PeriodicExportingMetricReaderProxy.h @@ -15,13 +15,15 @@ namespace metric_sdk = opentelemetry::sdk::metrics; namespace libmexclass::opentelemetry::sdk { class PeriodicExportingMetricReaderProxy : public libmexclass::proxy::Proxy { public: - PeriodicExportingMetricReaderProxy(std::shared_ptr exporter, double interval, double timeout); + PeriodicExportingMetricReaderProxy(std::shared_ptr exporter); static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments); std::unique_ptr getInstance(); - void getDefaultOptionValues(libmexclass::proxy::method::Context& context); + void setInterval(libmexclass::proxy::method::Context& context); + + void setTimeout(libmexclass::proxy::method::Context& context); private: metric_sdk::PeriodicExportingMetricReaderOptions CppOptions; diff --git a/sdk/metrics/src/PeriodicExportingMetricReaderProxy.cpp b/sdk/metrics/src/PeriodicExportingMetricReaderProxy.cpp index 4f0f3e6..c4b05ed 100644 --- a/sdk/metrics/src/PeriodicExportingMetricReaderProxy.cpp +++ b/sdk/metrics/src/PeriodicExportingMetricReaderProxy.cpp @@ -10,18 +10,10 @@ namespace libmexclass::opentelemetry::sdk { -PeriodicExportingMetricReaderProxy::PeriodicExportingMetricReaderProxy(std::shared_ptr exporter, - double interval, double timeout) +PeriodicExportingMetricReaderProxy::PeriodicExportingMetricReaderProxy(std::shared_ptr exporter) : MetricExporter(exporter) { - - if (interval > 0) { - CppOptions.export_interval_millis = std::chrono::milliseconds(static_cast(interval)); - } - if (timeout > 0) { - CppOptions.export_timeout_millis = std::chrono::milliseconds(static_cast(timeout)); - } - - REGISTER_METHOD(PeriodicExportingMetricReaderProxy, getDefaultOptionValues); + REGISTER_METHOD(PeriodicExportingMetricReaderProxy, setInterval); + REGISTER_METHOD(PeriodicExportingMetricReaderProxy, setTimeout); } @@ -30,12 +22,8 @@ libmexclass::proxy::MakeResult PeriodicExportingMetricReaderProxy::make(const li libmexclass::proxy::ID exporterid = exporterid_mda[0]; std::shared_ptr exporter = std::static_pointer_cast( libmexclass::proxy::ProxyManager::getProxy(exporterid)); - matlab::data::TypedArray interval_mda = constructor_arguments[1]; - double interval = interval_mda[0]; - matlab::data::TypedArray timeout_mda = constructor_arguments[2]; - double timeout = timeout_mda[0]; - return std::make_shared(exporter, interval, timeout); + return std::make_shared(exporter); } @@ -45,15 +33,14 @@ std::unique_ptr PeriodicExportingMetricReaderProxy::ge } -void PeriodicExportingMetricReaderProxy::getDefaultOptionValues(libmexclass::proxy::method::Context& context){ - metric_sdk::PeriodicExportingMetricReaderOptions options; - matlab::data::ArrayFactory factory; - auto interval_mda = factory.createScalar(static_cast( - options.export_interval_millis.count())); - auto timeout_mda = factory.createScalar(static_cast( - options.export_timeout_millis.count())); - context.outputs[0] = interval_mda; - context.outputs[1] = timeout_mda; +void PeriodicExportingMetricReaderProxy::setInterval(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray interval_mda = context.inputs[0]; + CppOptions.export_interval_millis = std::chrono::milliseconds(static_cast(interval_mda[0])); +} + +void PeriodicExportingMetricReaderProxy::setTimeout(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray timeout_mda = context.inputs[0]; + CppOptions.export_timeout_millis = std::chrono::milliseconds(static_cast(timeout_mda[0])); } } // namespace libmexclass::opentelemetry diff --git a/test/tmetrics.m b/test/tmetrics.m index 9cecbf9..d869a9e 100644 --- a/test/tmetrics.m +++ b/test/tmetrics.m @@ -42,13 +42,13 @@ function testDefaultExporter(testCase) verifyEqual(testCase, string(class(exporter)), "opentelemetry.exporters.otlp.OtlpHttpMetricExporter"); verifyEqual(testCase, string(exporter.Endpoint), "http://localhost:4318/v1/metrics"); verifyEqual(testCase, exporter.Timeout, seconds(10)); - verifyEqual(testCase, string(exporter.PreferredAggregationTemporality), "Cumulative"); + verifyEqual(testCase, string(exporter.PreferredAggregationTemporality), "cumulative"); end function testExporterBasic(testCase) timeout = seconds(5); - temporality = "Delta"; + temporality = "delta"; exporter = opentelemetry.exporters.otlp.OtlpHttpMetricExporter("Timeout", timeout, ... "PreferredAggregationTemporality", temporality); verifyEqual(testCase, exporter.Timeout, timeout); @@ -60,7 +60,7 @@ function testDefaultReader(testCase) reader = opentelemetry.sdk.metrics.PeriodicExportingMetricReader(); verifyEqual(testCase, string(class(reader.MetricExporter)), ... "opentelemetry.exporters.otlp.OtlpHttpMetricExporter"); - verifyEqual(testCase, reader.Interval, seconds(60)); + verifyEqual(testCase, reader.Interval, minutes(1)); verifyEqual(testCase, reader.Timeout, seconds(30)); end @@ -81,9 +81,9 @@ function testAddMetricReader(testCase) metername = "foo"; countername = "bar"; exporter1 = opentelemetry.exporters.otlp.OtlpHttpMetricExporter(... - "PreferredAggregationTemporality", "Delta"); + "PreferredAggregationTemporality", "delta"); exporter2 = opentelemetry.exporters.otlp.OtlpHttpMetricExporter(... - "PreferredAggregationTemporality", "Delta"); + "PreferredAggregationTemporality", "delta"); reader1 = opentelemetry.sdk.metrics.PeriodicExportingMetricReader(exporter1, ..., "Interval", seconds(2), "Timeout", seconds(1)); reader2 = opentelemetry.sdk.metrics.PeriodicExportingMetricReader(exporter2, ...,