classdef (Hidden = true) ExecStackHelper < coder.profile.ExecHelper
%EXECSTACKHELPER contains functions to generate stack profiling report

% Copyright 2021-2022 The MathWorks, Inc.

methods(Static=true)

function [sdiText, sdiMatlabCmd, sdiTooltip] = ...
getDataForSdiLink(idParent, sectionProfile, modelName, lReportFcn, lUseAbsPath, ~)
icon = coder.profile.ExecHelper.getSdiIcon;
if lUseAbsPath
icon = fullfile(coder.profile.ExecHelper.ResourcesFolder, icon);
end
sdiText = ['<img src="' icon '"/>'];
idSection = sectionProfile.getIdUint64;
sdiMatlabCmd = ...
sprintf('coder.profile.ExecStackHelper.sdiPlot(uint64(%u), uint64(%u), ''%s'', %s);', ...
idParent, idSection, modelName, lReportFcn);
sdiTooltip = DAStudio.message('CoderProfile:ExecutionStack:SdiLinkTooltip');
end

function sdiPlot(idParent, idSection, modelName, lReportFcn)
engine = Simulink.sdi.Instance.engine;
idSdi = engine.getSignalIDByMetaData(int2str(idSection));

if isempty(idSdi)
% Try to locate the ExecutionTime object and import it into SDI
executionProfileBaseStr = coder.profile.ExecStackHelper...
.searchForObjInBaseWS(idParent);
if isempty(executionProfileBaseStr)
[~, executionProfileBaseStr] = ...
lReportFcn.searchForStackProfObj(idParent, modelName);
end

if isempty(executionProfileBaseStr)
errTitle = DAStudio.message...
('CoderProfile:ExecutionStack:SdiLinkFailedTitle');
errMsg = DAStudio.message...
('CoderProfile:ExecutionStack:SdiLinkFailedMsg');
errordlg(errMsg, errTitle, 'modal');
return;
else
runName = sprintf('Run <run_index>');
Simulink.sdi.createRun(runName, 'base', {executionProfileBaseStr});
end
idSdi = engine.getSignalIDByMetaData(int2str(idSection));
assert(~isempty(idSdi), 'Data must now be in SDI');
end

if ~isempty(idSdi)
% Bring SDI to the foreground
Simulink.sdi.view
% Toggle the setting
engine.setSignalChecked(idSdi, true);
end
end

function [hstText, hstMatlabCmd, hstTooltip] = ...
getDataForHistogramLink(idParent, sectionProfile, modelName, lReportFcn)
if sectionProfile.NumCalls > 1
sectionNumber = sectionProfile.Number;
icon = coder.profile.ExecHelper.HistogramIcon;
hstMatlabCmd = sprintf(...
'coder.profile.ExecStackHelper.sectionHistogram(uint64(%u), uint64(%u), ''%s'', ''%s'', %s);', ...
idParent, sectionNumber, sectionProfile.Name, modelName, lReportFcn);
hstText = ['<img src="' icon '"/>'];
hstTooltip = DAStudio.message('CoderProfile:ExecutionStack:ReportStackDistributionTooltip');
else
hstText = '';
hstMatlabCmd = '';
hstTooltip = '';
end
end

function sectionHistogram(idParent, sectionNumber, sectioName, modelName, lReportFcn)
% Try to locate the ExecutionTime object
executionStackVarName = ...
coder.profile.ExecStackHelper.searchForObjInBaseWS(...
idParent);
if isempty(executionStackVarName)
[~, executionStackVarName] = ...
lReportFcn.searchForStackProfObj(idParent, modelName);
end

foundData = ~isempty(executionStackVarName);

if ~foundData
executionStackVarName = 'executionStack';
end

sectionsCmdStr = sprintf('%s.TaskSections(%u).StackUsage', ...
executionStackVarName, sectionNumber);

