-
Notifications
You must be signed in to change notification settings - Fork 3
Initial MBT plugin draft #236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6014966
237a018
05b8948
3d56c45
e2de938
20aa4ef
df90996
a974c14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| classdef OpenTelemetryPluginService < matlab.buildtool.internal.services.plugins.BuildRunnerPluginService | ||
| % This class is unsupported and might change or be removed without notice | ||
| % in a future version. | ||
|
|
||
| % Copyright 2026 The MathWorks, Inc. | ||
|
|
||
| methods | ||
| function plugins = providePlugins(~, ~) | ||
| plugins = matlab.buildtool.plugins.BuildRunnerPlugin.empty(1,0); | ||
| if ~isMATLABReleaseOlderThan("R2026a") | ||
| plugins = matlab.buildtool.plugins.OpenTelemetryPlugin(); | ||
| end | ||
| end | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| classdef OpenTelemetryPlugin < matlab.buildtool.plugins.BuildRunnerPlugin | ||
|
|
||
| % Copyright 2026 The MathWorks, Inc. | ||
|
|
||
| methods(Access = protected) | ||
| function runBuild(plugin, pluginData) | ||
| % Configure by attaching to span if passed in via environment | ||
| % variable, and propagating baggage | ||
| configureOTel(); | ||
|
|
||
| tr = opentelemetry.trace.getTracer("buildtool"); | ||
| sp = tr.startSpan("buildtool"); | ||
| scope = makeCurrent(sp); %#ok<NASGU> | ||
|
|
||
| % Run build | ||
| [email protected](plugin, pluginData); | ||
|
|
||
| % Update status | ||
| if pluginData.BuildResult.Failed | ||
| sp.setStatus("Error", "Build completed, results not successful"); | ||
| else | ||
| sp.setStatus("Ok"); | ||
| end | ||
|
|
||
| % Results-based attributes | ||
| taskResults = pluginData.BuildResult.TaskResults; | ||
| successful = [taskResults([taskResults.Successful]).Name]; | ||
| failed = [taskResults([taskResults.Failed]).Name]; | ||
| skipped = [taskResults([taskResults.Skipped]).Name]; | ||
|
|
||
| sp.setAttributes( ... | ||
| "buildtool.tasks", numel(pluginData.BuildResult.TaskResults), ... | ||
| "buildtool.tasks.successful", successful, ... | ||
| "buildtool.tasks.failed", failed, ... | ||
| "buildtool.tasks.skipped", skipped, ... | ||
| "buildtool.build.successes", numel(successful), ... | ||
| "buildtool.build.failures", numel(failed), ... | ||
| "buildtool.build.skips", numel(skipped) ... | ||
| ); | ||
|
|
||
| % Update metrics | ||
| meter = opentelemetry.metrics.getMeter("buildtool"); | ||
| successes = meter.createCounter("buildtool.tasks.successful"); | ||
| failures = meter.createCounter("buildtool.tasks.failed"); | ||
| skips = meter.createCounter("buildtool.tasks.skipped"); | ||
| buildSuccesses = meter.createCounter("buildtool.build.successes"); | ||
| buildFailures = meter.createCounter("buildtool.build.failures"); | ||
|
|
||
| successes.add(numel(successful)); | ||
| failures.add(numel(failed)); | ||
| skips.add(numel(skipped)); | ||
| buildSuccesses.add(double(~pluginData.BuildResult.Failed)); | ||
| buildFailures.add(double(pluginData.BuildResult.Failed)); | ||
|
|
||
| cleanupOTel(sp); | ||
| end | ||
|
|
||
| function runTask(plugin, pluginData) | ||
| % TODO: | ||
| % - buildtool.task.outputs | ||
| % - buildtool.task.inputs | ||
|
|
||
| % Definitions | ||
| task = pluginData.TaskGraph.Tasks; | ||
| taskName = pluginData.Name; | ||
| taskDescription = task.Description; | ||
|
|
||
| % Attributes | ||
| otelAttributes = dictionary( ... | ||
| [ ... | ||
| "buildtool.task.name", ... | ||
| "buildtool.task.description", ... | ||
| ], ... | ||
| [ ... | ||
| taskName, ... | ||
| taskDescription ... | ||
| ] ... | ||
| ); | ||
|
|
||
| tr = opentelemetry.trace.getTracer(taskName); | ||
| sp = tr.startSpan(taskName, Attributes=otelAttributes); | ||
| scope = makeCurrent(sp); %#ok<NASGU> | ||
|
|
||
| % Run task | ||
| [email protected](plugin, pluginData); | ||
|
|
||
| % Set results-based attributes | ||
| resultAttributes = dictionary( ... | ||
| [ ... | ||
| "buildtool.task.successful", ... | ||
| "buildtool.task.failed", ... | ||
| "buildtool.task.skipped" ... | ||
| ], ... | ||
| [ ... | ||
| pluginData.TaskResults.Successful, ... | ||
| pluginData.TaskResults.Failed, ... | ||
| pluginData.TaskResults.Skipped ... | ||
| ] ... | ||
| ); | ||
| sp.setAttributes(resultAttributes); | ||
|
|
||
| % Update span status | ||
| if pluginData.TaskResults.Successful | ||
| sp.setStatus("Ok"); | ||
| else | ||
| sp.setStatus("Error", "Task completed, results not successful"); | ||
| end | ||
|
|
||
| sp.endSpan(); | ||
| end | ||
| end | ||
| end | ||
|
|
||
| % Use the same configuration as PADV | ||
| function extcontextscope = configureOTel() | ||
|
|
||
| % Skip configuration if NO_MBT_OTEL_CONFIG set | ||
| if (getenv("NO_MBT_OTEL_CONFIG")) | ||
| return; | ||
| end | ||
|
|
||
| % populate resource attributes | ||
| otelservicename = "buildtool"; | ||
| otelresource = dictionary("service.name", otelservicename); | ||
|
|
||
| % baggage propagation | ||
| otelbaggage = getenv("BAGGAGE"); | ||
| if ~isempty(otelbaggage) | ||
| otelbaggage = split(split(string(otelbaggage),','), "="); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know you took this code from Process Advisor. But as I am reading it, I realize it doesn't work if there is only one parameter in the baggage. Please add a reshape and it will work in all cases:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, I'll update that. |
||
| otelresource = insert(otelresource, otelbaggage(:,1), otelbaggage(:,2)); | ||
| end | ||
|
|
||
| % check for passed in external context | ||
| extcontextscope = []; | ||
| traceid = getenv("TRACE_ID"); | ||
| spanid = getenv("SPAN_ID"); | ||
| if ~isempty(traceid) && ~isempty(spanid) | ||
| spcontext = opentelemetry.trace.SpanContext(traceid, spanid); | ||
| extcontextscope = makeCurrent(spcontext); | ||
| end | ||
|
|
||
| % tracer provider | ||
| otelspexp = opentelemetry.exporters.otlp.OtlpGrpcSpanExporter; % use gRPC because Otel plugin for Jenkins only use gRPC | ||
| otelspproc = opentelemetry.sdk.trace.BatchSpanProcessor(otelspexp); | ||
| oteltp = opentelemetry.sdk.trace.TracerProvider(otelspproc, Resource=otelresource); | ||
| setTracerProvider(oteltp); | ||
|
|
||
| % meter provider | ||
| otelmexp = opentelemetry.exporters.otlp.OtlpGrpcMetricExporter; % use gRPC because Otel plugin for Jenkins only use gRPC | ||
| otelmread = opentelemetry.sdk.metrics.PeriodicExportingMetricReader(otelmexp); | ||
| otelmp = opentelemetry.sdk.metrics.MeterProvider(otelmread, Resource=otelresource); | ||
| setMeterProvider(otelmp); | ||
|
|
||
| % logger provider | ||
| otellgexp = opentelemetry.exporters.otlp.OtlpGrpcLogRecordExporter; % use gRPC because Otel plugin for Jenkins only use gRPC | ||
| otellgproc = opentelemetry.sdk.logs.BatchLogRecordProcessor(otellgexp); | ||
| otellp = opentelemetry.sdk.logs.LoggerProvider(otellgproc, Resource=otelresource); | ||
| setLoggerProvider(otellp); | ||
| end | ||
|
|
||
| % Use the same cleanup as PADV | ||
| function cleanupOTel(span) | ||
|
|
||
| % Skip cleanup if NO_MBT_OTEL_CONFIG set | ||
| if (getenv("NO_MBT_OTEL_CONFIG")) | ||
| return; | ||
| end | ||
|
|
||
| timeout = 5; | ||
|
|
||
| % end the input span before cleaning up | ||
| if nargin > 0 | ||
| endSpan(span); | ||
| end | ||
|
|
||
| % tracer provider | ||
| oteltp = opentelemetry.trace.Provider.getTracerProvider; | ||
| opentelemetry.sdk.common.Cleanup.forceFlush(oteltp, timeout); | ||
| opentelemetry.sdk.common.Cleanup.shutdown(oteltp); | ||
|
|
||
| % meter provider | ||
| otelmp = opentelemetry.metrics.Provider.getMeterProvider; | ||
| opentelemetry.sdk.common.Cleanup.forceFlush(otelmp, timeout); | ||
| opentelemetry.sdk.common.Cleanup.shutdown(otelmp); | ||
|
|
||
| % logger provider | ||
| otellp = opentelemetry.logs.Provider.getLoggerProvider; | ||
| opentelemetry.sdk.common.Cleanup.forceFlush(otellp, timeout); | ||
| opentelemetry.sdk.common.Cleanup.shutdown(otellp); | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this will initialize/configure OTel at the start of each build and then clean up everything after. Process Advisor only initializes before the first build of a MATLAB session. If you run multiple builds within a MATLAB session, OTel would only be initialized once. Do you want to do something like that, which is more efficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, that's an interesting question. Only setting up once is nice for improving performance, but if I understand correctly it allows for mutation of the environment in between runs right?
In order to ensure maximum reproducibility and determinism, having a consistent execution path through builds is quite powerful.
Overall, I think unless the performance costs prove to be expensive, I like reconfiguring each build to try and make the build process more hermetic. What are your thoughts there @duncanpo? Are there downsides to reconfiguration outside of the performance costs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is reasonable to configure/clean up every build if performance cost doesn't prove prohibitive. But I would recommend providing a way to skip config/clean up (such as using an environment variable). That way if a customer already sets up OTel for something else, there would be at least a way to opt out of build tool overriding their config
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense!