diff --git a/auto-instrumentation/+opentelemetry/+autoinstrument/AutoTrace.m b/auto-instrumentation/+opentelemetry/+autoinstrument/AutoTrace.m index 27b75a3..11aa03f 100644 --- a/auto-instrumentation/+opentelemetry/+autoinstrument/AutoTrace.m +++ b/auto-instrumentation/+opentelemetry/+autoinstrument/AutoTrace.m @@ -58,9 +58,15 @@ options.AdditionalFiles {mustBeText} options.AutoDetectFiles (1,1) {mustBeNumericOrLogical} = true end + % check for anonymous function + fs = functions(startfun); + if fs.type == "anonymous" + error("opentelemetry:autoinstrument:AutoTrace:AnonymousFunction", ... + "Anonymous functions are not supported."); + end obj.StartFunction = startfun; startfunname = func2str(startfun); - processFileInput(startfunname); % validate startfun + startfunname = processFileInput(startfunname); % validate startfun if options.AutoDetectFiles if isdeployed % matlab.codetools.requiredFilesAndProducts is not @@ -76,15 +82,16 @@ end else % only include the input file, not its dependencies - files = string(which(startfunname)); + files = startfunname; end % add extra files, this is intended for files % matlab.codetools.requiredFilesAndProducts somehow missed if isfield(options, "AdditionalFiles") - incfiles = string(options.AdditionalFiles); - for i = 1:numel(incfiles) - incfiles(i) = which(incfiles(i)); % get the full path - processFileInput(incfiles(i)); % validate additional file + incinput = string(options.AdditionalFiles); + incfiles = []; + for i = 1:numel(incinput) + % validate additional file + incfiles = [incfiles; processFileOrFolderInput(incinput(i))]; %#ok end files = union(files, incfiles); end @@ -94,9 +101,11 @@ % filter out excluded files if isfield(options, "ExcludeFiles") - excfiles = string(options.ExcludeFiles); - for i = 1:numel(excfiles) - excfiles(i) = which(excfiles(i)); % get the full path + excinput = string(options.ExcludeFiles); + excfiles = []; + for i = 1:numel(excinput) + % validate exclude file + excfiles = [excfiles; processFileOrFolderInput(excinput(i))]; %#ok end files = setdiff(files, excfiles); end @@ -155,15 +164,13 @@ function handleError(obj, ME) end % check input file is valid -function processFileInput(f) +function f = processFileInput(f) f = string(f); % force into a string -if startsWith(f, '@') % check for anonymous function - error("opentelemetry:autoinstrument:AutoTrace:AnonymousFunction", ... - replace(f, "\", "\\") + " is an anonymous function and is not supported."); -end [~,~,fext] = fileparts(f); % check file extension filetype = exist(f, "file"); % check file type -if ~(filetype == 2 && ismember(fext, ["" ".m" ".mlx"])) +if filetype == 2 && ismember(fext, ["" ".m" ".mlx"]) + f = string(which(f)); +else if exist(f, "builtin") error("opentelemetry:autoinstrument:AutoTrace:BuiltinFunction", ... replace(f, "\", "\\") + " is a builtin function and is not supported."); @@ -172,4 +179,20 @@ function processFileInput(f) replace(f, "\", "\\") + " is not found or is not a valid MATLAB file with a .m or .mlx extension."); end end +end + +% check input file or folder is valid +function f = processFileOrFolderInput(f) +f = string(f); % force into a string +if isfolder(f) + % expand the directory + mfileinfo = dir(fullfile(f, "*.m")); + mfiles = fullfile(string({mfileinfo.folder}), string({mfileinfo.name})); + mlxfileinfo = dir(fullfile(f, "*.mlx")); + mlxfiles = fullfile(string({mlxfileinfo.folder}), string({mlxfileinfo.name})); + f = [mfiles; mlxfiles]; +else + % file + f = processFileInput(f); +end end \ No newline at end of file diff --git a/test/autotrace_examples/example2/example2.m b/test/autotrace_examples/example2/example2.m new file mode 100644 index 0000000..b3649b4 --- /dev/null +++ b/test/autotrace_examples/example2/example2.m @@ -0,0 +1,9 @@ +function x = example2 +% example code for testing auto instrumentation. + +% Copyright 2024 The MathWorks, Inc. + +x = 10; +x = ex2helper1(x); +x = ex2helper2(x); + diff --git a/test/autotrace_examples/example2/helpers/ex2helper1.m b/test/autotrace_examples/example2/helpers/ex2helper1.m new file mode 100644 index 0000000..14b12a4 --- /dev/null +++ b/test/autotrace_examples/example2/helpers/ex2helper1.m @@ -0,0 +1,6 @@ +function x = ex2helper1(x) +% example code for testing auto instrumentation + +% Copyright 2024 The MathWorks, Inc. + +x = x * 2; diff --git a/test/autotrace_examples/example2/helpers/ex2helper2.m b/test/autotrace_examples/example2/helpers/ex2helper2.m new file mode 100644 index 0000000..31415ca --- /dev/null +++ b/test/autotrace_examples/example2/helpers/ex2helper2.m @@ -0,0 +1,6 @@ +function x = ex2helper2(x) +% example code for testing auto instrumentation + +% Copyright 2024 The MathWorks, Inc. + +x = x * 3; \ No newline at end of file diff --git a/test/tautotrace.m b/test/tautotrace.m index 63f9387..7fb7198 100644 --- a/test/tautotrace.m +++ b/test/tautotrace.m @@ -24,6 +24,10 @@ function setupOnce(testCase) % add the example folders to the path example1folder = fullfile(fileparts(mfilename('fullpath')), "autotrace_examples", "example1"); testCase.applyFixture(matlab.unittest.fixtures.PathFixture(example1folder)); + example2folder = fullfile(fileparts(mfilename('fullpath')), "autotrace_examples", "example2"); + testCase.applyFixture(matlab.unittest.fixtures.PathFixture(example2folder)); + example2helpersfolder = fullfile(fileparts(mfilename('fullpath')), "autotrace_examples", "example2", "helpers"); + testCase.applyFixture(matlab.unittest.fixtures.PathFixture(example2helpersfolder)); commonSetupOnce(testCase); % configure the global tracer provider @@ -115,6 +119,54 @@ function testDisableFileDetection(testCase) verifyEqual(testCase, string(results{1}.resourceSpans.scopeSpans.spans.name), "example1"); end + function testIncludeFolder(testCase) + % testIncludeFolder: specify a folder in AdditionalFiles + + % set up AutoTrace + example2helpers = fullfile(fileparts(mfilename('fullpath')), ... + "autotrace_examples", "example2", "helpers"); + % turn off automatic detection and specify dependencies using + % their folder name + at = opentelemetry.autoinstrument.AutoTrace(@example2, ... + "AutoDetectFiles", false, "AdditionalFiles", example2helpers); + + % run the example + [~] = beginTrace(at); + + % perform test comparisons + results = readJsonResults(testCase); + verifyNumElements(testCase, results, 3); + + % check span names + verifyEqual(testCase, string(results{1}.resourceSpans.scopeSpans.spans.name), "ex2helper1"); + verifyEqual(testCase, string(results{2}.resourceSpans.scopeSpans.spans.name), "ex2helper2"); + verifyEqual(testCase, string(results{3}.resourceSpans.scopeSpans.spans.name), "example2"); + + % check parent children relationship + verifyEqual(testCase, results{1}.resourceSpans.scopeSpans.spans.parentSpanId, results{3}.resourceSpans.scopeSpans.spans.spanId); + verifyEqual(testCase, results{2}.resourceSpans.scopeSpans.spans.parentSpanId, results{3}.resourceSpans.scopeSpans.spans.spanId); + end + + function testExcludeFolder(testCase) + % testExcludeFolder: specify a folder in ExcludeFiles + + % set up AutoTrace + example2helpers = fullfile(fileparts(mfilename('fullpath')), ... + "autotrace_examples", "example2", "helpers"); + at = opentelemetry.autoinstrument.AutoTrace(@example2, ... + "ExcludeFiles", example2helpers); + + % run the example + [~] = beginTrace(at); + + % perform test comparisons + results = readJsonResults(testCase); + verifyNumElements(testCase, results, 1); + + % check span names + verifyEqual(testCase, string(results{1}.resourceSpans.scopeSpans.spans.name), "example2"); + end + function testNonFileOptions(testCase) % testNonFileOptions: other options not related to files, % "TracerName", "TracerVersion", "TracerSchema", "Attributes",