if foundData
data = evalin('base', sectionsCmdStr);
tag = coder.profile.ExecStackHelper.generateTag(sectionNumber);
figureHandle = figure('Tag', tag);
histogram(data, 30, 'Normalization', 'probability');
sectioName = strrep(sectioName, '_', '\_');
title(DAStudio.message('CoderProfile:ExecutionTime:ReportDistributionTitle', sectioName));
plotedit(figureHandle, 'setenabletools', 'off');
xlabel(DAStudio.message('CoderProfile:ExecutionStack:ReportStackDistributionXLabel'));
ylabel(DAStudio.message('CoderProfile:ExecutionTime:ReportDistributionYLabel'));
else
errTitle = DAStudio.message...
('CoderProfile:ExecutionStack:LinkFailedTitle');
errMsg = DAStudio.message...
('CoderProfile:ExecutionStack:LinkApiLinkFailedMsg', ...
executionStackVarName);
errordlg(errMsg, errTitle, 'modal');
end
end

function setSummarySection(lTargetWordSize, lTargetLargestAtomicIntegerType, ...
lPtrSize, lMaxStack, lMaxStackPrm, lTimeCreated, lGenericDriver, reportBuilder)
rows = cell(4, 2);
idx = 1;
% Target Largest Atomic Integer Type
atomicIntTxt = DAStudio.message('CoderProfile:ExecutionStack:ReportAtomicInt');
atomicIntTooltip = DAStudio.message('CoderProfile:ExecutionStack:ReportAtomicIntTooltip');
rows{idx,1} = sprintf("<a title='%s'>%s</a>", atomicIntTooltip, atomicIntTxt);
rows{idx,2} = lTargetLargestAtomicIntegerType;
idx = idx + 1;
% Target word size
wordSizeTxt = DAStudio.message('CoderProfile:ExecutionStack:ReportWordSize');
wordSizeTooltip = DAStudio.message('CoderProfile:ExecutionStack:ReportWordSizeTooltip');
rows{idx, 1} = sprintf("<a title='%s'>%s</a>", wordSizeTooltip, wordSizeTxt);
rows{idx,2} = num2str(lTargetWordSize);
idx = idx + 1;
% Pointer size in bits
ptrSizeTxt = DAStudio.message('CoderProfile:ExecutionStack:ReportPtrSize');
ptrSizeTooltip = DAStudio.message('CoderProfile:ExecutionStack:ReportPtrSizeTooltip');
rows{idx,1} = sprintf("<a title='%s'>%s</a>", ptrSizeTooltip, ptrSizeTxt);
rows{idx,2} = num2str(lPtrSize);
idx = idx + 1;
% max stack allowed
if ~isempty(lMaxStack)
maxStackText = DAStudio.message('CoderProfile:ExecutionStack:ReportMaxStack');
maxStackTooltip = DAStudio.message('CoderProfile:ExecutionStack:ReportMaxStackTooltip', lMaxStackPrm);
maxStackStr = sprintf('<a title="%s">%s</a>', ...
maxStackTooltip, maxStackText);
rows{idx,1} = maxStackStr;
rows{idx,2} = num2str(lMaxStack);
idx = idx + 1;
end
% Stack driver type
driverText = DAStudio.message('CoderProfile:ExecutionStack:ReportStackDriver');
driverTooltip = DAStudio.message('CoderProfile:ExecutionStack:ReportStackDriverTooltip');
if lGenericDriver
driverVal = DAStudio.message('CoderProfile:ExecutionStack:ReportStackDriverGeneric');
else
driverVal = DAStudio.message('CoderProfile:ExecutionStack:ReportStackDriverSpecific');
end
driverStr = sprintf('<a title="%s">%s</a>', ...
driverTooltip, driverText);
rows{idx,1} = driverStr;
rows{idx,2} = driverVal;
idx = idx + 1;
% Creation time
timeCreatedText = DAStudio.message('CoderProfile:ExecutionStack:ReportCreated');
timeCreatedTooltip = DAStudio.message('CoderProfile:ExecutionStack:ReportCreatedTooltip');
timeCreatedStr = sprintf('<a title="%s">%s</a>', ...
timeCreatedTooltip, timeCreatedText);
rows{idx,1} = timeCreatedStr;
rows{idx,2} = lTimeCreated;

