Skip to content

Commit 6014966

Browse files
author
Sam Eagen
committed
Initial plugin draft
1 parent 721da05 commit 6014966

File tree

3 files changed

+746
-0
lines changed

3 files changed

+746
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
classdef OpenTelemetryPluginService < matlab.buildtool.internal.services.plugins.BuildRunnerPluginService
2+
% This class is unsupported and might change or be removed without notice
3+
% in a future version.
4+
5+
% Copyright 2026 The MathWorks, Inc.
6+
7+
methods
8+
function plugins = providePlugins(~, ~)
9+
plugins = matlab.buildtool.plugins.BuildRunnerPlugin.empty(1,0);
10+
if ~isMATLABReleaseOlderThan("R2026a")
11+
plugins = matlab.buildtool.plugins.OpenTelemetryPlugin();
12+
end
13+
end
14+
end
15+
end
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
classdef OpenTelemetryPlugin < matlab.buildtool.plugins.BuildRunnerPlugin
2+
3+
% Copyright 2026 The MathWorks, Inc.
4+
5+
methods(Access = protected)
6+
function runBuild(plugin, pluginData)
7+
% Configure by attaching to span if passed in via environment
8+
% variable, and propagating baggage
9+
configureOTel();
10+
11+
% Attributes
12+
otelAttributes = dictionary( ...
13+
[ ...
14+
"otel.library.name", ...
15+
"span.kind", ...
16+
"internal.span.format", ...
17+
], ...
18+
[ ...
19+
"buildtool", ...
20+
"internal", ...
21+
"proto", ...
22+
] ...
23+
);
24+
25+
tr = opentelemetry.trace.getTracer("buildtool");
26+
sp = tr.startSpan("buildtool", Attributes=otelAttributes);
27+
scope = makeCurrent(sp); %#ok<NASGU>
28+
29+
% Run build
30+
runBuild@matlab.buildtool.plugins.BuildRunnerPlugin(plugin, pluginData);
31+
32+
% Update status
33+
if pluginData.BuildResult.Failed
34+
sp.setStatus("Error", "Build completed, results not successful");
35+
else
36+
sp.setStatus("Ok");
37+
end
38+
39+
% Results-based attributes
40+
taskResults = pluginData.BuildResult.TaskResults;
41+
successful = [taskResults([taskResults.Successful]).Name];
42+
failed = [taskResults([taskResults.Failed]).Name];
43+
skipped = [taskResults([taskResults.Skipped]).Name];
44+
45+
sp.setAttributes( ...
46+
"buildtool.tasks", numel(pluginData.BuildResult.TaskResults), ...
47+
"buildtool.tasks.successful", successful, ...
48+
"buildtool.tasks.failed", failed, ...
49+
"buildtool.tasks.skipped", skipped, ...
50+
"buildtool.build.successes", numel(successful), ...
51+
"buildtool.build.failures", numel(failed), ...
52+
"buildtool.build.skips", numel(skipped) ...
53+
);
54+
55+
% Update metrics
56+
meter = opentelemetry.metrics.getMeter("buildtool");
57+
successes = meter.createCounter("buildtool.tasks.successful");
58+
failures = meter.createCounter("buildtool.tasks.failed");
59+
skips = meter.createCounter("buildtool.tasks.skipped");
60+
buildSuccesses = meter.createCounter("buildtool.build.successes");
61+
buildFailures = meter.createCounter("buildtool.build.failures");
62+
63+
successes.add(numel(successful));
64+
failures.add(numel(failed));
65+
skips.add(numel(skipped));
66+
buildSuccesses.add(double(~pluginData.BuildResult.Failed));
67+
buildFailures.add(double(pluginData.BuildResult.Failed));
68+
69+
cleanupOTel(sp);
70+
end
71+
72+
function runTask(plugin, pluginData)
73+
% TODO:
74+
% - buildtool.task.outputs
75+
% - buildtool.task.inputs
76+
77+
% Definitions
78+
taskName = pluginData.Name;
79+
taskDescription = pluginData.Tasks.Description;
80+
81+
% Attributes
82+
otelAttributes = dictionary( ...
83+
[ ...
84+
"otel.library.name", ...
85+
"span.kind", ...
86+
"internal.span.format", ...
87+
"buildtool.task.name", ...
88+
"buildtool.task.description", ...
89+
], ...
90+
[ ...
91+
taskName, ...
92+
"internal", ...
93+
"proto", ...
94+
taskName, ...
95+
taskDescription ...
96+
] ...
97+
);
98+
99+
tr = opentelemetry.trace.getTracer(taskName);
100+
sp = tr.startSpan(taskName, Attributes=otelAttributes);
101+
scope = makeCurrent(sp); %#ok<NASGU>
102+
103+
% Run task
104+
runTask@matlab.buildtool.plugins.BuildRunnerPlugin(plugin, pluginData);
105+
106+
% Set results-based attributes
107+
resultAttributes = dictionary( ...
108+
[ ...
109+
"buildtool.task.successful", ...
110+
"buildtool.task.failed", ...
111+
"buildtool.task.skipped" ...
112+
], ...
113+
[ ...
114+
pluginData.TaskResults.Successful, ...
115+
pluginData.TaskResults.Failed, ...
116+
pluginData.TaskResults.Skipped ...
117+
] ...
118+
);
119+
sp.setAttributes(resultAttributes);
120+
121+
% Update span status
122+
if pluginData.TaskResults.Successful
123+
sp.setStatus("Ok");
124+
else
125+
sp.setStatus("Error", "Task completed, results not successful");
126+
end
127+
128+
sp.endSpan();
129+
end
130+
end
131+
end
132+
133+
% Use the same configuration as PADV
134+
function extcontextscope = configureOTel()
135+
% populate resource attributes
136+
otelservicename = "buildtool";
137+
otelresource = dictionary("service.name", otelservicename);
138+
139+
% baggage propagation
140+
otelbaggage = getenv("BAGGAGE");
141+
if ~isempty(otelbaggage)
142+
otelbaggage = split(split(string(otelbaggage),','), "=");
143+
otelresource = insert(otelresource, otelbaggage(:,1), otelbaggage(:,2));
144+
end
145+
146+
% check for passed in external context
147+
extcontextscope = [];
148+
traceid = getenv("TRACE_ID");
149+
spanid = getenv("SPAN_ID");
150+
if ~isempty(traceid) && ~isempty(spanid)
151+
spcontext = opentelemetry.trace.SpanContext(traceid, spanid);
152+
extcontextscope = makeCurrent(spcontext);
153+
end
154+
155+
% tracer provider
156+
otelspexp = opentelemetry.exporters.otlp.OtlpGrpcSpanExporter; % use gRPC because Otel plugin for Jenkins only use gRPC
157+
otelspproc = opentelemetry.sdk.trace.BatchSpanProcessor(otelspexp);
158+
oteltp = opentelemetry.sdk.trace.TracerProvider(otelspproc, Resource=otelresource);
159+
setTracerProvider(oteltp);
160+
161+
% meter provider
162+
otelmexp = opentelemetry.exporters.otlp.OtlpGrpcMetricExporter; % use gRPC because Otel plugin for Jenkins only use gRPC
163+
otelmread = opentelemetry.sdk.metrics.PeriodicExportingMetricReader(otelmexp);
164+
otelmp = opentelemetry.sdk.metrics.MeterProvider(otelmread, Resource=otelresource);
165+
setMeterProvider(otelmp);
166+
167+
% logger provider
168+
otellgexp = opentelemetry.exporters.otlp.OtlpGrpcLogRecordExporter; % use gRPC because Otel plugin for Jenkins only use gRPC
169+
otellgproc = opentelemetry.sdk.logs.BatchLogRecordProcessor(otellgexp);
170+
otellp = opentelemetry.sdk.logs.LoggerProvider(otellgproc, Resource=otelresource);
171+
setLoggerProvider(otellp);
172+
end
173+
174+
% Use the same cleanup as PADV
175+
function cleanupOTel(span)
176+
177+
timeout = 5;
178+
179+
% end the input span before cleaning up
180+
if nargin > 0
181+
endSpan(span);
182+
end
183+
184+
% tracer provider
185+
oteltp = opentelemetry.trace.Provider.getTracerProvider;
186+
opentelemetry.sdk.common.Cleanup.forceFlush(oteltp, timeout);
187+
opentelemetry.sdk.common.Cleanup.shutdown(oteltp);
188+
189+
% meter provider
190+
otelmp = opentelemetry.metrics.Provider.getMeterProvider;
191+
opentelemetry.sdk.common.Cleanup.forceFlush(otelmp, timeout);
192+
opentelemetry.sdk.common.Cleanup.shutdown(otelmp);
193+
194+
% logger provider
195+
otellp = opentelemetry.logs.Provider.getLoggerProvider;
196+
opentelemetry.sdk.common.Cleanup.forceFlush(otellp, timeout);
197+
opentelemetry.sdk.common.Cleanup.shutdown(otellp);
198+
end

0 commit comments

Comments
 (0)