diff --git a/CMakeLists.txt b/CMakeLists.txt index 402c979..4ad886b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,8 +187,8 @@ if(WITH_OTLP_GRPC) endif() endif() -set(TRACE_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/trace/include) -set(METRICS_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/metrics/include) +set(TRACE_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/trace/include) +set(METRICS_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/metrics/include) set(CONTEXT_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/context/include) set(BAGGAGE_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/baggage/include) set(COMMON_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/common/include) @@ -409,7 +409,6 @@ set(OTLP_GRPC_EXPORTER_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcSpanExporter.m ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcMetricExporter.m) - set(OTLP_EXPORTERS_DIR +opentelemetry/+exporters/+otlp) install(DIRECTORY ${TRACE_API_MATLAB_SOURCES} DESTINATION .) diff --git a/api/trace/+opentelemetry/+trace/Span.m b/api/trace/+opentelemetry/+trace/Span.m index 5698920..3ac71b7 100644 --- a/api/trace/+opentelemetry/+trace/Span.m +++ b/api/trace/+opentelemetry/+trace/Span.m @@ -132,7 +132,11 @@ function setStatus(obj, status, description) % new status is not valid, ignore return end - description = opentelemetry.common.mustBeScalarString(description); + if nargin < 3 + description = ""; + else + description = opentelemetry.common.mustBeScalarString(description); + end obj.Proxy.setStatus(status, description); end @@ -181,8 +185,7 @@ function setStatus(obj, status, description) function attrs = processAttributes(attrsin) import opentelemetry.trace.Span.processAttribute - nin = length(attrsin); - if nin == 1 && isa(attrsin{1}, "dictionary") + if isscalar(attrsin) && isa(attrsin{1}, "dictionary") % dictionary case attrtbl = entries(attrsin{1}); nattr = height(attrtbl); @@ -203,6 +206,7 @@ function setStatus(obj, status, description) attrs = attrs(:); else % NV pairs + nin = length(attrsin); if rem(nin,2) ~= 0 % Mismatched name-value pairs. Ignore all attributes. attrs = cell(1,0); diff --git a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcSpanExporter.m b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcSpanExporter.m index 068755a..bcf2bb6 100644 --- a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcSpanExporter.m +++ b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcSpanExporter.m @@ -5,13 +5,13 @@ % 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 + 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 end methods @@ -43,93 +43,73 @@ optionvalues end + obj = obj@opentelemetry.sdk.trace.SpanExporter(... + "libmexclass.opentelemetry.exporters.OtlpGrpcSpanExporterProxy"); + validnames = ["Endpoint", "UseCredentials ", "CertificatePath", ... "CertificateString", "Timeout", "HttpHeaders"]; - % set default values to empty or negative - endpoint = ""; - usessl = false; - certificatepath = ""; - certificatestring = ""; - timeout_millis = -1; - headerkeys = string.empty(); - headervalues = string.empty(); 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:OtlpGrpcSpanExporter: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:OtlpGrpcSpanExporter: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:OtlpGrpcSpanExporter: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:OtlpGrpcSpanExporter:CertificateStringNotScalarText", "CertificateString must be a scalar string."); - end - certificatestring = string(valuei); - elseif strcmp(namei, "Timeout") - if ~(isduration(valuei) && isscalar(valuei)) - error("opentelemetry:exporters:otlp:OtlpGrpcSpanExporter:TimeoutNotScalarDuration", "Timeout must be a scalar duration."); - end - timeout = valuei; - timeout_millis = milliseconds(timeout); - else % HttpHeaders - if ~isa(valuei, "dictionary") - error("opentelemetry:exporters:otlp:OtlpGrpcSpanExporter:HttpHeadersNotDictionary", "HttpHeaders input must be a dictionary."); - end - httpheaders = valuei; - headerkeys = keys(valuei); - headervalues = values(valuei); - if ~isstring(headervalues) - error("opentelemetry:exporters:otlp:OtlpGrpcSpanExporter:HttpHeadersNonStringValues", "HttpHeaders dictionary values must be strings.") - end - end + obj.(namei) = valuei; end - - obj = obj@opentelemetry.sdk.trace.SpanExporter(... - "libmexclass.opentelemetry.exporters.OtlpGrpcSpanExporterProxy", ... - endpoint, usessl, certificatepath, certificatestring, ... - timeout_millis, headerkeys, headervalues); + end - % populate immutable properties - [defaultendpoint, defaultcertpath, defaultcertstring, defaultmillis] = ... - 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.Endpoint(obj, ep) + if ~(isStringScalar(ep) || (ischar(ep) && isrow(ep))) + error("opentelemetry:exporters:otlp:OtlpGrpcSpanExporter:EndpointNotScalarText", "Endpoint must be a scalar string."); end - if certificatestring == "" % not specified, use default value - obj.CertificateString = defaultcertstring; - else - obj.CertificateString = certificatestring; + 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:OtlpGrpcSpanExporter:UseCredentialsNotScalarLogical", "UseCredentials must be a scalar logical.") + end + uc = logical(uc); + obj.Proxy.setUseCredentials(uc); + obj.UseCredentials = uc; + end + + function obj = set.CertificatePath(obj, certpath) + if ~(isStringScalar(certpath) || (ischar(certpath) && isrow(certpath))) + error("opentelemetry:exporters:otlp:OtlpGrpcSpanExporter:CertificatePathNotScalarText", "CertificatePath must be a scalar string."); end - if timeout_millis < 0 % not specified, use default value - obj.Timeout = milliseconds(defaultmillis); - else - obj.Timeout = timeout; + 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:OtlpGrpcSpanExporter:CertificateStringNotScalarText", "CertificateString must be a scalar string."); + end + 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:OtlpGrpcSpanExporter:TimeoutNotScalarDuration", "Timeout must be a scalar duration."); + end + obj.Proxy.setTimeout(milliseconds(timeout)); + obj.Timeout = timeout; + end + + function obj = set.HttpHeaders(obj, httpheaders) + if ~isa(httpheaders, "dictionary") + error("opentelemetry:exporters:otlp:OtlpGrpcSpanExporter:HttpHeadersNotDictionary", "HttpHeaders input must be a dictionary."); end - if isempty(headerkeys) % not specified, return empty dictionary - obj.HttpHeaders = dictionary(headerkeys, headervalues); - else - obj.HttpHeaders = httpheaders; + headerkeys = keys(httpheaders); + headervalues = values(httpheaders); + if ~isstring(headervalues) + error("opentelemetry:exporters:otlp:OtlpGrpcSpanExporter:HttpHeadersNonStringValues", "HttpHeaders dictionary values must be strings.") end - + obj.Proxy.setHttpHeaders(headerkeys, headervalues); + obj.HttpHeaders = httpheaders; end end end diff --git a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpSpanExporter.m b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpSpanExporter.m index 8bed109..cf79565 100644 --- a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpSpanExporter.m +++ b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpSpanExporter.m @@ -5,13 +5,13 @@ % 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 + properties + Endpoint (1,1) string = "http://localhost:4318/v1/traces" % 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 end methods @@ -43,89 +43,67 @@ optionvalues end + obj = obj@opentelemetry.sdk.trace.SpanExporter(... + "libmexclass.opentelemetry.exporters.OtlpHttpSpanExporterProxy"); + validnames = ["Endpoint", "Format", "JsonBytesMapping", ... "UseJsonName", "Timeout", "HttpHeaders"]; - % set default values to empty or negative - endpoint = ""; - dataformat = ""; - jsonbytesmapping = ""; - usejsonname = false; - timeout_millis = -1; - headerkeys = string.empty(); - headervalues = string.empty(); 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:OtlpHttpSpanExporter: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:OtlpHttpSpanExporter:UseJsonNameNotScalarLogical", "UseJsonName must be a scalar logical.") - end - usejsonname = logical(valuei); - elseif strcmp(namei, "Timeout") - if ~(isduration(valuei) && isscalar(valuei)) - error("opentelemetry:exporters:otlp:OtlpHttpSpanExporter:TimeoutNotScalarDuration", "Timeout must be a scalar duration."); - end - timeout = valuei; - timeout_millis = milliseconds(timeout); - else % HttpHeaders - if ~isa(valuei, "dictionary") - error("opentelemetry:exporters:otlp:OtlpHttpSpanExporter:HttpHeadersNotDictionary", "HttpHeaders input must be a dictionary."); - end - httpheaders = valuei; - headerkeys = keys(valuei); - headervalues = values(valuei); - if ~isstring(headervalues) - error("opentelemetry:exporters:otlp:OtlpHttpSpanExporter:HttpHeadersNonStringValues", "HttpHeaders dictionary values must be strings.") - end - end + obj.(namei) = valuei; end - - obj = obj@opentelemetry.sdk.trace.SpanExporter(... - "libmexclass.opentelemetry.exporters.OtlpHttpSpanExporterProxy", ... - endpoint, dataformat, jsonbytesmapping, usejsonname, ... - timeout_millis, headerkeys, headervalues); + end - % populate immutable properties - if endpoint == "" || dataformat == "" || jsonbytesmapping == "" || ... - timeout_millis < 0 - [defaultendpoint, defaultformat, defaultmapping, defaultmillis] = ... - getDefaultOptionValues(obj); + function obj = set.Endpoint(obj, ep) + if ~(isStringScalar(ep) || (ischar(ep) && isrow(ep))) + error("opentelemetry:exporters:otlp:OtlpHttpSpanExporter:EndpointNotScalarText", "Endpoint must be a scalar string."); end - if endpoint == "" % not specified, use default value - obj.Endpoint = defaultendpoint; - else - obj.Endpoint = endpoint; - end - if dataformat == "" % not specified, use default value - obj.Format = defaultformat; - else - obj.Format = dataformat; + 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:OtlpHttpSpanExporter:UseJsonNameNotScalarLogical", "UseJsonName must be a scalar logical.") end - if jsonbytesmapping == "" % not specified, use default value - obj.JsonBytesMapping = defaultmapping; - else - obj.JsonBytesMapping = jsonbytesmapping; + 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:OtlpHttpSpanExporter:TimeoutNotScalarDuration", "Timeout must be a scalar duration."); end - obj.UseJsonName = usejsonname; - if timeout_millis < 0 % not specified, use default value - obj.Timeout = milliseconds(defaultmillis); - else - obj.Timeout = timeout; + obj.Proxy.setTimeout(milliseconds(timeout)); + obj.Timeout = timeout; + end + + function obj = set.HttpHeaders(obj, httpheaders) + if ~isa(httpheaders, "dictionary") + error("opentelemetry:exporters:otlp:OtlpHttpSpanExporter:HttpHeadersNotDictionary", "HttpHeaders input must be a dictionary."); end - if isempty(headerkeys) % not specified, return empty dictionary - obj.HttpHeaders = dictionary(headerkeys, headervalues); - else - obj.HttpHeaders = httpheaders; + headerkeys = keys(httpheaders); + headervalues = values(httpheaders); + if ~isstring(headervalues) + error("opentelemetry:exporters:otlp:OtlpHttpSpanExporter:HttpHeadersNonStringValues", "HttpHeaders dictionary values must be strings.") end + obj.Proxy.setHttpHeaders(headerkeys, headervalues); + obj.HttpHeaders = httpheaders; end end end diff --git a/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcSpanExporterProxy.h b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcSpanExporterProxy.h index 716c941..bcc4879 100644 --- a/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcSpanExporterProxy.h +++ b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcSpanExporterProxy.h @@ -17,14 +17,24 @@ namespace libmexclass::opentelemetry::exporters { class OtlpGrpcSpanExporterProxy: public libmexclass::opentelemetry::sdk::SpanExporterProxy { public: OtlpGrpcSpanExporterProxy(otlp_exporter::OtlpGrpcExporterOptions options) : CppOptions(options) { - REGISTER_METHOD(OtlpGrpcSpanExporterProxy, getDefaultOptionValues); + REGISTER_METHOD(OtlpGrpcSpanExporterProxy, setEndpoint); + REGISTER_METHOD(OtlpGrpcSpanExporterProxy, setUseCredentials); + REGISTER_METHOD(OtlpGrpcSpanExporterProxy, setCertificatePath); + REGISTER_METHOD(OtlpGrpcSpanExporterProxy, setCertificateString); + REGISTER_METHOD(OtlpGrpcSpanExporterProxy, setTimeout); + REGISTER_METHOD(OtlpGrpcSpanExporterProxy, setHttpHeaders); } 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); private: otlp_exporter::OtlpGrpcExporterOptions CppOptions; diff --git a/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpSpanExporterProxy.h b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpSpanExporterProxy.h index f843303..f735635 100644 --- a/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpSpanExporterProxy.h +++ b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpSpanExporterProxy.h @@ -17,7 +17,12 @@ namespace libmexclass::opentelemetry::exporters { class OtlpHttpSpanExporterProxy: public libmexclass::opentelemetry::sdk::SpanExporterProxy { public: OtlpHttpSpanExporterProxy(otlp_exporter::OtlpHttpExporterOptions options) : CppOptions(options) { - REGISTER_METHOD(OtlpHttpSpanExporterProxy, getDefaultOptionValues); + REGISTER_METHOD(OtlpHttpSpanExporterProxy, setEndpoint); + REGISTER_METHOD(OtlpHttpSpanExporterProxy, setFormat); + REGISTER_METHOD(OtlpHttpSpanExporterProxy, setJsonBytesMapping); + REGISTER_METHOD(OtlpHttpSpanExporterProxy, setUseJsonName); + REGISTER_METHOD(OtlpHttpSpanExporterProxy, setTimeout); + REGISTER_METHOD(OtlpHttpSpanExporterProxy, setHttpHeaders); } static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments); @@ -26,6 +31,18 @@ class OtlpHttpSpanExporterProxy: public libmexclass::opentelemetry::sdk::SpanExp 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); + private: otlp_exporter::OtlpHttpExporterOptions CppOptions; }; diff --git a/exporters/otlp/src/OtlpGrpcSpanExporterProxy.cpp b/exporters/otlp/src/OtlpGrpcSpanExporterProxy.cpp index 1ae66a0..c4e4770 100644 --- a/exporters/otlp/src/OtlpGrpcSpanExporterProxy.cpp +++ b/exporters/otlp/src/OtlpGrpcSpanExporterProxy.cpp @@ -10,60 +10,63 @@ namespace otlp_exporter = opentelemetry::exporter::otlp; namespace libmexclass::opentelemetry::exporters { libmexclass::proxy::MakeResult OtlpGrpcSpanExporterProxy::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { - matlab::data::StringArray endpoint_mda = constructor_arguments[0]; + otlp_exporter::OtlpGrpcExporterOptions options; + return std::make_shared(options); +} + +std::unique_ptr OtlpGrpcSpanExporterProxy::getInstance() { + return otlp_exporter::OtlpGrpcExporterFactory::Create(CppOptions); +} + +void OtlpGrpcSpanExporterProxy::setEndpoint(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray endpoint_mda = context.inputs[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]; - otlp_exporter::OtlpGrpcExporterOptions options; if (!endpoint.empty()) { - options.endpoint = endpoint; - } - // use_ssl - options.use_ssl_credentials = use_ssl; + CppOptions.endpoint = endpoint; + } +} + + +void OtlpGrpcSpanExporterProxy::setUseCredentials(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray use_credentials_mda = context.inputs[0]; + CppOptions.use_ssl_credentials = use_credentials_mda[0]; +} + +void OtlpGrpcSpanExporterProxy::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()) { - 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)); + CppOptions.ssl_credentials_cacert_path = certpath; } - // 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])}); +} + +void OtlpGrpcSpanExporterProxy::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; } - return std::make_shared(options); } -std::unique_ptr OtlpGrpcSpanExporterProxy::getInstance() { - return otlp_exporter::OtlpGrpcExporterFactory::Create(CppOptions); +void OtlpGrpcSpanExporterProxy::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)); + } } -void OtlpGrpcSpanExporterProxy::getDefaultOptionValues(libmexclass::proxy::method::Context& context) { - otlp_exporter::OtlpGrpcExporterOptions 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())); - context.outputs[0] = endpoint_mda; - context.outputs[1] = certpath_mda; - context.outputs[2] = certstring_mda; - context.outputs[3] = timeout_mda; +void OtlpGrpcSpanExporterProxy::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])}); + } } } // namespace libmexclass::opentelemetry diff --git a/exporters/otlp/src/OtlpHttpSpanExporterProxy.cpp b/exporters/otlp/src/OtlpHttpSpanExporterProxy.cpp index f8c42ce..ebcdaa5 100644 --- a/exporters/otlp/src/OtlpHttpSpanExporterProxy.cpp +++ b/exporters/otlp/src/OtlpHttpSpanExporterProxy.cpp @@ -10,84 +10,68 @@ namespace otlp_exporter = opentelemetry::exporter::otlp; namespace libmexclass::opentelemetry::exporters { libmexclass::proxy::MakeResult OtlpHttpSpanExporterProxy::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { - matlab::data::StringArray endpoint_mda = constructor_arguments[0]; + otlp_exporter::OtlpHttpExporterOptions options; + return std::make_shared(options); +} + +std::unique_ptr OtlpHttpSpanExporterProxy::getInstance() { + return otlp_exporter::OtlpHttpExporterFactory::Create(CppOptions); +} + +void OtlpHttpSpanExporterProxy::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]; - otlp_exporter::OtlpHttpExporterOptions options; if (!endpoint.empty()) { - options.url = endpoint; + CppOptions.url = endpoint; } - // TODO: store the relationship between strings and enums in an associative container - // dataformat +} + +void OtlpHttpSpanExporterProxy::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 OtlpHttpSpanExporterProxy::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 - if (timeout >= 0) { - options.timeout = std::chrono::milliseconds(static_cast(timeout)); - } - // http headers - for (size_t i = 0; i < nheaders; ++i) { - options.http_headers.insert(std::pair{static_cast(headernames_mda[i]), - static_cast(headervalues_mda[i])}); - } - return std::make_shared(options); } -std::unique_ptr OtlpHttpSpanExporterProxy::getInstance() { - return otlp_exporter::OtlpHttpExporterFactory::Create(CppOptions); +void OtlpHttpSpanExporterProxy::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 OtlpHttpSpanExporterProxy::getDefaultOptionValues(libmexclass::proxy::method::Context& context) { - otlp_exporter::OtlpHttpExporterOptions options; - matlab::data::ArrayFactory factory; - auto endpoint_mda = factory.createScalar(options.url); - std::string dataformat, json_bytes_mapping; - // dataformat - if (options.content_type == otlp_exporter::HttpRequestContentType::kJson) { - dataformat = "JSON"; - } else { - dataformat = "binary"; +void OtlpHttpSpanExporterProxy::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)); } - // 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"; +} + +void OtlpHttpSpanExporterProxy::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.http_headers.insert(std::pair{static_cast(headernames_mda[i]), + static_cast(headervalues_mda[i])}); } - 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())); - context.outputs[0] = endpoint_mda; - context.outputs[1] = dataformat_mda; - context.outputs[2] = json_bytes_mapping_mda; - context.outputs[3] = timeout_mda; } } // namespace libmexclass::opentelemetry diff --git a/sdk/trace/+opentelemetry/+sdk/+trace/BatchSpanProcessor.m b/sdk/trace/+opentelemetry/+sdk/+trace/BatchSpanProcessor.m index 84b05bc..3101dd3 100644 --- a/sdk/trace/+opentelemetry/+sdk/+trace/BatchSpanProcessor.m +++ b/sdk/trace/+opentelemetry/+sdk/+trace/BatchSpanProcessor.m @@ -3,10 +3,10 @@ % Copyright 2023 The MathWorks, Inc. - properties (SetAccess=immutable) - MaximumQueueSize (1,1) double % Maximum queue size. After queue size is reached, spans are dropped. - ScheduledDelay (1,1) duration % Time interval between span exports - MaximumExportBatchSize (1,1) double % Maximum batch size to export. + properties + MaximumQueueSize (1,1) double = 2048 % Maximum queue size. After queue size is reached, spans are dropped. + ScheduledDelay (1,1) duration = seconds(5) % Time interval between span exports + MaximumExportBatchSize (1,1) double = 512 % Maximum batch size to export. end methods @@ -44,61 +44,46 @@ optionvalues end + obj = obj@opentelemetry.sdk.trace.SpanProcessor(spanexporter, ... + "libmexclass.opentelemetry.sdk.BatchSpanProcessorProxy"); + validnames = ["MaximumQueueSize", "ScheduledDelay", "MaximumExportBatchSize"]; - % set default values to negative - qsize = -1; - delaymillis = -1; - batchsize = -1; for i = 1:length(optionnames) namei = validatestring(optionnames{i}, validnames); valuei = optionvalues{i}; - if strcmp(namei, "MaximumQueueSize") - if ~isnumeric(valuei) || ~isscalar(valuei) || valuei <= 0 || ... - round(valuei) ~= valuei - error("opentelemetry:sdk:trace:BatchSpanProcessor:InvalidMaxQueueSize", ... - "MaximumQueueSize must be a scalar positive integer."); - end - qsize = double(valuei); - elseif strcmp(namei, "ScheduledDelay") - if ~isduration(valuei) || ~isscalar(valuei) || valuei <= 0 - error("opentelemetry:sdk:trace:BatchSpanProcessor:InvalidScheduledDelay", ... - "ScheduledDelay must be a positive duration scalar."); - end - delay = valuei; - delaymillis = milliseconds(valuei); - else % "MaximumExportBatchSize" - if ~isnumeric(valuei) || ~isscalar(valuei) || valuei <= 0 || ... - round(valuei) ~= valuei - error("opentelemetry:sdk:trace:BatchSpanProcessor:InvalidMaxExportBatchSize", ... - "MaximumExportBatchSize must be a scalar positive integer."); - end - batchsize = double(valuei); - end + obj.(namei) = valuei; end - - obj = obj@opentelemetry.sdk.trace.SpanProcessor(spanexporter, ... - "libmexclass.opentelemetry.sdk.BatchSpanProcessorProxy", ... - qsize, delaymillis, batchsize); + end - % populate immutable properties - if qsize < 0 || delaymillis < 0 || batchsize < 0 - [defaultqsize, defaultmillis, defaultbatchsize] = obj.Proxy.getDefaultOptionValues(); - end - if qsize < 0 % not specified, use default value - obj.MaximumQueueSize = defaultqsize; - else - obj.MaximumQueueSize = qsize; + function obj = set.MaximumQueueSize(obj, maxqsz) + if ~isnumeric(maxqsz) || ~isscalar(maxqsz) || maxqsz <= 0 || ... + round(maxqsz) ~= maxqsz + error("opentelemetry:sdk:trace:BatchSpanProcessor:InvalidMaxQueueSize", ... + "MaximumQueueSize must be a scalar positive integer."); end - if delaymillis < 0 % not specified, use default value - obj.ScheduledDelay = milliseconds(defaultmillis); - else - obj.ScheduledDelay = delay; + maxqsz = double(maxqsz); + obj.Proxy.setMaximumQueueSize(maxqsz); + obj.MaximumQueueSize = maxqsz; + end + + function obj = set.ScheduledDelay(obj, delay) + if ~isduration(delay) || ~isscalar(delay) || delay <= 0 + error("opentelemetry:sdk:trace:BatchSpanProcessor:InvalidScheduledDelay", ... + "ScheduledDelay must be a positive duration scalar."); end - if batchsize < 0 % not specified, use default value - obj.MaximumExportBatchSize = defaultbatchsize; - else - obj.MaximumExportBatchSize = batchsize; + obj.Proxy.setScheduledDelay(milliseconds(delay)); + obj.ScheduledDelay = delay; + end + + function obj = set.MaximumExportBatchSize(obj, maxbatch) + if ~isnumeric(maxbatch) || ~isscalar(maxbatch) || maxbatch <= 0 || ... + round(maxbatch) ~= maxbatch + error("opentelemetry:sdk:trace:BatchSpanProcessor:InvalidMaxExportBatchSize", ... + "MaximumExportBatchSize must be a scalar positive integer."); end + maxbatch = double(maxbatch); + obj.Proxy.setMaximumExportBatchSize(maxbatch); + obj.MaximumExportBatchSize = maxbatch; end end end diff --git a/sdk/trace/+opentelemetry/+sdk/+trace/Sampler.m b/sdk/trace/+opentelemetry/+sdk/+trace/Sampler.m index ccaee21..397f310 100644 --- a/sdk/trace/+opentelemetry/+sdk/+trace/Sampler.m +++ b/sdk/trace/+opentelemetry/+sdk/+trace/Sampler.m @@ -4,15 +4,18 @@ % Copyright 2023 The MathWorks, Inc. properties (GetAccess={?opentelemetry.sdk.trace.TracerProvider,... - ?opentelemetry.sdk.trace.ParentBasedSampler}) + ?opentelemetry.sdk.trace.ParentBasedSampler, ... + ?opentelemetry.sdk.trace.TraceIdRatioBasedSampler}) Proxy % Proxy object to interface C++ code end methods (Access=protected) function obj = Sampler(proxyname, varargin) % Base class constructor - obj.Proxy = libmexclass.proxy.Proxy("Name", proxyname, ... - "ConstructorArguments", varargin); + if nargin > 0 + obj.Proxy = libmexclass.proxy.Proxy("Name", proxyname, ... + "ConstructorArguments", varargin); + end end end end diff --git a/sdk/trace/+opentelemetry/+sdk/+trace/SpanExporter.m b/sdk/trace/+opentelemetry/+sdk/+trace/SpanExporter.m index b2bcf4b..846e518 100644 --- a/sdk/trace/+opentelemetry/+sdk/+trace/SpanExporter.m +++ b/sdk/trace/+opentelemetry/+sdk/+trace/SpanExporter.m @@ -3,7 +3,9 @@ % Copyright 2023 The MathWorks, Inc. - properties (GetAccess=?opentelemetry.sdk.trace.SpanProcessor) + properties (GetAccess={?opentelemetry.sdk.trace.SpanProcessor, ... + ?opentelemetry.exporters.otlp.OtlpHttpSpanExporter, ... + ?opentelemetry.exporters.otlp.OtlpGrpcSpanExporter}) Proxy % Proxy object to interface C++ code end diff --git a/sdk/trace/+opentelemetry/+sdk/+trace/TraceIdRatioBasedSampler.m b/sdk/trace/+opentelemetry/+sdk/+trace/TraceIdRatioBasedSampler.m index 3b1f2ca..ab7310c 100644 --- a/sdk/trace/+opentelemetry/+sdk/+trace/TraceIdRatioBasedSampler.m +++ b/sdk/trace/+opentelemetry/+sdk/+trace/TraceIdRatioBasedSampler.m @@ -3,7 +3,7 @@ % Copyright 2023 The MathWorks, Inc. - properties (SetAccess=immutable) + properties Ratio (1,1) double % Sampling ratio between 0 and 1 end @@ -16,12 +16,18 @@ % See also OPENTELEMETRY.SDK.TRACE.ALWAYSONSAMPLER, % OPENTELEMETRY.SDK.TRACE.ALWAYSOFFSAMPLER, % OPENTELEMETRY.SDK.TRACE.PARENTBASEDSAMPLER - arguments - ratio (1,1) {mustBeNumeric, mustBeNonnegative, mustBeLessThanOrEqual(ratio,1)} + + obj.Ratio = ratio; + end + + function obj = set.Ratio(obj, ratio) + if ~(ratio >= 0 && ratio <= 1) + error("opentelemetry:sdk:trace:TraceIdRatioBasedSampler:InvalidRatio", ... + "Ratio must be a numeric scalar between 0 and 1."); end - ratio = double(ratio); - obj = obj@opentelemetry.sdk.trace.Sampler(... - "libmexclass.opentelemetry.sdk.TraceIdRatioBasedSamplerProxy", ratio); + obj.Proxy = libmexclass.proxy.Proxy("Name", ... + "libmexclass.opentelemetry.sdk.TraceIdRatioBasedSamplerProxy", ... + "ConstructorArguments", {ratio}); obj.Ratio = ratio; end end diff --git a/sdk/trace/include/opentelemetry-matlab/sdk/trace/BatchSpanProcessorProxy.h b/sdk/trace/include/opentelemetry-matlab/sdk/trace/BatchSpanProcessorProxy.h index 1afc8a0..00ecd21 100644 --- a/sdk/trace/include/opentelemetry-matlab/sdk/trace/BatchSpanProcessorProxy.h +++ b/sdk/trace/include/opentelemetry-matlab/sdk/trace/BatchSpanProcessorProxy.h @@ -15,14 +15,17 @@ namespace trace_sdk = opentelemetry::sdk::trace; namespace libmexclass::opentelemetry::sdk { class BatchSpanProcessorProxy : public SpanProcessorProxy { public: - BatchSpanProcessorProxy(std::shared_ptr exporter, double qsize, - double delay, double batchsize); + BatchSpanProcessorProxy(std::shared_ptr exporter); static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments); std::unique_ptr getInstance() override; - void getDefaultOptionValues(libmexclass::proxy::method::Context& context); + void setMaximumQueueSize(libmexclass::proxy::method::Context& context); + + void setScheduledDelay(libmexclass::proxy::method::Context& context); + + void setMaximumExportBatchSize(libmexclass::proxy::method::Context& context); private: trace_sdk::BatchSpanProcessorOptions CppOptions; diff --git a/sdk/trace/src/BatchSpanProcessorProxy.cpp b/sdk/trace/src/BatchSpanProcessorProxy.cpp index 4e369b6..75b0cb5 100644 --- a/sdk/trace/src/BatchSpanProcessorProxy.cpp +++ b/sdk/trace/src/BatchSpanProcessorProxy.cpp @@ -8,52 +8,46 @@ #include "opentelemetry/sdk/trace/batch_span_processor_factory.h" namespace libmexclass::opentelemetry::sdk { -BatchSpanProcessorProxy::BatchSpanProcessorProxy(std::shared_ptr exporter, - double qsize, double delay, double batchsize) +BatchSpanProcessorProxy::BatchSpanProcessorProxy(std::shared_ptr exporter) : SpanProcessorProxy(exporter) { - - if (qsize > 0) { - CppOptions.max_queue_size = static_cast(qsize); - } - if (delay > 0) { - CppOptions.schedule_delay_millis = std::chrono::milliseconds(static_cast(delay)); - } - if (batchsize > 0) { - CppOptions.max_export_batch_size = static_cast(batchsize); - } - REGISTER_METHOD(BatchSpanProcessorProxy, getDefaultOptionValues); + REGISTER_METHOD(BatchSpanProcessorProxy, setMaximumQueueSize); + REGISTER_METHOD(BatchSpanProcessorProxy, setScheduledDelay); + REGISTER_METHOD(BatchSpanProcessorProxy, setMaximumExportBatchSize); } libmexclass::proxy::MakeResult BatchSpanProcessorProxy::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { matlab::data::TypedArray exporterid_mda = constructor_arguments[0]; - libmexclass::proxy::ID exporterid = exporterid_mda[0]; + libmexclass::proxy::ID exporterid = exporterid_mda[0]; std::shared_ptr exporter = std::static_pointer_cast( libmexclass::proxy::ProxyManager::getProxy(exporterid)); - matlab::data::TypedArray qsize_mda = constructor_arguments[1]; - double qsize = qsize_mda[0]; - matlab::data::TypedArray delay_mda = constructor_arguments[2]; - double delay = delay_mda[0]; - matlab::data::TypedArray batchsize_mda = constructor_arguments[3]; - double batchsize = batchsize_mda[0]; - - return std::make_shared(exporter, qsize, delay, batchsize); + return std::make_shared(exporter); } std::unique_ptr BatchSpanProcessorProxy::getInstance() { return trace_sdk::BatchSpanProcessorFactory::Create(std::move(SpanExporter->getInstance()), CppOptions); } -void BatchSpanProcessorProxy::getDefaultOptionValues(libmexclass::proxy::method::Context& context) { - trace_sdk::BatchSpanProcessorOptions options; - matlab::data::ArrayFactory factory; - auto qsize_mda = factory.createScalar(static_cast( - options.max_queue_size)); - auto delay_mda = factory.createScalar(static_cast( - options.schedule_delay_millis.count())); - auto batchsize_mda = factory.createScalar(static_cast( - options.max_export_batch_size)); - context.outputs[0] = qsize_mda; - context.outputs[1] = delay_mda; - context.outputs[2] = batchsize_mda; +void BatchSpanProcessorProxy::setMaximumQueueSize(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray qsize_mda = context.inputs[0]; + double qsize = qsize_mda[0]; + if (qsize > 0) { + CppOptions.max_queue_size = static_cast(qsize); + } +} + +void BatchSpanProcessorProxy::setScheduledDelay(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray delay_mda = context.inputs[0]; + double delay = delay_mda[0]; + if (delay > 0) { + CppOptions.schedule_delay_millis = std::chrono::milliseconds(static_cast(delay)); + } +} + +void BatchSpanProcessorProxy::setMaximumExportBatchSize(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray batchsize_mda = context.inputs[0]; + double batchsize = batchsize_mda[0]; + if (batchsize > 0) { + CppOptions.max_export_batch_size = static_cast(batchsize); + } } } // namespace libmexclass::opentelemetry diff --git a/test/commonSetup.m b/test/commonSetup.m index 98d1fa8..6ac5190 100644 --- a/test/commonSetup.m +++ b/test/commonSetup.m @@ -1,8 +1,12 @@ -function commonSetup(testCase) +function commonSetup(testCase, configfile) % Setup function for tests % % Copyright 2023 The MathWorks, Inc. +if nargin < 2 + configfile = testCase.OtelConfigFile; +end + % start collector -system(testCase.Otelcol + " --config " + testCase.OtelConfigFile + '&'); +system(testCase.Otelcol + " --config " + configfile + '&'); pause(1); % give a little time for Collector to start up \ No newline at end of file diff --git a/test/commonTeardown.m b/test/commonTeardown.m index 0f037a2..e484629 100644 --- a/test/commonTeardown.m +++ b/test/commonTeardown.m @@ -10,8 +10,10 @@ function commonTeardown(testCase) if ispc system(testCase.ListPid("cmd") + " > " + testCase.PidFile); tbl = testCase.ReadPidList(testCase.PidFile); - pid = tbl.Var2(end-1); - system(testCase.Sigterm(pid)); + if height(tbl) > 1 + pid = tbl.Var2(end-1); + system(testCase.Sigterm(pid)); + end end if exist(testCase.JsonFile, "file") diff --git a/test/nondefault_endpoint.yml b/test/nondefault_endpoint.yml new file mode 100644 index 0000000..c3ec1d7 --- /dev/null +++ b/test/nondefault_endpoint.yml @@ -0,0 +1,33 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:9922 + http: + endpoint: 0.0.0.0:9921 + +processors: + +exporters: + file: + path: ./myoutput.json + +service: + + pipelines: + + logs: + receivers: [otlp] + processors: + exporters: [file] + + traces: + receivers: [otlp] + processors: + exporters: [file] + + metrics: + receivers: [otlp] + processors: + exporters: [file] + diff --git a/test/ttrace.m b/test/ttrace.m index 8b01773..02d2eb7 100644 --- a/test/ttrace.m +++ b/test/ttrace.m @@ -262,6 +262,28 @@ function testTime(testCase) "convertFrom", "posixtime") - endtime), seconds(2)); end + function testStatus(testCase) + % testStatus: setting status + tp = opentelemetry.sdk.trace.TracerProvider(); + tr = getTracer(tp, "foo"); + sp = startSpan(tr, "bar"); + setStatus(sp, "ok"); + endSpan(sp); + + sp1 = startSpan(tr, "quz"); + setStatus(sp1, "Error", "Something went wrong.") % with description + endSpan(sp1); + + % perform test comparisons + results = readJsonResults(testCase); + % status codes + % Unset: 0 + % Ok: 1 + % Error: 2 + verifyEqual(testCase, results{1}.resourceSpans.scopeSpans.spans.status.code, 1); + verifyEqual(testCase, results{2}.resourceSpans.scopeSpans.spans.status.code, 2); + end + function testAttributes(testCase) % testAttributes: specifying attributes when starting spans diff --git a/test/ttrace_sdk.m b/test/ttrace_sdk.m new file mode 100644 index 0000000..2040c55 --- /dev/null +++ b/test/ttrace_sdk.m @@ -0,0 +1,213 @@ +classdef ttrace_sdk < matlab.unittest.TestCase + % tests for tracing SDK (span processors, exporters, samplers, resource) + + % Copyright 2023 The MathWorks, Inc. + + properties + OtelConfigFile + JsonFile + PidFile + OtelcolName + Otelcol + ListPid + ReadPidList + ExtractPid + Sigint + Sigterm + end + + methods (TestClassSetup) + function setupOnce(testCase) + commonSetupOnce(testCase); + end + end + + methods (TestMethodTeardown) + function teardown(testCase) + commonTeardown(testCase); + end + end + + methods (Test) + function testNondefaultEndpoint(testCase) + % testNondefaultEndpoint: using an alternative endpoint + + testCase.assumeTrue(logical(exist("opentelemetry.exporters.otlp.OtlpHttpSpanExporter", "class")), ... + "Otlp HTTP exporter must be installed."); + + commonSetup(testCase, "nondefault_endpoint.yml") + + tracername = "foo"; + spanname = "bar"; + + exp = opentelemetry.exporters.otlp.OtlpHttpSpanExporter(... + "Endpoint", "http://localhost:9921/v1/traces"); + processor = opentelemetry.sdk.trace.SimpleSpanProcessor(exp); + tp = opentelemetry.sdk.trace.TracerProvider(processor); + tr = getTracer(tp, tracername); + sp = startSpan(tr, spanname); + pause(1); + endSpan(sp); + + % perform test comparisons + results = readJsonResults(testCase); + results = results{1}; + + % check span and tracer names + verifyEqual(testCase, string(results.resourceSpans.scopeSpans.spans.name), spanname); + verifyEqual(testCase, string(results.resourceSpans.scopeSpans.scope.name), tracername); + end + + function testNondefaultGrpcEndpoint(testCase) + % testNondefaultEndpoint: using an alternative endpoint + + testCase.assumeTrue(logical(exist("opentelemetry.exporters.otlp.OtlpGrpcSpanExporter", "class")), ... + "Otlp gRPC exporter must be installed."); + + commonSetup(testCase, "nondefault_endpoint.yml") + + tracername = "foo"; + spanname = "bar"; + + exp = opentelemetry.exporters.otlp.OtlpGrpcSpanExporter(... + "Endpoint", "http://localhost:9922"); + processor = opentelemetry.sdk.trace.SimpleSpanProcessor(exp); + tp = opentelemetry.sdk.trace.TracerProvider(processor); + tr = getTracer(tp, tracername); + sp = startSpan(tr, spanname); + pause(1); + endSpan(sp); + + % perform test comparisons + results = readJsonResults(testCase); + results = results{1}; + + % check span and tracer names + verifyEqual(testCase, string(results.resourceSpans.scopeSpans.spans.name), spanname); + verifyEqual(testCase, string(results.resourceSpans.scopeSpans.scope.name), tracername); + end + + function testAlwaysOffSampler(testCase) + % testAlwaysOffSampler: should not produce any spans + commonSetup(testCase) + + tp = opentelemetry.sdk.trace.TracerProvider( ... + opentelemetry.sdk.trace.SimpleSpanProcessor, ... + "Sampler", opentelemetry.sdk.trace.AlwaysOffSampler); + tr = getTracer(tp, "mytracer"); + sp = startSpan(tr, "myspan"); + pause(1); + endSpan(sp); + + % verify no spans are generated + results = readJsonResults(testCase); + verifyEmpty(testCase, results); + end + + function testAlwaysOnSampler(testCase) + % testAlwaysOnSampler: should produce all spans + commonSetup(testCase) + + tracername = "foo"; + spanname = "bar"; + + tp = opentelemetry.sdk.trace.TracerProvider( ... + opentelemetry.sdk.trace.SimpleSpanProcessor, ... + "Sampler", opentelemetry.sdk.trace.AlwaysOnSampler); + tr = getTracer(tp, tracername); + sp = startSpan(tr, spanname); + pause(1); + endSpan(sp); + + % perform test comparisons + results = readJsonResults(testCase); + results = results{1}; + + % check span and tracer names + verifyEqual(testCase, string(results.resourceSpans.scopeSpans.spans.name), spanname); + verifyEqual(testCase, string(results.resourceSpans.scopeSpans.scope.name), tracername); + end + + function testTraceIdRatioBasedSampler(testCase) + % testTraceIdRatioBasedSampler: filter spans based on a ratio + commonSetup(testCase) + + s = opentelemetry.sdk.trace.TraceIdRatioBasedSampler(0); % equivalent to always off + + tracername = "mytracer"; + offspan = "offspan"; + tp = opentelemetry.sdk.trace.TracerProvider( ... + opentelemetry.sdk.trace.SimpleSpanProcessor, "Sampler", s); + tr = getTracer(tp, tracername); + sp = startSpan(tr, offspan); + pause(1); + endSpan(sp); + + s.Ratio = 1; % equivalent to always on + onspan = "onspan"; + tp = opentelemetry.sdk.trace.TracerProvider( ... + opentelemetry.sdk.trace.SimpleSpanProcessor, "Sampler", s); + tr = getTracer(tp, tracername); + sp = startSpan(tr, onspan); + pause(1); + endSpan(sp); + + s.Ratio = 0.5; % filter half of the spans + sampledspan = "sampledspan"; + numspans = 10; + tp = opentelemetry.sdk.trace.TracerProvider( ... + opentelemetry.sdk.trace.SimpleSpanProcessor, "Sampler", s); + tr = getTracer(tp, tracername); + for i = 1:numspans + sp = startSpan(tr, sampledspan + i); + pause(1); + endSpan(sp); + end + + % perform test comparisons + results = readJsonResults(testCase); + n = length(results); + % total spans should be 1 span when ratio == 1, plus a number of + % spans between 0 and numspans when ratio == 0.5 + % Verifying 1 < total_spans < numspans+1. If this fails, there + % is still a chance nothing went wrong, because number of spans + % are non-deterministic when ratio == 0.5. When ratio == 0.5, + % it is still possible to get 0 or numspans spans. But that + % probability is small, so we fail the test to flag something + % may have gone wrong. + verifyGreaterThan(testCase, n, 1); + verifyLessThan(testCase, n, 1 + numspans); + verifyEqual(testCase, string(results{1}.resourceSpans.scopeSpans.spans.name), onspan); + for i = 2:n + verifySubstring(testCase, string(results{i}.resourceSpans.scopeSpans.spans.name), ... + sampledspan); + end + end + + function testCustomResource(testCase) + % testCustomResource: check custom resources are included in + % emitted spans + commonSetup(testCase) + + customkeys = ["foo" "bar"]; + customvalues = [1 5]; + tp = opentelemetry.sdk.trace.TracerProvider(opentelemetry.sdk.trace.SimpleSpanProcessor, ... + "Resource", dictionary(customkeys, customvalues)); + tr = getTracer(tp, "mytracer"); + sp = startSpan(tr, "myspan"); + pause(1); + endSpan(sp); + + % perform test comparisons + results = readJsonResults(testCase); + results = results{1}; + + resourcekeys = string({results.resourceSpans.resource.attributes.key}); + for i = length(customkeys) + idx = find(resourcekeys == customkeys(i)); + verifyNotEmpty(testCase, idx); + verifyEqual(testCase, results.resourceSpans.resource.attributes(idx).value.doubleValue, customvalues(i)); + end + end + end +end \ No newline at end of file