% create section
headings = {};
colWidthsInPercent = [65 35];
colAlignment = {'left', 'left'};
reportBuilder.addTableSection('Summary', headings, ...
colWidthsInPercent, colAlignment, rows, 0.5);
end

function setResultTaskSection(idParent, sections, reportFcn, cmpName, topModelName, reportBuilder)
isExtReport = coder.profile.private.featureOn('FunctionStackUsage');
if isExtReport
headings = {'Task', ...
'Minimum Stack Usage in bytes', ...
'Average Stack Usage in bytes', ...
'Maximum Stack Usage in bytes', ...
'Minimum Function-Call Depth', ...
'Maximum Function-Call Depth', ...
'Calls', ...
''};
else
headings = {'Task', ...
'Minimum Stack Usage in bytes', ...
'Average Stack Usage in bytes', ...
'Maximum Stack Usage in bytes', ...
'Calls', ...
''};
end
numSections = length(sections);
if isExtReport
offset = 2;
else
offset = 0;
end
rows = cell(numSections, 6 + offset);
rowIndent = zeros(1, numSections);
footNotes = containers.Map('KeyType', 'char', 'ValueType', 'any');
for i=1:numSections
section = sections(i);
minStackUsage = section.MinimumStackUsage;
if minStackUsage == 0
if ~footNotes.isKey('ZeroStackUsage')
footNoteText = DAStudio.message('CoderProfile:ExecutionStack:NoteZeroUsage');
footNoteRef = coder.profile.private.createProbeIdentifier('ZeroStackUsage', 32);
footNote = struct('Number', 1, 'Text', footNoteText, 'Ref', footNoteRef);
footNotes('ZeroStackUsage') = footNote;
end
noteHtml = sprintf('&#91;<a href="#%s" title="%s">Note %d</a>&#93;', footNoteRef, '1', 1);
else
noteHtml = '';
end
rows{i, 1} = coder.profile.profGetMatlabCmd(section, cmpName, reportFcn, noteHtml);
rows{i, 2} = sprintf("<a>%d</a>", minStackUsage);
rows{i, 3} = sprintf("<a>%d</a>", section.AverageStackUsage);
rows{i, 4} = sprintf("<a>%d</a>", section.MaximumStackUsage);
if isExtReport
rows{i, 5} = sprintf("<a>%d</a>", section.MinimumCallDepth);
rows{i, 6} = sprintf("<a>%d</a>", section.MaximumCallDepth);
end
rows{i, 5 + offset} = sprintf("<a>%d</a>", section.NumCalls);
[hstText, hstMatlabCmd, hstTooltip] = ...
coder.profile.ExecStackHelper.getDataForHistogramLink(...
idParent, section, topModelName, reportFcn);
hstHyperlink = coder.profile.ExecHelper.formattedHtml(hstText, hstMatlabCmd, hstTooltip);
lUseAbsPath = false;
[sdiText, sdiMatlabCmd, sdiTooltip] = ...
coder.profile.ExecStackHelper.getDataForSdiLink(...
idParent, section, topModelName, reportFcn, lUseAbsPath);
sdiHyperlink = coder.profile.ExecHelper.formattedHtml(sdiText, sdiMatlabCmd, sdiTooltip);

rows{i, 6 + offset} = sprintf("%s %s", hstHyperlink, sdiHyperlink);
end

if isExtReport
colWidthsInPercent = [30 15 15 15 15 15 15 10];
colAlignment = {'left', 'right', 'right', 'right', 'right', 'right', 'right', 'center'};
else
colWidthsInPercent = [30 15 15 15 15 10];
colAlignment = {'left', 'right', 'right', 'right', 'right', 'center'};
end

