classdef (Hidden = true) ExecTimeHelper
%EXECTIMEHELPER provides helper functions for execution profile
%   reporting.
%
%   The methods in this class are undocumented and subject to change from one
%   release to the next.

%   Copyright 2010-2019 The MathWorks, Inc.
    
    properties (Constant=true, Access=public)
        CodeIcon = 'coder_profile_code_icon.png';
        SdiIcon = 'SimulationDataInspector_16.png';
        ApiIcon = 'membrane_16.png';
        HistogramIcon = 'histogram_16.png';
        PieIcon = 'pie_16.png';
        ResourcesFolder = fullfile(matlabroot, 'toolbox',  'coder', ...
                              'profile', 'resources');
    end
    
    properties (Constant=true, Access=private)
        SdiIconCheckedExistence = false;
        % Nano second resolution
        SampleTimeTolerance = 1e-9;
    end
    
    methods (Static=true, Access=private)
        function copySingleIcon(dstFolder, iconName)
            dst = fullfile(dstFolder, iconName);
            if ~isfile(dst)
                src = fullfile(coder.profile.ExecTimeHelper.ResourcesFolder, iconName);
                copyfile(src, dst);
                fileattrib(dst,'+w');
            end
        end
    end
    
    methods (Static=true)
        
        function copyAllIcons (htmlFile, sdiIcon)
            dstFolder = fileparts(htmlFile);
            coder.profile.ExecTimeHelper.copySingleIcon(dstFolder, ...
                coder.profile.ExecTimeHelper.CodeIcon);
            coder.profile.ExecTimeHelper.copySingleIcon(dstFolder, ...
                coder.profile.ExecTimeHelper.ApiIcon);
            if sdiIcon
                coder.profile.ExecTimeHelper.copySingleIcon(dstFolder, ...
                    coder.profile.ExecTimeHelper.SdiIcon);
            end
            coder.profile.ExecTimeHelper.copySingleIcon(dstFolder, ...
                coder.profile.ExecTimeHelper.HistogramIcon);
            coder.profile.ExecTimeHelper.copySingleIcon(dstFolder, ...
                coder.profile.ExecTimeHelper.PieIcon);
        end
        
        function lSdiIcon = getSdiIcon
            if ~coder.profile.ExecTimeHelper.SdiIconCheckedExistence
                sdiIconLocaltion = fullfile(...
                    coder.profile.ExecTimeHelper.ResourcesFolder, ...
                    coder.profile.ExecTimeHelper.SdiIcon);
                assert(isfile(sdiIconLocaltion), 'SDI icon must exist');
            end
            lSdiIcon = coder.profile.ExecTimeHelper.SdiIcon;
        end
        
        function lApiIcon = getApiIcon
            lApiIcon = coder.profile.ExecTimeHelper.ApiIcon;
        end
                
       function [url, hDlg] = codeHighlight(buildDir, modelName, checksumStr, varargin)
            
            url = '';
            hDlg = [];
            input = [];
            
            evaluatedChecksumStr = coder.profile.ProbeComponentRegistry ...
                .getTraceabilityChecksumForBuildDir(buildDir);
            
            errorMsg = '';
            if strcmp(evaluatedChecksumStr,  checksumStr)
                url = coder.internal.slcoderReport('hiliteCode', buildDir, varargin{:});
                cr = simulinkcoder.internal.Report.getInstance;
                if cr.features.profiling
                    input.title = 'Code Profiling';
                    data = cell([length(varargin{2}) 1]);
                    for i =1:length(varargin{2})
                       data(i) = {struct('file',varargin{1},'line',varargin{2}(i))};
                    end
                    input.data = data;
                    simulinkcoder.internal.util.highlightInCode(modelName, input);
                end
            else
                errorMsg = DAStudio.message...
                    ('CoderProfile:ExecutionTime:ProfilingStaleBuildDir', buildDir);
            end
            
            if ~isempty(errorMsg) 
                errTitle = DAStudio.message...
                    ('CoderProfile:ExecutionTime:CodeReportLinkFailedTitle'); 
                hDlg = errordlg(errorMsg, errTitle, 'modal');
            end
            
        end
        
        function [rows, rowIndent, footNotes] = getResultSectionContent ...
                (lReportFcn, sectionProfile, sectionProfileCompareTo, ...
                comparisonEnabled, displayInSeconds, scaleFactorArg, ...
                               formatArg, modelName, footNotes, actionLinks, ...
                               columns, simComp, depth, showFunctionName)
            
            if nargin<13
                depth = 0;
            end

            if isempty(footNotes)
                footNotes = containers.Map('KeyType', 'char', 'ValueType', 'any');
            end
            
            rows = {};
            rowIndent = [];
            
            if depth==0
                % No html for the root section
            else
                lUseAbsolutePathForIcons = false;
                [rowData, footNotes, noteHtml] = ...
                    coder.profile.getReportContentForSection...
                    (sectionProfile, displayInSeconds, formatArg, ...
                    scaleFactorArg, lReportFcn, footNotes, modelName, ...
                    actionLinks, columns, lUseAbsolutePathForIcons, ...
                    sectionProfileCompareTo, comparisonEnabled);
                matlabCommand = coder.profile.profGetMatlabCmd( ..., 
                            sectionProfile, simComp, lReportFcn, noteHtml, ...
                            showFunctionName);
                rowData = [matlabCommand, rowData];
                rows = [rows; rowData];
                rowIndent(end+1)=depth-1;
            end

            childSections = sectionProfile.profGetChildren;
            childSectionsCompareTo = [];
            if comparisonEnabled
                if isempty(sectionProfileCompareTo)
                    return;
                else
                    childSectionsCompareTo = sectionProfileCompareTo.profGetChildren;
                end
            end
            
            for i=1:length(childSections)
                if ~childSections(i).isPreemptionSection
                    internalSectionCompareTo = [];
                    if comparisonEnabled
                        t1 = childSections(i).getTraceInfo;
                        for j=1:length(childSectionsCompareTo)
                            t2 = childSectionsCompareTo(j).getTraceInfo;
                            if strcmp(t1.getName, t2.getName) && ...
                                    strcmp(t1.getCodeSectionName, t2.getCodeSectionName) && ...
                                    strcmp(t1.getPrimaryCallSiteSID, t2.getPrimaryCallSiteSID)
                                internalSectionCompareTo = childSectionsCompareTo(j);
                                break;
                            end
                        end
                    end
                    
                    [childRows, childRowIndent, footNotes] = ...
                        coder.profile.ExecTimeHelper ...
                        .getResultSectionContent(lReportFcn,...
                                                 childSections(i), ...
                                                 internalSectionCompareTo, ...
                                                 comparisonEnabled, ...
                                                 displayInSeconds, ...
                                                 scaleFactorArg, ...
                                                 formatArg, modelName, ...
                                                 footNotes, ...
                                                 actionLinks, ...
                                                 columns, ...
                                                 simComp, ...
                                                 depth+1, ...
                                                 showFunctionName);
                    rows = [rows; childRows];  %#ok<AGROW>
                    rowIndent = [rowIndent; childRowIndent]; %#ok<AGROW>
                end
            end
            
        end
        
        function htmlStr = formattedHtml(text, matlabCmd, tooltip, noteHtml, additionalName)
            
            if nargin < 4
                noteHtml = '';
            end
            if nargin < 5
                additionalName = '';
            end
            
            if ~isempty(tooltip)
                titleStr = sprintf(' title = "%s"', tooltip);
            else
                titleStr = '';
            end
            
            if ~isempty(matlabCmd)
                hrefStr = sprintf(' href = "matlab:%s"', matlabCmd);
            else
                hrefStr = '';
            end
            
            if isempty(additionalName)
                htmlStr = sprintf('<a%s%s>%s</a>', titleStr, hrefStr, text);
            else
                htmlStr = sprintf('<a%s%s>%s</a> (%s)', titleStr, hrefStr, text, additionalName);
            end

            if ~isempty(noteHtml)
                htmlStr = sprintf('%s %s', htmlStr, noteHtml);
            end
            
        end
        
        function [execProfBaseStr, execProfFullStr, execProfileObjFunction] = ...
                searchForExecProfObj(idParent, modelName, lReportFcn)
            
            % Identify variables in the base workspace
            whosBase = evalin('base', 'whos');
            nWhos = length(whosBase);
            [names{1:nWhos}] = deal(whosBase.name);
            [classes{1:nWhos}] = deal(whosBase.class);
            
            
            % Identify objects of type coder.profile.ExecutionTime
            execTimeIdx = strcmp(classes, 'coder.profile.ExecutionTime');
            namesExecTime = names(execTimeIdx);
            
            for i = 1:length(namesExecTime)
                execProfVarName = namesExecTime{i};
                candidate = evalin('base', execProfVarName);
                if candidate.getIdUint64==idParent
                    execProfBaseStr = execProfVarName;
                    execProfFullStr = execProfBaseStr;
                    execProfileObjFunction = [];
                    return
                end
            end
            
            %Now check call report type specific search method
            [execProfBaseStr, execProfFullStr, execProfileObjFunction ] = ...
                lReportFcn.searchForExecProfObj(idParent, modelName);
            
        end

         function [section, hDlg] = sectionHistogram (idParent, sectionNumber, isInSeconds, modelName, lReportFcn)
            
            hDlg = [];
            section = [];
        
            % Try to locate the ExecutionTime object
            [~, executionProfileFullStr, execProfileObjFunction] = coder.profile.ExecTimeHelper... 
                .searchForExecProfObj(idParent, modelName, lReportFcn);
        
            foundData = ~isempty(executionProfileFullStr);
        
            if ~foundData
                executionProfileFullStr = 'executionProfile';
            end
            
            sectionsCmdStr = sprintf('%s.Sections(%u)', ...
                    executionProfileFullStr, sectionNumber);
            if ~isempty(execProfileObjFunction)
                evalSectionsCmdStr = sprintf('getSectionProfile(%s, %u)', ...
                    execProfileObjFunction, sectionNumber);
            else
                evalSectionsCmdStr = sectionsCmdStr;
            end
        
            if foundData
                section = evalin('base', evalSectionsCmdStr);
                figureHandle = figure;
                if strcmp(isInSeconds, 'true')
                    data = section.ExecutionTimeInSeconds;
                else
                    data = section.ExecutionTimeInTicks;
                end
                histogram(data, 30, 'Normalization', 'probability');
                name = strrep(section.Name, '_', '\_');
                title(DAStudio.message('CoderProfile:ExecutionTime:ReportDistributionTitle', name));
                plotedit(figureHandle, 'setenabletools', 'off');
                xlabel(DAStudio.message('CoderProfile:ExecutionTime:ReportDistributionXLabel'));
                ylabel(DAStudio.message('CoderProfile:ExecutionTime:ReportDistributionYLabel'));
            else
                errTitle = DAStudio.message...
                    ('CoderProfile:ExecutionTime:ApiLinkFailedTitle'); 
                errMsg = DAStudio.message...
                         ('CoderProfile:ExecutionTime:ApiLinkFailedMsg', ...
                          sectionsCmdStr, executionProfileFullStr);
                hDlg = errordlg(errMsg, errTitle, 'modal');
            end
        end
        
        function hDlg = sectionPie (idParent, sectionNumber, modelName, lReportFcn)
            hDlg = [];
            
            % Try to locate the ExecutionTime object
            [~, executionProfileFullStr, execProfileObjFunction] = coder.profile.ExecTimeHelper... 
                .searchForExecProfObj(idParent, modelName, lReportFcn);
            
            foundData = ~isempty(executionProfileFullStr);
            
            if ~foundData
                executionProfileFullStr = 'executionProfile';
            end
            
            sectionsCmdStr = sprintf('%s.Sections(%u)', ...
                    executionProfileFullStr, sectionNumber);
            if ~isempty(execProfileObjFunction)
                evalSectionsCmdStr = sprintf('getSectionProfile(%s, %u)', ...
                    execProfileObjFunction, sectionNumber);
            else
                evalSectionsCmdStr = sectionsCmdStr;
            end
            
            if foundData
                mainSection = evalin('base', evalSectionsCmdStr);
                numSections = 1 + length(mainSection.profGetChildren);
                names = cell(1, numSections);
                name = strrep(mainSection.Name, '_', '\_');
                names{1} = name;
                expand = false(1, numSections);
                expand(1) = true;                
                
                avg_execs = zeros(1, numSections);
                max_execs = zeros(1, numSections);
                avg_execs(1) = double(mainSection.TotalSelfTimeInTicks)/double(mainSection.NumCalls);
                max_execs(1) = mainSection.MaximumSelfTimeInTicks;
                i = 2;
                children = mainSection.profGetChildren;
                for section = children
                    avg_execs(i) = double(section.TotalExecutionTimeInTicks)/...
                        double(section.NumCalls);
                    max_execs(i) = section.MaximumExecutionTimeInTicks;
                    names{i} = strrep(section.Name, '_', '\_');
                    i = i + 1;
                end
                figure;
                t = tiledlayout(2,1);
                ax1 = nexttile;
                pie(ax1, avg_execs, expand);
                legend(names, 'Location', 'eastoutside', 'Orientation', 'vertical');
                title(DAStudio.message('CoderProfile:ExecutionTime:ReportPieAvgTitle', name));
                ax2 = nexttile;
                pie(ax2, max_execs, expand);
                legend(names, 'Location', 'eastoutside', 'Orientation', 'vertical');
                title(DAStudio.message('CoderProfile:ExecutionTime:ReportPieMaxTitle', name));
                plotedit(t, 'setenabletools', 'off');
            else
                errTitle = DAStudio.message...
                    ('CoderProfile:ExecutionTime:ApiLinkFailedTitle');
                errMsg = DAStudio.message...
                    ('CoderProfile:ExecutionTime:ApiLinkFailedMsg', ...
                    sectionsCmdStr, executionProfileFullStr);
                hDlg = errordlg(errMsg, errTitle, 'modal');
            end
        end
        
        function [section, hDlg] = sectionDisplay(idParent, sectionNumber, modelName, lReportFcn)
            
            hDlg = [];
            section = [];
            
            % Try to locate the ExecutionTime object
            [~, executionProfileFullStr, execProfileObjFunction] = coder.profile.ExecTimeHelper... 
                .searchForExecProfObj(idParent, modelName, lReportFcn);
            
            foundData = ~isempty(executionProfileFullStr);
            
            if ~foundData
                executionProfileFullStr = 'executionProfile';
            end
            
            sectionsCmdStr = sprintf('%s.Sections(%u)', ...
                    executionProfileFullStr, sectionNumber);
            if ~isempty(execProfileObjFunction)
                fprintf('>> %s=%s;\n',executionProfileFullStr, execProfileObjFunction);
                evalSectionsCmdStr = sprintf('getSectionProfile(%s, %u)', ...
                    execProfileObjFunction, sectionNumber);
                displayPrefix = '';
            else
                displayPrefix = '>> ';
                evalSectionsCmdStr = sectionsCmdStr;
            end
            fprintf('%s%s\n', displayPrefix, sectionsCmdStr);

            
            if foundData 
                section = evalin('base', evalSectionsCmdStr);
                feval(@disp, section); %#ok<FVAL>
                commandwindow;
            else
                errTitle = DAStudio.message...
                    ('CoderProfile:ExecutionTime:ApiLinkFailedTitle'); 
                errMsg = DAStudio.message...
                         ('CoderProfile:ExecutionTime:ApiLinkFailedMsg', ...
                          sectionsCmdStr, executionProfileFullStr);
                hDlg = errordlg(errMsg, errTitle, 'modal');
            end
        end

        function [idSdi, hDlg] = sdiPlot(idParent, idSection, modelName, lReportFcn)

            engine = Simulink.sdi.Instance.engine;
            idSdi = engine.getSignalIDByMetaData(int2str(idSection));
            
            hDlg = [];

            if isempty(idSdi)
                % Try to locate the ExecutionTime object and import it into SDI
                executionProfileBaseStr = coder.profile.ExecTimeHelper...
                    .searchForExecProfObj(idParent, modelName, lReportFcn);
                
                if isempty(executionProfileBaseStr)
                    errTitle = DAStudio.message...
                        ('CoderProfile:ExecutionTime:SdiLinkFailedTitle');
                    errMsg = DAStudio.message...
                        ('CoderProfile:ExecutionTime:SdiLinkFailedMsg');
                    hDlg = 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 [apiText, apiMatlabCmd, apiTooltip] = ...
                getDataForApiLink(sectionProfile, modelName, lReportFcn, lUseAbsPath)
            
            [~, idParent] = sectionProfile.getIdUint64;
            sectionNumber = sectionProfile.Number;
            apiMatlabCmd = sprintf...
                (['coder.profile.ExecTimeHelper.sectionDisplay'...
                  '(uint64(%u), uint64(%u), ''%s'', %s);'], ...
                 idParent, sectionNumber, modelName, lReportFcn);
            icon = coder.profile.ExecTimeHelper.ApiIcon;
            if lUseAbsPath
                icon = fullfile(coder.profile.ExecTimeHelper.ResourcesFolder, icon);
            end
            apiText = ['<img src="' icon '"/>'];
            apiTooltip = DAStudio.message('CoderProfile:ExecutionTime:ApiLinkTooltip',...
                                          sectionProfile.Number);
        end
        
        function [hstText, hstMatlabCmd, hstTooltip] = ...
                getDataForHistogramLink(sectionProfile, modelName, displayInSeconds, lReportFcn, lUseAbsPath)
            
            if sectionProfile.NumCalls > 1
                [~, idParent] = sectionProfile.getIdUint64;
                sectionNumber = sectionProfile.Number;
                if displayInSeconds
                    isInSecond = 'true';
                else
                    isInSecond = 'false';
                end
            icon = coder.profile.ExecTimeHelper.HistogramIcon;
            if lUseAbsPath
                icon = fullfile(coder.profile.ExecTimeHelper.ResourcesFolder, icon);
            end
                hstMatlabCmd = sprintf...
                    (['coder.profile.ExecTimeHelper.sectionHistogram'...
                    '(uint64(%u), uint64(%u), ''%s'', ''%s'', %s);'], ...
                    idParent, sectionNumber, isInSecond, modelName, lReportFcn);
                hstText = ['<img src="' icon '"/>'];
                hstTooltip = DAStudio.message('CoderProfile:ExecutionTime:ReportDistributionTooltip', ...
                    sectionProfile.Number);
            else
                hstText = '';
                hstMatlabCmd = '';
                hstTooltip = '';
            end
        end
        
        function [pieText, pieMatlabCmd, pieTooltip] = getDataForPieLink(...
                sectionProfile, modelName, lReportFcn, lUseAbsPath)
            
            if ~isempty(sectionProfile.profGetChildren)
                [~, idParent] = sectionProfile.getIdUint64;
                sectionNumber = sectionProfile.Number;
                icon = coder.profile.ExecTimeHelper.PieIcon;
                if lUseAbsPath
                    icon = fullfile(coder.profile.ExecTimeHelper.ResourcesFolder, icon);
                end
                pieMatlabCmd = sprintf...
                    (['coder.profile.ExecTimeHelper.sectionPie'...
                    '(uint64(%u), uint64(%u), ''%s'', %s);'], ...
                    idParent, sectionNumber, modelName, lReportFcn);
                pieText = ['<img src="' icon '"/>'];
                pieTooltip = DAStudio.message('CoderProfile:ExecutionTime:ReportPieTooltip', ...
                    sectionProfile.Number);
            else
                pieText = '';
                pieMatlabCmd = '';
                pieTooltip = '';
            end
        end
        
        function [sdiText, sdiMatlabCmd, sdiTooltip] = ...
                getDataForSdiLink(sectionProfile, modelName, lReportFcn, lUseAbsPath)
            icon = coder.profile.ExecTimeHelper.getSdiIcon;
            if lUseAbsPath
                icon = fullfile(coder.profile.ExecTimeHelper.ResourcesFolder, icon);
            end
            sdiText = ['<img src="' icon '"/>'];
            [idSection, idParent] = sectionProfile.getIdUint64;
            sdiMatlabCmd = ...
                sprintf('coder.profile.ExecTimeHelper.sdiPlot(uint64(%u), uint64(%u), ''%s'', %s);', ...
                        idParent, idSection, modelName, lReportFcn);
            sdiTooltip = DAStudio.message('CoderProfile:ExecutionTime:SdiLinkTooltip');
        end

        function [name, avgUtil, tooltipAvg, maxUtil, tooltipMax] = ...
                      getTaskCapacity(section, rate, scaleFactor, lReportFcn, unit, numericFormat, simCmp, tps)
            name = coder.profile.profGetMatlabCmd(section, simCmp, lReportFcn, []);
            rateNorm = rate / scaleFactor;
            numCalls = double(section.NumCalls);
            avgExec = mean(double(section.TotalExecutionTimeInTicks) / numCalls) / tps;
            avgUtil = (avgExec / rate) * 100;
            maxExec = double(section.MaximumExecutionTimeInTicks) / tps;
            maxUtil = (maxExec / rate) * 100;
            avgExecNorm = avgExec / scaleFactor;
            tooltipAvg = DAStudio.message('CoderProfile:ExecutionTime:ReportAverageCPUUtilizationTooltip', ...
                sprintf([numericFormat '%s'], avgExecNorm, unit), ...
                sprintf([numericFormat '%s'], rateNorm, unit));
            maxExecNorm = maxExec/scaleFactor;
            tooltipMax = DAStudio.message('CoderProfile:ExecutionTime:ReportMaximumCPUUtilizationTooltip', ...
                sprintf([numericFormat '%s'], maxExecNorm, unit));
        end

        function [avgTotal, maxTotal] = getTotalCapacity(isSummaryOnly, sections, tps)
            avgTotal = 0;
            maxTotal = 0;
            for i=1:length(sections)
                section = sections(i);
                [isPeriodic, rate] = coder.profile.ExecTimeHelper.isPeriodicTask(section);
                if isPeriodic
                    if isSummaryOnly
                        numCalls = double(section.NumCalls);
                        avgData = double(section.TotalExecutionTimeInTicks) / numCalls;
                    else
                        avgData = mean(double(section.ExecutionTimeInTicks));
                    end
                    avgData = (avgData / tps) / rate;
                    avgTotal = avgTotal + avgData;

                    maxData = double(section.MaximumExecutionTimeInTicks);
                    maxData = (maxData / tps) / rate;
                    maxTotal = maxTotal + maxData;
                end
            end
            avgTotal = avgTotal * 100;
            maxTotal = maxTotal * 100;
        end

        function [res, rate] = isPeriodicTask (section)
            res = false;
            rate = '';
            t = section.getTraceInfo;
            if t.isTask
                rate = t.getSampleTime;
                rate = rate(1);
                if ~isinf(rate) && rate > 0
                    res = true;
                end
            end
        end

    end
end

