1+ classdef AutoTrace < handle
2+ % Automatic instrumentation with OpenTelemetry tracing.
3+
4+ % Copyright 2024 The MathWorks, Inc.
5+
6+ properties (SetAccess = private )
7+ StartFunction function_handle % entry function
8+ InstrumentedFiles string % list of M-files that are auto-instrumented
9+ end
10+
11+ properties (Access = private )
12+ Instrumentor (1 ,1 ) opentelemetry.autoinstrument.AutoTraceInstrumentor % helper object
13+ end
14+
15+ methods
16+ function obj = AutoTrace(startfun , options )
17+ % AutoTrace Automatic instrumentation with OpenTelemetry tracing
18+ % AT = OPENTELEMETRY.AUTOINSTRUMENT.AUTOTRACE(FUN) where FUN
19+ % is a function handle, automatically instruments the function
20+ % and all the functions in the same file, as well as their dependencies.
21+ % For each function, a span is automatically started and made
22+ % current at the beginning, and ended at the end. Returns an
23+ % object AT. When AT is cleared or goes out-of-scope, automatic
24+ % instrumentation will stop and the functions will no longer
25+ % be instrumented.
26+ %
27+ % AT = OPENTELEMETRY.AUTOINSTRUMENT.AUTOTRACE(FUN, NAME1, VALUE1,
28+ % NAME2, VALUE2, ...) specifies optional name-value pairs.
29+ % Supported options are:
30+ % "AdditionalFiles" - List of additional file names to
31+ % include. Specifying additional files
32+ % are useful in cases when automatic
33+ % dependency detection failed to include them.
34+ % For example, MATLAB Toolbox functions
35+ % authored by MathWorks are excluded by default.
36+ % "ExcludeFiles" - List of file names to exclude
37+ % "AutoDetectFiles" - Whether to automatically include dependencies
38+ % of FUN, specified as a logical scalar.
39+ % Default value is true.
40+ % "TracerName" - Specifies the name of the tracer
41+ % the automatic spans are generated from
42+ % "TracerVersion" - The tracer version
43+ % "TracerSchema" - The tracer schema
44+ % "Attributes" - Add attributes to all the automatic spans.
45+ % Attributes must be specified as a dictionary.
46+ % "SpanKind" - Span kind of the automatic spans
47+ arguments
48+ startfun (1 ,1 ) function_handle
49+ options.TracerName {mustBeTextScalar } = " AutoTrace"
50+ options.TracerVersion {mustBeTextScalar } = " "
51+ options.TracerSchema {mustBeTextScalar } = " "
52+ options.SpanKind {mustBeTextScalar }
53+ options.Attributes {mustBeA(options .Attributes , " dictionary" )}
54+ options.ExcludeFiles {mustBeText }
55+ options.AdditionalFiles {mustBeText }
56+ options.AutoDetectFiles (1 ,1 ) {mustBeNumericOrLogical } = true
57+ end
58+ obj.StartFunction = startfun ;
59+ startfunname = func2str(startfun );
60+ processFileInput(startfunname ); % validate startfun
61+ if options .AutoDetectFiles
62+ if isdeployed
63+ % matlab.codetools.requiredFilesAndProducts is not
64+ % deployable. Instead instrument all files under CTFROOT
65+ fileinfo = dir(fullfile(ctfroot , " **" , " *.m" ));
66+ files = fullfile(string({fileinfo .folder }), string({fileinfo .name }));
67+
68+ % filter out internal files in the toolbox directory
69+ files = files(~startsWith(files , fullfile(ctfroot , " toolbox" )));
70+ else
71+ % #exclude matlab.codetools.requiredFilesAndProducts
72+ files = string(matlab .codetools .requiredFilesAndProducts(startfunname ));
73+ end
74+ else
75+ % only include the input file, not its dependencies
76+ files = string(which(startfunname ));
77+ end
78+ % add extra files, this is intended for files
79+ % matlab.codetools.requiredFilesAndProducts somehow missed
80+ if isfield(options , " AdditionalFiles" )
81+ incfiles = string(options .AdditionalFiles );
82+ for i = 1 : numel(incfiles )
83+ incfiles(i ) = which(incfiles(i )); % get the full path
84+ processFileInput(incfiles(i )); % validate additional file
85+ end
86+ files = union(files , incfiles );
87+ end
88+
89+ % make sure files are unique
90+ files = unique(files );
91+
92+ % filter out excluded files
93+ if isfield(options , " ExcludeFiles" )
94+ excfiles = string(options .ExcludeFiles );
95+ for i = 1 : numel(excfiles )
96+ excfiles(i ) = which(excfiles(i )); % get the full path
97+ end
98+ files = setdiff(files , excfiles );
99+ end
100+ % filter out OpenTelemetry files, in case manual
101+ % instrumentation is also used
102+ files = files(~contains(files , [" +opentelemetry" " +libmexclass" ]));
103+
104+ for i = 1 : length(files )
105+ currfile = files(i );
106+ if currfile ==" " % ignore empties
107+ continue
108+ end
109+ obj .Instrumentor .instrument(currfile , options );
110+ obj .InstrumentedFiles(end + 1 ,1 ) = currfile ;
111+ end
112+ end
113+
114+ function delete(obj )
115+ obj .Instrumentor .cleanup(obj .InstrumentedFiles );
116+ end
117+
118+ function varargout = beginTrace(obj , varargin )
119+ % beginTrace Run the auto-instrumented function
120+ % [OUT1, OUT2, ...] = BEGINTRACE(AT, IN1, IN2, ...) calls the
121+ % instrumented function with error handling. In case of
122+ % error, all running spans will end and the last span will
123+ % set to an "Error" status. The instrumented function is
124+ % called with the synax [OUT1, OUT2, ...] = FUN(IN1, IN2, ...)
125+ %
126+ % See also OPENTELEMETRY.AUTOINSTRUMENT.AUTOTRACE/HANDLEERROR
127+ try
128+ varargout = cell(1 ,nargout );
129+ [varargout{: }] = feval(obj .StartFunction , varargin{: });
130+ catch ME
131+ handleError(obj , ME );
132+ end
133+ end
134+
135+ function handleError(obj , ME )
136+ % handleError Perform cleanup in case of an error
137+ % HANDLEERROR(AT, ME) performs cleanup by ending all running
138+ % spans and their corresponding scopes. Rethrow the
139+ % exception ME.
140+ if ~isempty(obj .Instrumentor .Spans )
141+ setStatus(obj .Instrumentor .Spans(end ), " Error" );
142+ for i = length(obj .Instrumentor .Spans ): -1 : 1
143+ obj .Instrumentor .Spans(i ) = [];
144+ obj .Instrumentor .Scopes(i ) = [];
145+ end
146+ end
147+ rethrow(ME );
148+ end
149+ end
150+
151+
152+ end
153+
154+ % check input file is valid
155+ function processFileInput(f )
156+ f = string(f ); % force into a string
157+ if startsWith(f , ' @' ) % check for anonymous function
158+ error(f + " is an anonymous function and is not supported." );
159+ end
160+ if exist(f , " file" ) ~= 2
161+ error(f + " is not a valid MATLAB file with a .m extension and is not supported." )
162+ end
163+ end
0 commit comments