tableInfo.tableId = 'Profiled Tasks';
tableInfo.headings = headings;
tableInfo.rows = rows;
tableInfo.rowIndent = rowIndent;
tableInfo.colWidthsInPercent = colWidthsInPercent;
tableInfo.colAlignment = colAlignment;
tableInfo.Offset = 0;
reportBuilder.addCustomTableSection('Profiled Tasks', tableInfo, footNotes, '', false, true, 1.0);
end

function setResultFunctionSection(sections, reportBuilder, isSimulinkWorkflow)
headings = {'Function', ...
'Memory in bytes', ...
'Calls', ...
''};
numSections = length(sections);
rows = cell(numSections, 4);
rowIndent = zeros(1, numSections);
footNotes = containers.Map('KeyType', 'char', 'ValueType', 'any');
for i=1:numSections
section = sections(i);
if section.FlagMultipleValues
footNoteRef = 'FlagMultipleValues';
if ~footNotes.isKey(footNoteRef)
footNoteText = DAStudio.message('CoderProfile:ExecutionStack:FlagMultipleValuesNote');
footNote = struct('Number', 1, 'Text', footNoteText, 'Ref', footNoteRef);
footNotes(footNoteRef) = footNote;
end
noteHtml = sprintf(' &#91;<a href="#%s" title="%s">Note %d</a>&#93;', footNoteRef, '1', 1);
else
noteHtml = '';
end
rows{i, 1} = sprintf('<a>%s</a>%s', section.Name, noteHtml);
rows{i, 2} = sprintf('<a>%d</a>', section.Memory);
rows{i, 3} = sprintf('<a>%d</a>', section.NumCalls);
if isSimulinkWorkflow
[txt, cmd, tooltip] = coder.profile.ExecStackHelper.getSimulinkCodeLink(section.getTraceInfo());
else
[txt, cmd, tooltip] = coder.profile.ExecStackHelper.getMLCodeLink(section.getTraceInfo());
end
codeLink = coder.profile.ExecTimeHelper.formattedHtml...
(txt, cmd, tooltip);
rows{i, 4} = codeLink;
end

colWidthsInPercent = [40 25 25 10];
colAlignment = {'left', 'right', 'right', 'center'};

tableInfo.tableId = 'Profiled Functions';
tableInfo.headings = headings;
tableInfo.rows = rows;
tableInfo.rowIndent = rowIndent;
tableInfo.colWidthsInPercent = colWidthsInPercent;
tableInfo.colAlignment = colAlignment;
tableInfo.Offset = 0;
reportBuilder.addCustomTableSection('Profiled Functions', tableInfo, footNotes, '', false, true, 1.0);
end

function setMaxStackChartSection(sections, maxStackUsage, maxStackPrmName, reportBuilder)
numSections = length(sections);
barData = zeros(1, numSections);
barXLabels = cell(1, numSections);
for i=1:numSections
section = sections(i);
barData(i) = section.MaximumStackUsage;
barXLabels{i} = strrep(section.Name, '_', '\_');
end
[barData, indexs] = sort(barData);
barXLabels = barXLabels(indexs);
introTxt = DAStudio.message('CoderProfile:ExecutionStack:ReportChartIntro', maxStackPrmName);
imageSection = coder.profile.ProfileStackReportImage(...
'Maximum Stack Usage', introTxt, barData, barXLabels, maxStackUsage);
reportBuilder.addCustomObj(imageSection);
end

function codeMLHighlight(fileFullPath, lineNumber)
if ~isfile(fileFullPath)
return;
end
matlab.desktop.editor.openAndGoToLine(fileFullPath, lineNumber);
end

function [txt, cmd, tooltip] = getSimulinkCodeLink(lTraceInfo)
txt = '';
cmd = '';
tooltip = '';
if isempty(lTraceInfo) || isempty(lTraceInfo.getFileNames)
return;
end

argStr = '';
fileNames = lTraceInfo.getFileNames();
allLineNos = lTraceInfo.getLineNumbers();

tooltipStr = sprintf('%s\n', lTraceInfo.getCodeSectionName());
tooltipStr = coder.profile.htmlEscape(tooltipStr);
tooltipStr = strrep(tooltipStr, ' ', '&nbsp;');
fileNamesStr = '';
for i=1:length(fileNames)
lineNos = allLineNos{i};
% Build the line number string, e.g. [10 20]
lineNosStr = ' [';
for ii2 = 1:length(lineNos)
lineNosStr = sprintf('%s%d ', lineNosStr, lineNos(ii2));
end
lineNosStr = sprintf('%s]', lineNosStr(1:end-1));

% Build the file name part
fileName = fileNames{i};
argStr = sprintf('%s, ''%s'',%s', argStr, fileName, lineNosStr);

fileNamesStr = strtrim(sprintf('%s %s:%s',...
fileNamesStr, fileName, strrep(strtrim(lineNosStr), ' ', '&nbsp;')));
end

if ~isempty(argStr)
tooltip = sprintf('%s%s', tooltipStr, fileNamesStr);

% prepend the command names
checksumStr = lTraceInfo.getStoredChecksum;
buildDir = lTraceInfo.getBuildDir;
modelName = lTraceInfo.getOriginalModelName;
argStr = sprintf('''%s'',''%s'', ''%s''%s', buildDir, modelName, checksumStr, argStr);

% build the final command
cmd = sprintf('coder.profile.ExecHelper.codeHighlight(%s);', argStr);
icon = coder.profile.ExecTimeHelper.CodeIcon;
txt = sprintf('<img src="%s"/>', icon);
else
cmd = '';
txt = '';
tooltip = '';
end
end

function [txt, cmd, tooltip] = getMLCodeLink(lTraceInfo)
txt = '';
cmd = '';
tooltip = '';

if isempty(lTraceInfo)
return;
end
fileNames = lTraceInfo.getFileNames();
filePaths = lTraceInfo.getFilePaths();
allLineNos = lTraceInfo.getLineNumbers();
if isempty(fileNames) || isempty(filePaths) || isempty(allLineNos)
return;
end

fileName = fileNames{1};
fileFullPath = fullfile(filePaths{1}, fileName);
lineNumberStart = allLineNos{1}(1);
argStr = sprintf('''%s'', %d', fileFullPath, lineNumberStart);

cmd = sprintf('coder.profile.ExecStackHelper.codeMLHighlight(%s);', argStr);
icon = coder.profile.ExecTimeHelper.CodeIcon;
tooltip = sprintf("%s:%d", fileName, lineNumberStart);
txt = sprintf('<img src="%s"/>', icon);
end
end

methods(Access=private, Static)
function tagName = generateTag(number)
tagName = ['tag_stack_' num2str(number)];
h = findobj('Tag', tagName);
for i=1:length(h)
try
close(h(i));
catch ME %#ok<NASGU>
end
end
end

function [execProfBaseStr, execProfileObjFunction] = searchForObjInBaseWS(idParent)
% Identify variables in the base workspace
execProfBaseStr = [];
execProfileObjFunction = [];

whosBase = evalin('base', 'whos');
nWhos = length(whosBase);
[names{1:nWhos}] = deal(whosBase.name);
[classes{1:nWhos}] = deal(whosBase.class);

% Identify coder.profile.ExecutionStack objects
execTimeIdx = strcmp(classes, 'coder.profile.ExecutionStack');
namesExecTime = names(execTimeIdx);
for i = 1:length(namesExecTime)
execProfVarName = namesExecTime{i};
candidate = evalin('base', execProfVarName);
if candidate.getIdUint64==idParent
execProfBaseStr = execProfVarName;
execProfileObjFunction = [];
return
end
end
end
end
end