classdef ExecutionTime < handle
%EXECUTIONTIME provides access to execution profiling results 

%   Copyright 2010-2023 The MathWorks, Inc.

    properties (Constant=true, Access=private)
        % Handle object compatibility across releases
        CurrentObjectVersion = 1;
    end
    
    properties (Hidden=true, Access=private)
        TimerData;
        DoneInitialize=false;
        TimeCreated;
        SimTimeAvailable;
        DocumentationLinkParameters;
        SerializeObjectVersion;
        Modules;
        ParentModule;
    end
    
    properties (Hidden=true, Access = private, Transient=true)
        ExecutionTimelineController;        
        ExecutionScheduleController;
    end
    
    properties (Hidden=true, GetAccess=private, SetAccess=public)
        CodeDir;
        GlobalRegistry;
        RealTime;
        RealTimeXCP = false;
        InstrCodeSubFolder;
        PreviousResults;
        CheckScheduleSupport;
        IsSimulinkWorkflow = true;
    end

    properties (Hidden, Access = public)
        % true if the instance of this class is not created from a standard
        % XIL/XCP simulation with execution-time profiling
        IsSynthetic = false;
    end
    
    % Properties that must not be serialized
    properties (Hidden=true, GetAccess=private, SetAccess=public, Transient=true)
        ExecTimeReport;
    end
    
    properties (Dependent = true, Hidden=true, Access=public)
        ModelForProfilingTopSettings;
    end
    
    properties (Dependent = true, Hidden=true, GetAccess=private, SetAccess=public)
        CodeProfilingSaveOptions;
        OriginalComponentName;
    end
    
    properties (Dependent = true, Access=public)
        TimerTicksPerSecond;
    end

    properties (GetAccess=public, SetAccess=private)
        Sections;
    end
    
    properties (GetAccess=public, SetAccess=private, Hidden=true)
        IntermediateSIDs
        ReportFcn;
    end
    
    properties (Access=public)
        % 70% CPU utilization is the threshold for single-core targets
        % using Rate Monotonic as scheduler - it is an industry standard 
        % maximum
        CPUUtilization = 0.7;
    end
    
    properties (Access=public, Hidden=true)
        SimulationComponent;
        SimulationComponentSID;
        SimulationComponentAncestors;
        TimerHWCounterUnit = '';
        ShowCodeGenReportLink = true;
    end

    methods
        function set.CPUUtilization(this, val)
            arguments
                this (1,1) coder.profile.ExecutionTime
                val (1,1) {mustBePositive,mustBeNumeric}
            end
            this.CPUUtilization = val;
        end
        
        function lSections = get.Sections(this)

        % Make sure this object is initialized
            this.initialize;
            
            numSections = this.TimerData.getNumSectionNodes;
            
            lSections = coder.profile.ExecutionTimeSection.empty;
            
            for sectionNumber = 1:numSections
                lSections(sectionNumber) = ...
                  this.TimerData.getVisibleSectionProfile(sectionNumber);
            end

        end

        function set.CodeProfilingSaveOptions(this, val)
            this.TimerData.CodeProfilingSaveOptions = val;
        end
        
        function set.ModelForProfilingTopSettings(this, val)
            this.TimerData.ModelForProfilingTopSettings = val;
        end
        
        function val = get.ModelForProfilingTopSettings(this)
            val = this.TimerData.getModelForProfilingTopSettings;
        end
        
        function set.OriginalComponentName(this, val)
            this.TimerData.OriginalComponentName = val;
        end
        
        function val = get.OriginalComponentName(this)
            val = this.TimerData.getOriginalComponentName;
        end
        
        function set.TimerTicksPerSecond(this, val)
            for i=1:this.getModuleNumber()
                ex = this.getModule(i);
                ex.TimerData.TimerTicksPerSecond = val;
            end
        end
        
        function val = get.TimerTicksPerSecond(this)
            val = this.TimerData.getTimerTicksPerSecond;
        end
        
    end
    
    methods (Static=true, Access=private)
    
        function tableHtml = getTableHtml(headings, colWidthsInPercent, colAlignment, rows)
            idBackground = 1;
            backgroundColors = {'#eeeeff', '#ffffff'};
        
            % headers
            headingsHtml = '';
            for i=1:length(headings)
                headingHtml = sprintf(...
                    '<td width="%d%%" align="%s" style="border-style: none"><b>%s</b>\n</td>\n', ...
                    colWidthsInPercent(i), colAlignment{i}, headings{i});
                headingsHtml = sprintf('%s%s', headingsHtml, headingHtml);
            end
            if ~isempty(headingsHtml)
                headingsHtml = sprintf(...
                    '<tr style="background-color: %s">%s</tr>', ...
                    backgroundColors{idBackground}, headingsHtml);
                idBackground = 2;
            end
            
            % data
            rowsHtml = '';
            numCols = length(rows(1,:));
            for i=1:length(rows(:,1))
                cellsHtml = '';
                for ii1 =  1:numCols
                    cellContent = rows{i, ii1}; 
                    cellHtml = sprintf...
                              ('<td width="%d%%" align="%s" style="border-style: none">%s</td>\n', ...
                               colWidthsInPercent(ii1), colAlignment{ii1}, cellContent);
                    cellsHtml = sprintf('%s%s', cellsHtml, cellHtml);
                end

                rowHtml = sprintf('<tr style="border-style: none; background-color: %s">%s</tr>\n', ...
                                  backgroundColors{idBackground}, cellsHtml);
                idBackground = idBackground + 1;
                if idBackground > 2
                    idBackground = 1;
                end
                rowsHtml = sprintf('%s%s', rowsHtml, rowHtml);
            end

            % final table
            tableHtml = sprintf(...
                '<table width="100%%" cellpadding="2" border="0px">\n%s%s</table>\n', ...
                headingsHtml, rowsHtml);
        end
    end
            
    methods (Access=private)
         
        function sectionBaseline = getEquivalentSection(~, baselineObj, sectionToFind)
            sectionBaseline = [];
            if isa(baselineObj, 'coder.profile.SectionRoot')
                t1 = sectionToFind.getTraceInfo;
                childSectionsCompareTo = baselineObj.getChildren();
                for j=1:length(childSectionsCompareTo)
                    t2 = childSectionsCompareTo(j);
                    if strcmp(t1.getName, t2.getName) && ...
                            strcmp(t1.getCodeSectionName, t2.getCodeSectionName) && ...
                            strcmp(t1.getPrimaryCallSiteSID, t2.getPrimaryCallSiteSID)
                        sectionBaseline = childSectionsCompareTo(j);
                        break;
                    end
                end
            elseif ~isempty(baselineObj)
                for i = 1:length(baselineObj.Sections)
                    section = baselineObj.Sections(i);
                    t = section.getTraceInfo;
                    if t.isTask && strcmp(sectionToFind.Name, section.Name)
                        sectionBaseline = section;
                        break;
                    end
                end
            end
        end
        
        function color = getColor(~, current, baseline)
            if current > baseline
                color = 'red';
            elseif current < baseline
                color = 'green';
            else
                color = 'black';
            end
        end
    
        function internalTraceabilityUpdate(this, lProcessSIDs)
            if isempty(this.OriginalComponentName)
                % No traceability update required
                return;
            end
            
            this.initialize;
            
            if lProcessSIDs
                lTraceabilityHelper = coder.profile.TraceabilityHelper...
                    (this.CodeDir, this.OriginalComponentName, this);
            else
                lTraceabilityHelper = [];
            end
            
            lSections = this.Sections;
            for i = 1:length(lSections)
                lTraceInfo = getTraceInfo(lSections(i));
                resolveCallSites(lTraceInfo, lSections(i), lTraceabilityHelper);
            end                
            
            if lProcessSIDs
                % Capture SIDs of blocks that contain profiled blocks but 
                % are not themselves profiled
                this.IntermediateSIDs = getAllIntermediateSIDs(lTraceabilityHelper);  
                this.IntermediateSIDs = [coder.profile.TraceabilityHelper. ...
                        extendAncestors(this.SimulationComponentAncestors), ...
                        this.IntermediateSIDs];
            end
        end

        function lBuildDir = getBuildDir(this)
            lBuildDir = this.CodeDir;
        end
        
        function name = getReportName(~, timeCreatedStr, varargin)
            % Return a name for the report that uniquely encodes the time 
            % when the execution profile object was created and the 
            % settings for the report
            asInts = coder.profile.md5(timeCreatedStr, varargin{:});
            assert(length(asInts) == 4, 'Unexpected length for MD5 checksum');
            hash = '';
            for i = 1:length(asInts)
                currInt = asInts(i);
                currHex = lower(dec2hex(currInt, 8));
                
                % Need to reverse the hex pairwise
                hash = [hash, currHex(7), currHex(8), ...
                        currHex(5), currHex(6), ...
                        currHex(3), currHex(4), ...
                        currHex(1), currHex(2)]; %#ok<AGROW>
            end
            name = sprintf('ExecutionProfiling_%s.html', hash(1:2:end));
        end

        
        function fileName = getReportFileName(this, varargin)
            if isempty(dir(this.getBuildDir))
                reportFolder = pwd;
            else
                reportFolder = this.getBuildDir;
            end
            
            reportFolder = fullfile(reportFolder, 'html');
            if isempty(dir(reportFolder))
                mkdir(reportFolder);
            end
            
            this.initialize;
            
            if ~this.GlobalRegistry.isTraceabilityValid
                reportFolder = fullfile(reportFolder, 'orphaned');
                if exist(reportFolder,'dir') ~= 7
                    mkdir(reportFolder)
                end
            end
            
            fileName = fullfile...
                (reportFolder, ...
                 this.getReportName...
                 (this.TimeCreated, varargin{:}));
        end
        
        function doVersionCheck(this)
            if isempty(this.SerializeObjectVersion) || ...
                    (this.SerializeObjectVersion < ...
                    coder.profile.ExecutionTime.CurrentObjectVersion)
                DAStudio.error('CoderProfile:ExecutionTime:InvalidProbesDataFormat');
            end
        end
            
        function initialize(this)
            doVersionCheck(this);
            
            if ~this.DoneInitialize
                this.GlobalRegistry.updateTraceInfo...
                    (this.InstrCodeSubFolder);
                this.TimerData.initialize(this.GlobalRegistry);
                this.DoneInitialize = true;
            end
        end
    
        function tableHtml = getSummarySectionHtml(this, displayInSeconds, ...
                objectName, unitArg, unitSIArg, scaleFactorArg, formatArg, ...
                baselineObjectName, baselineObj)
            % Make sure this object is initialized
            this.initialize;

            if ~this.DoneInitialize
                this.initialize;
            end           

            rows = {};
            
            if displayInSeconds
                ticksPerSecond = this.TimerTicksPerSecond;
            else
                ticksPerSecond = 0;
            end
            
            % Total time
            if ~this.TimerData.isOnTargetData && ~this.RealTimeXCP
               totalTimeTooltip = DAStudio.message('CoderProfile:ExecutionTime:ReportTotalTimeTooltip');
               if ~isempty(baselineObj)
                    if isa(baselineObj, 'coder.profile.SectionRoot')
                        totalTimeDiff = double(this.TimerData.getTotalExecutionTime) - ...
                            double(baselineObj.getAverExecTime * baselineObj.getNumCalls);
                        totalTimeDiff = totalTimeDiff / double(baselineObj.getAverExecTime * baselineObj.getNumCalls);
                    else
                        totalTimeDiff = double(this.TimerData.getTotalExecutionTime) - ...
                            double(baselineObj.TimerData.getTotalExecutionTime);
                        totalTimeDiff = totalTimeDiff / double(baselineObj.TimerData.getTotalExecutionTime);
                    end
                   totalTimeDiff = totalTimeDiff * 100;
                   if totalTimeDiff >= 0
                       symbol = '+';
                   else
                       symbol = '-';
                   end
                   totalTimeDiffStr = sprintf(' (%s%2.2f%%)', symbol, abs(totalTimeDiff));
               else
                   totalTimeDiffStr = '';
               end
               if displayInSeconds
                   scaleFactor = str2double(scaleFactorArg);
                   timeInSeconds = double(this.TimerData.getTotalExecutionTime) / ...
                       scaleFactor / double(ticksPerSecond);
                   totalTimeValStr = sprintf(['<a>' formatArg '%s</a>'], timeInSeconds, totalTimeDiffStr);
               else
                   totalTimeValStr = sprintf(['<a>' formatArg '%s</a>'], ...
                       this.TimerData.getTotalExecutionTime, totalTimeDiffStr);
               end
               totalTimeText = DAStudio.message...
                   ('CoderProfile:ExecutionTime:ReportTotalTime');
               totalTimeStr = sprintf('<a title="%s">%s</a>', ...
                   totalTimeTooltip, totalTimeText);
               rows(end+1,1:2) = {totalTimeStr, totalTimeValStr};
            end
            
            % Time unit
            timeUnitTooltip = DAStudio.message('CoderProfile:ExecutionTime:ReportTimeUnitTooltip');
            if isempty(unitSIArg)
                if strcmp(unitArg, DAStudio.message('CoderProfile:ExecutionTime:ReportUnitsTimerTicks'))
                    unitArg = ...
                        DAStudio.message('CoderProfile:ExecutionTime:ReportCapitalUnitsTimerTicks');
                elseif strcmp(unitArg, DAStudio.message('CoderProfile:ExecutionTime:ReportUnitsSeconds'))
                    unitArg = ...
                        DAStudio.message('CoderProfile:ExecutionTime:ReportCapitalUnitsSeconds');
                else
                    % no action: keep the same unit (e.g.: HW counters)
                end
                totalTimeValueStr = sprintf('<a>%s</a>', unitArg);
            else
                totalTimeValueStr = sprintf('<a>%s</a>', unitSIArg);
            end
            
            timeUnitText = DAStudio.message('CoderProfile:ExecutionTime:ReportTimeUnit');
            totalTimeStr = sprintf('<a title="%s">%s</a>', ...
                                   timeUnitTooltip, timeUnitText);
            rows(end+1,1:2) = {totalTimeStr, totalTimeValueStr};
            
            % Report command line
            if ~isempty(objectName)
                commandLineText = DAStudio.message...
                    ('CoderProfile:ExecutionTime:ReportCommandLine');
                commandLineTooltip = DAStudio.message...
                    ('CoderProfile:ExecutionTime:ReportCommandLineTooltip');
                commandLineStr = sprintf('<a title="%s">%s</a>', ...
                    commandLineTooltip, commandLineText);
                commandLineValueStr = this.getReportCommand(displayInSeconds, ...
                    objectName, unitArg, scaleFactorArg, formatArg, baselineObjectName);
                rows(end+1,1:2) = {commandLineStr, ['<a>' commandLineValueStr '</a>']};
                if ~isempty(baselineObj)
                    % command to open baseline profiling report
                    commandLineText = DAStudio.message...
                        ('CoderProfile:ExecutionTime:ReportBaselineReportCommand');
                    commandLineTooltip = DAStudio.message...
                        ('CoderProfile:ExecutionTime:ReportBaselineReportCommandTooltip');
                    commandLineStr = sprintf('<a title="%s">%s</a>', commandLineTooltip, commandLineText);
                    commandLineValueStr = this.getReportCommand(displayInSeconds, ...
                        baselineObjectName, unitArg, scaleFactorArg, formatArg);
                    hyperlink = coder.profile.ExecTimeHelper.formattedHtml(...
                        commandLineValueStr, commandLineValueStr, '');
                    rows(end+1,1:2) = {commandLineStr, hyperlink};
                end
            end
            
            % Timer frequency, ticks per second
            ticksPerSecText = DAStudio.message('CoderProfile:ExecutionTime:ReportTimerFrequency');
            ticksPerSecTooltip = DAStudio.message('CoderProfile:ExecutionTime:ReportTimerFrequencyTooltip');
            if displayInSeconds
                ticksPerSecValStr = sprintf('<a>%g</a>', ticksPerSecond);
            else
                ticksPerSecValStr = sprintf...
                    ('<a>%s</a>',DAStudio.message('CoderProfile:ExecutionTime:ReportTimerFrequencyUnavailable'));
            end
            ticksPerSecStr = sprintf('<a title="%s">%s</a>', ...
                                     ticksPerSecTooltip, ticksPerSecText);  
            rows(end+1, 1:2) = {ticksPerSecStr, ticksPerSecValStr};

            timeCreatedText = DAStudio.message('CoderProfile:ExecutionTime:ReportCreated');
            timeCreatedTooltip = DAStudio.message('CoderProfile:ExecutionTime:ReportCreatedTooltip');
            timeCreatedStr = sprintf('<a title="%s">%s</a>', ...
                                   timeCreatedTooltip, timeCreatedText);
            rows(end+1,1:2) = {timeCreatedStr, this.TimeCreated};

            if ~isempty(baselineObj)
                tooltip = DAStudio.message('CoderProfile:ExecutionTime:ReportCreatedBaselineTooltip');
                text = DAStudio.message('CoderProfile:ExecutionTime:ReportCreatedBaseline');
                baselineTimeCreatedStr = sprintf('<a title="%s">%s</a>', tooltip, text);
                rows(end+1,1:2) = {baselineTimeCreatedStr, baselineObj.getTimeCreated};
            end

            headings = {};
            colWidthsInPercent = [50 50];
            colAlignment = {'left', 'left'};
            tableHtml = coder.profile.ExecutionTime ...
                .getTableHtml(headings, colWidthsInPercent,...
                                        colAlignment, rows);
        end
        
        function commandLineValueStr = getReportCommand(~, displayInSeconds, ...
                objectName, unitArg, scaleFactorArg, formatArg, baselineObjName)
            
            if nargin < 7 || isempty(baselineObjName)
                baselineCmd = '';
            else
                baselineCmd = sprintf(', ''baseline'', %s', baselineObjName);
            end
            
            if displayInSeconds
                commandLineValueStr = sprintf...
                    ('report(%s, ''Units'', ''%s'', ''ScaleFactor'', ''%s'', ''NumericFormat'', ''%s''%s);',...
                    objectName, unitArg, scaleFactorArg, formatArg, baselineCmd);
            elseif strcmpi(unitArg, 'ticks')
                commandLineValueStr = sprintf('report(%s, ''Units'', ''%s''%s);', objectName, unitArg, baselineCmd);
            else
                % HW counters - use the internal type
                commandLineValueStr = sprintf('report(%s%s);', objectName, baselineCmd);
            end
        end
        
        function definitionHtml = getDefinitionSectionHtml(~, definitionList)
            definitionHtml = '';
            keys = definitionList.keys;
            for i=1:length(keys)
                k = keys{i};
                definitionHtml = sprintf('%s<p><b><a>%s</a></b>: %s</p>',...
                    definitionHtml, k, definitionList(k));
            end
        end
        
        function sectionHtml = getAllReportSection(this, modelName, lReportFcn)
            numModules = this.getModuleNumber();
            rows = cell(numModules, 2);

            for i=1:numModules
                module = this.getModule(i);
                sections = module.Sections;
                if isempty(sections)
                    continue;
                end
                [~, idParent] = sections(1).getIdUint64();
                [blkHtml, lnkHtml] = ...
                    coder.profile.ExecTimeHelper.getReportLinkSection(...
                        module, idParent, i, modelName, lReportFcn);
                rows{i,1} = blkHtml;
                if strcmp(module.SimulationComponent, this.SimulationComponent)
                    rows{i,2} = DAStudio.message('CoderProfile:ExecutionTime:ProfReportThisReportStr');
                else
                    rows{i,2} = lnkHtml;
                end
            end
            
            headings = {};
            colWidthsInPercent = [50 50];
            colAlignment = {'left', 'left'};
            tableHtml = coder.profile.ExecutionTime.getTableHtml(...
                headings, colWidthsInPercent, colAlignment, rows);
            intro = DAStudio.message('CoderProfile:ExecutionTime:ProfReportComponentLinks');
            sectionHtml = sprintf('<p>%s</p>%s', intro, tableHtml);
        end
        
        function [resultsHtml, definitionList] = getResultsSectionHtml...
                (this, rpt, displayInSeconds, definitionList, ...
                unitsArg, unitsSIArg, scaleFactorArg, formatArg, ...
                modelName, showFunctionName)
            
            import coder.profile.ExecTimeHelper
            
            % Use a method in the helper class to get data for the report
            % as it is guaranteed only to use public methods on this class
            rootSection = this.getRootSection;
            footNotes = [];
            fullData = this.isTimeSeriesDataAvailable();
            actionLinks.sdi = fullData;
            actionLinks.histogram = fullData;
            actionLinks.pieChart = this.isInternalDistributionAvailable();
            actionLinks.codeLink = this.ShowCodeGenReportLink;
            
            columns = this.getResultColumns([]);
            sectionToCompare = [];
            comparisonEnabled = false;
            [rows, rowIndent, footNotes]  = ...
                coder.profile.ExecTimeHelper.getResultSectionContent...
                (this.ReportFcn, rootSection, ...
                sectionToCompare, comparisonEnabled, ... % no baseline
                displayInSeconds, scaleFactorArg, ...
                formatArg, modelName, footNotes, actionLinks, columns, ...
                this.SimulationComponent, 0, showFunctionName);
            
            [headings, definitionList] = coder.profile.profReportColumnHeadings...
                (columns, unitsArg, unitsSIArg, definitionList);
            
            if this.TimerData.isOnTargetData
                colWidths = [23 10 10 7 10];
                key = DAStudio.message('CoderProfile:ExecutionTime:ColumnHeadingCalls');
                definitionList(key) = DAStudio.message('CoderProfile:ExecutionTime:NumCallsOnTargetDefinition');
            else
                colWidths = [23 10 10 10 10 7 10];
            end
            
            colWidthsInPercent = colWidths/sum(colWidths)*100;
            colAlignment = {'left', 'right', 'right', 'right', 'right', 'right', 'center'};
            
            tableId = 'Code Execution Profiling Report';
            tableHTML = rpt.generateTableTree(headings, rows, rowIndent,...
                                              colWidthsInPercent, colAlignment, ...
                                              tableId, 0, 'results');
            
            if footNotes.length > 0
                notesHtml = '<br><p><b>Notes:</b></p>';
                values = footNotes.values;
                numbers = zeros(size(values));
                for i=1:length(numbers)
                    numbers(i) = values{i}.Number;
                end
                [numbers, sortIdx] = sort(numbers);
                values =  values(sortIdx);
                for i=1:length(numbers)
                    notesHtml = sprintf('%s&#91;<a name = "%s">%d</a>&#93; %s<br>',...
                                        notesHtml, ...
                                        values{i}.Ref, values{i}.Number, ....
                                        values{i}.Text);
                end
                resultsHtml = sprintf('%s\n%s', tableHTML, notesHtml);
            else
                resultsHtml = tableHTML;
            end     
                
            linksStyle = sprintf([...
                '<style>\n'...
                'table.links_fixed {table-layout:fixed; width:100px; background-color:transparent}\n'...
                'table.links_fixed td:nth-of-type(1) {width:20px;}/*Setting the width of column 1.*/\n'...
                'table.links_fixed td:nth-of-type(2) {width:20px;}/*Setting the width of column 2.*/\n'...
                'table.links_fixed td:nth-of-type(3) {width:20px;}/*Setting the width of column 3.*/\n'...
                'table.links_fixed td:nth-of-type(4) {width:20px;}/*Setting the width of column 4.*/\n'...
                'table.links_fixed td:nth-of-type(5) {width:20px;}/*Setting the width of column 5.*/\n'...
                '</style>    \n']);
            
            resultsHtml = sprintf('%s%s', resultsHtml, linksStyle);
        end
            
        function [tableHTML, definitionList] = getFunctionPercentageSectionHtml...
                (this, rpt, definitionList)
            
            simComp = this.SimulationComponent;
            isOnTargetData = this.TimerData.isOnTargetData();
            [rows, rowIndent]  = ...
                coder.profile.ExecTimeHelper.getPercentageSectionContent...
                (this.ReportFcn, this.Sections, simComp, ...
                double(this.TimerData.getTotalExecutionTime()), ...
                isOnTargetData);
            
            headings = cell(1,5);
            headings{1} = DAStudio.message('CoderProfile:ExecutionTime:PercHeaderSection');
            headings{2} = sprintf('<a title="%s">%s</a>', ...
                DAStudio.message('CoderProfile:ExecutionTime:PercHeaderSelfWrtCallerTooltip'), ...
                DAStudio.message('CoderProfile:ExecutionTime:PercHeaderSelfWrtCaller'));
            headings{3} = sprintf('<a title="%s">%s</a>', ...
                DAStudio.message('CoderProfile:ExecutionTime:PercHeaderSelfWrtTaskTooltip'), ...
                DAStudio.message('CoderProfile:ExecutionTime:PercHeaderSelfWrtTask'));
            headings{4} = sprintf('<a title="%s">%s</a>', ...
                DAStudio.message('CoderProfile:ExecutionTime:PercHeaderSelfWrtTotalTooltip'), ...
                DAStudio.message('CoderProfile:ExecutionTime:PercHeaderSelfWrtTotal'));
            headings{5} = sprintf('<a title="%s">%s</a>', ...
                DAStudio.message('CoderProfile:ExecutionTime:PercHeaderExecWrtTotalTooltip'), ...
                DAStudio.message('CoderProfile:ExecutionTime:PercHeaderExecWrtTotal'));
    
            colWidths = [20, 20, 20, 20, 20];
            colAlignment = {'left', 'right', 'right', 'right', 'right'};
            
            if isOnTargetData
                headings(2:4) = [];
                rows(:,2:4) = [];
                colWidths(2:4) = [];
                colAlignment(2:4) = [];
            end
            
            colWidthsInPercent = colWidths/sum(colWidths)*100;
            tableId = 'Percentage contribution';
            tableHTML = rpt.generateTableTree(headings, rows, rowIndent,...
                                              colWidthsInPercent, colAlignment, ...
                                              tableId, 0, 'percentage');
        end
        
        function [resultsHtml, definitionList] = getCompareResultsSectionHtml...
                (this, rpt, displayInSeconds, definitionList, ...
                unitsArg, unitsSIArg, scaleFactorArg, formatArg, modelName, baselineObj)
            import coder.profile.ExecTimeHelper
            
            lColumnsToDiscard = [coder.profile.ResultColumns.Calls, ...
                                 coder.profile.ResultColumns.Links];
            columns = this.getResultColumns(lColumnsToDiscard);
            
            rootSection = this.getRootSection;
            if isa(baselineObj, 'coder.profile.SectionRoot')
                rootSectionBaseline = baselineObj;
            else
                rootSectionBaseline = baselineObj.getRootSection;
            end
            comparisonEnabled = ~isempty(rootSectionBaseline);            
            actionLinks.sdi = false;
            actionLinks.histogram = false;
            actionLinks.pieChart = false;
            actionLinks.codeLink = this.ShowCodeGenReportLink;
            
            showFunctionName = false;
            lFootNotes = [];
            [rows, rowIndent]  = ...
                coder.profile.ExecTimeHelper.getResultSectionContent...
                (this.ReportFcn, rootSection, ...
                rootSectionBaseline, comparisonEnabled, ...
                displayInSeconds, scaleFactorArg, ...
                formatArg, modelName, lFootNotes, actionLinks, columns, ...
                this.SimulationComponent, 0, showFunctionName);
            
            [headings, definitionList] = coder.profile.profCompareReportColumnHeadings...
                (columns, unitsArg, unitsSIArg, definitionList);
            
            if this.TimerData.isOnTargetData
                colWidths = [25 10 10];
                colAlignment = {'left', 'right', 'right'};
            else
                colWidths = [25 10 10 10 10];
                colAlignment = {'left', 'right', 'right', 'right', 'right'};
            end
            colWidthsInPercent = colWidths/sum(colWidths)*100;
            
            
            tableId = 'Code Execution Profiling Comparison';
            resultsHtml = rpt.generateTableTree(headings, rows, rowIndent,...
                                              colWidthsInPercent, colAlignment, ...
                                              tableId, length(this.Sections), 'comparison');
        end
        
        function res = validateComparisonInput (this, execProf)
            res = false;
            if ~isempty(this.SimulationComponent)
                nameOrg = this.SimulationComponent;
                nameCmp = execProf.SimulationComponent;
            else 
                nameOrg = this.OriginalComponentName;
                nameCmp = execProf.OriginalComponentName;
            end
            if ~(isempty(nameOrg) && isempty(nameCmp)) && ~strcmp(nameOrg, nameCmp)
                MSLDiagnostic('CoderProfile:ExecutionTime:ReportBaselineMismatch', 'model').reportAsWarning;
                return;
            end
            if this.CurrentObjectVersion ~= execProf.CurrentObjectVersion
                MSLDiagnostic('CoderProfile:ExecutionTime:ReportBaselineMismatch', 'version').reportAsWarning;
                return;
            end    
            if this.RealTime ~= execProf.RealTime || this.RealTimeXCP ~= execProf.RealTimeXCP
                MSLDiagnostic('CoderProfile:ExecutionTime:ReportBaselineMismatch', 'RealTime').reportAsWarning;
                return;
            end
            if this.SimTimeAvailable ~= execProf.SimTimeAvailable
                MSLDiagnostic('CoderProfile:ExecutionTime:ReportBaselineMismatch', 'SimTime').reportAsWarning;
                return;
            end
            % if TimerTicksPerSecond is empty, the unit of time is ticks,
            % seconds otherwise
            lDifferentUnitOfTime = isempty(this.TimerTicksPerSecond) ~= isempty(execProf.TimerTicksPerSecond);
            if lDifferentUnitOfTime
                MSLDiagnostic('CoderProfile:ExecutionTime:ReportBaselineMismatch', 'TicksPerSecond').reportAsWarning;
                return;
            end
            % if the unit is in second, check that the TicksPerSecond values are
            % the same
            if ~isempty(this.TimerTicksPerSecond) && (this.TimerTicksPerSecond ~= execProf.TimerTicksPerSecond)
                MSLDiagnostic('CoderProfile:ExecutionTime:ReportBaselineMismatch', 'TicksPerSecond').reportAsWarning;
                return;
            end
            isHWMetricsEmpty = isempty(this.TimerHWCounterUnit);
            if isHWMetricsEmpty ~= isempty(execProf.TimerHWCounterUnit) || ...
                   (~isHWMetricsEmpty && ...
                       ~strcmp(this.TimerHWCounterUnit, execProf.TimerHWCounterUnit))
                MSLDiagnostic('CoderProfile:ExecutionTime:ReportBaselineMismatch', 'unit').reportAsWarning;
                return;
            end
            if ~strcmp(this.ReportFcn, execProf.ReportFcn)
                MSLDiagnostic('CoderProfile:ExecutionTime:ReportBaselineMismatch', 'report function').reportAsWarning;
                return;
            end
            res = true;
        end
        
        function out = isSimulinkInstalled(~)
            persistent simulinkInstalled;
            if isempty(simulinkInstalled)
                simulinkInstalled = license('test', 'SIMULINK') && exist('sl_refresh_customizations','file');
            end
            out = simulinkInstalled;
        end
    end    
    
    methods (Access=public, Hidden=true)
        
        function attachExecutionProfile(this, profVar)
            if isempty(this.ParentModule)
                % this is the parent ExecutionProfile object: add the
                % children in this instance
                if isempty(this.Modules)
                    this.Modules = profVar;
                else
                    this.Modules(end+1) = profVar;
                end
                profVar.setParentModule(this);
            else
                % store ExecutionProfile object in the parent
                this.ParentModule.attachExecutionProfile(profVar);
            end
        end
        
        function setParentModule(this, profVar)
            this.ParentModule = profVar;
        end
        
        function traceabilityUpdate(this)
            lProcessSIDs = true;
            this.internalTraceabilityUpdate(lProcessSIDs);
            end
            
        function traceabilityUpdateWithoutSIDs(this)
            lProcessSIDs = false;
            this.internalTraceabilityUpdate(lProcessSIDs);
        end
        
        function columns = getResultColumns(this, removeColumns)
        
            allColumns = [
                coder.profile.ResultColumns.Model
                coder.profile.ResultColumns.MaxTurnaround
                coder.profile.ResultColumns.AverageTurnaround
                coder.profile.ResultColumns.MaxRealTimeExecution
                coder.profile.ResultColumns.MaxNonRealTimeExecution
                coder.profile.ResultColumns.AverageRealTimeExecution
                coder.profile.ResultColumns.AverageNonRealTimeExecution
                coder.profile.ResultColumns.MaxSelf
                coder.profile.ResultColumns.AverageSelf
                coder.profile.ResultColumns.Calls
                coder.profile.ResultColumns.Links
                         ];
            
            if this.RealTime
                % Self time is useful but no space to include self time as well as turnaround
                % time
                columnsToDiscard = [
                    coder.profile.ResultColumns.MaxNonRealTimeExecution
                    coder.profile.ResultColumns.AverageNonRealTimeExecution
                    coder.profile.ResultColumns.MaxSelf
                    coder.profile.ResultColumns.AverageSelf
                                   ];
            else
                % With no preemption, turnaround time and execution time are the same
                columnsToDiscard = [
                    coder.profile.ResultColumns.MaxRealTimeExecution
                    coder.profile.ResultColumns.AverageRealTimeExecution
                    coder.profile.ResultColumns.MaxTurnaround;
                    coder.profile.ResultColumns.AverageTurnaround;
                                   ];
            end 
            
            if this.TimerData.isOnTargetData
                columnsToDiscard(end+1) = coder.profile.ResultColumns.MaxSelf;
                columnsToDiscard(end+1) = coder.profile.ResultColumns.AverageSelf;
                columnsToDiscard(end+1) = coder.profile.ResultColumns.MaxTurnaround;
                columnsToDiscard(end+1) = coder.profile.ResultColumns.AverageTurnaround;
            end
            
            for i=1:length(removeColumns)
                columnsToDiscard(end+1) = removeColumns(i); %#ok<AGROW>
            end
            
            [~, ic] = setdiff(allColumns, columnsToDiscard);
            keepIdx = false(size(allColumns));
            keepIdx(ic) = true;
            columns = allColumns(keepIdx);
        end
        
        function lTimerData = getTimerData(this)
            lTimerData = this.TimerData;
        end
        
        function lTimeCreated = getTimeCreated(this)
            lTimeCreated = this.TimeCreated;
        end
        
        function varargout =  plot(this)
            [varargout{1:nargout}] = this.timeline;
        end
        
        function lOnTarget = IsOnTargetData(this)
            lOnTarget = this.TimerData.isOnTargetData;
        end

        function id = getIdUint64(this)
            % Get the 64-bit identifier for this execution profile object
            id = this.TimerData.getIdUint64;
        end
        
        function val = isTimeSeriesDataAvailable(this) 
            val = ~this.TimerData.isSummaryDataOnly && this.SimTimeAvailable;
        end
        
        function val = isInternalDistributionAvailable(this)
            val = ~this.TimerData.isOnTargetData && ~this.RealTime;
        end
        
        function len = getNumSectionProfiles(this)
            % Make sure this object is initialized
            this.initialize;
            len = this.TimerData.getNumSectionNodes;
        end
        

        function sectionProfile = getSectionProfile(this, sectionNumber)
            validateattributes(sectionNumber,...
                               {'numeric'},{'positive','real','scalar'}, ...
                               'coder.profile.ExecutionTime.getSectionProfile',...
                               'sectionNumber',2);
        
            % Make sure this object is initialized
            this.initialize;
            
            numSections = this.TimerData.getNumSectionNodes;     
            if (sectionNumber<0 || sectionNumber>numSections || ...
                round(sectionNumber)~=sectionNumber) 
                DAStudio.error('CoderProfile:ExecutionTime:ExecProfSectionNumRangeCheck', ...
                               sprintf('%d', sectionNumber), ...
                               sprintf('%d', numSections),...
                               sprintf('%d', numSections));
            end
            sectionProfile=this.TimerData.getVisibleSectionProfile(sectionNumber);
        end
        
        function val = getTimerTicksPerSecond(this)
        % Retained for backwards compatibility
            val = this.TimerTicksPerSecond;
        end
        
        function setTimerTicksPerSecond(this, val)
        % Retained for backwards compatibility
            this.TimerTicksPerSecond = val;
        end
        
        function rootSection = getRootSection(this)
            % Make sure this object is initialized
            this.initialize;
            rootSection=this.TimerData.getSectionProfile(0);
        end
        
        function taskDisplay(this)
            lSectionProfiles = this.Sections;
            numProf = length(lSectionProfiles);
            
            assert(numProf~=0,'Already checked number of sections not zero');
            
            sectionLabels=cell(numProf,1);
            sectionNumbers=cell(numProf,1);
            ticks=cell(numProf,1);
            strLenMax=0;
            for i=1:length(lSectionProfiles)
                sectionProfile = lSectionProfiles(i);
                dispStr = sprintf('%d.',i);
                sectionNumbers{i} = dispStr;
                
                dispStr = sectionProfile.Name;

                sectionLabels{i}=dispStr;
                ticks{i}=sectionProfile.ExecutionTimeInTicks;
                if length(dispStr)>strLenMax
                    strLenMax=length(dispStr);
                end
            end
            
            numFieldWidth = length(sprintf('%d', numProf));
            strLenMax = strLenMax+numFieldWidth + 2;

            ticksPerSecond=this.TimerTicksPerSecond;
            
            codeSectionStr = 'Code Section';
            minStr = DAStudio.message('CoderProfile:ExecutionTime:ExecutionProfilingMinimum');
            avStr = DAStudio.message('CoderProfile:ExecutionTime:ExecutionProfilingAverage');
            maxStr = DAStudio.message('CoderProfile:ExecutionTime:ExecutionProfilingMaximum');
            
            numericWidth = 13;
            colWidths = [max(strLenMax,length(codeSectionStr))+3 ...
                         max(numericWidth, length(minStr))+1 ...
                         max(numericWidth, length(avStr))+1 ...
                         max(numericWidth, length(maxStr))+1 ];
            colStarts = zeros(size(colWidths));
            colStarts(1)=1;
            for i=2:length(colStarts)
                colStarts(i)=colStarts(i-1)+colWidths(i-1);
            end
            
            dispStr = ' ';
            dispStr = repmat(dispStr,numProf+2, sum(colWidths));
            
            % Insert the title
            dispStr(1, colStarts(2):colStarts(2)+length(minStr)-1)=minStr;
            dispStr(1, colStarts(3):colStarts(3)+length(avStr)-1)=avStr;
            dispStr(1, colStarts(4):colStarts(4)+length(maxStr)-1)=maxStr;
            
            % Insert the section labels and data
            for  i=1:numProf
                sectionNumber = sectionNumbers{i};
                dispStr(i+2, 1:length(sectionNumber))=sectionNumber;
                sectionLabel = sectionLabels{i};
                len = length(sectionLabel);
                startIdx=colStarts(2)-len-3;
                dispStr(i+2,(startIdx:(startIdx+len-1)))=sectionLabel;
                dispStr(i+2, colStarts(2)-2)= ':';
                
                profile = ticks{i};
                
                if ~isempty(ticksPerSecond)
                    profile=double(double(profile)/ticksPerSecond);
                end
                profile_average = mean(profile);
                dataType=class(profile);
                eval(['profile_average = ' dataType '(profile_average);']); %#ok<EVLEQ>
                if isfloat(profile)
                    format='%11g';
                else
                    format='%11d';
                end
                minStr = sprintf(format, min(profile)); 
                avStr = sprintf(format, profile_average);
                maxStr = sprintf(format, max(profile));
                minStr = strtrim(minStr);
                avStr = strtrim(avStr);
                maxStr = strtrim(maxStr);
                
                dispStr(i+2, colStarts(2):colStarts(2)+length(minStr)-1)=minStr;
                dispStr(i+2, colStarts(3):colStarts(3)+length(avStr)-1)=avStr;
                dispStr(i+2, colStarts(4):colStarts(4)+length(maxStr)-1)=maxStr;
            end
            
            disp(' ')
            disp(dispStr)
            disp(' ')            
        end
        % returns a struct containing parent-child relationships
        % and call stack info
        function res = getSimulationStepData(this, simTime) 
            [timerStartValues, timerEndValues, ~, ~, nodeNumbers, ~, lSimTimes] = this.TimerData.getTimerValues;
            idxs = abs(lSimTimes - simTime) < 0.0000001;
            if ~any(idxs)
                res = [];
                return;
            end
            res.TimerStartValues = timerStartValues(idxs);
            res.TimerEndValues = timerEndValues(idxs);
            res.NodeNumbers = nodeNumbers(idxs) - 1;

            numSections = this.TimerData.getNumSectionNodes;
            taskNames = cell(1, numSections);
            uniqueSectionNames = cell(1,0);

            matchSectionIdToName = zeros(1, numSections);
            numUniqueNames = 0;
            for i=1:numSections
                section =  this.TimerData.getVisibleSectionProfile(i);
                if section.getTraceInfo.isTask()
                    taskNames{i} = section.Name;
                    fcnCallName = section.Name;
                    fcnCallName = regexprep(fcnCallName, '\s\[(.+)\]$', '');
                    fcnCallName = strrep(fcnCallName, ' ', '-');
                else
                    taskNames{i} = '';
                    fcnCallName = section.getTraceInfo.getCodeSectionName();
                end
                idx = find(strcmp(fcnCallName, uniqueSectionNames));
                if isempty(idx)
                    numUniqueNames = numUniqueNames + 1;
                    matchSectionIdToName(i) = numUniqueNames;
                    uniqueSectionNames{end+1} = fcnCallName; %#ok<AGROW>
                else
                    matchSectionIdToName(i) = idx;
                end
            end

            numNodes = this.TimerData.getNumNodes - 1;
            realNodes = zeros(1, numNodes);
            namesByNodeId = cell(1, numNodes);
            originalFullName = cell(1, numNodes);
            isRealNode = false(1, numNodes);
            tasks = false(1, numNodes);
            for i=1:numNodes
                section = this.TimerData.getSectionProfile(i);
                id = section.Number;
                if id > 0
                    tasks(i) = section.getTraceInfo.isTask();
                    originalFullName{i} = section.Name;
                    isRealNode(i) = true;
                    realNodes(i) = id;
                    namesByNodeId{i} = uniqueSectionNames{matchSectionIdToName(id)};
                end
            end
            res.IsNodeIdATask = tasks;
            res.IsNodeIdARealNode = isRealNode;
            res.SectionNameByNodeId = originalFullName;
            res.FunctionNameByNodeId = namesByNodeId;
        end

        function res = getSummaryInfo(this)
            res = [];
            res.TimeCreated = this.TimeCreated;
            res.Workflow.XIL = ~(this.RealTime || this.RealTimeXCP);
            res.Workflow.RealTime = this.RealTime;
            res.Workflow.RealTimeXCP = this.RealTimeXCP;
            if this.RealTime
                res.TopModel = this.OriginalComponentName;
            else
                res.TopModel = this.ModelForProfilingTopSettings;
            end
            res.SaveOptions = this.TimerData.getCodeProfilingSaveOptions;
            res.IsSimulinkWorkflow = this.IsSimulinkWorkflow;
            numModules = this.getModuleNumber();
            profiledElements = struct('ModelName', {}, 'Component', {}, 'SID', {}, 'Path', {}, 'Idx', {});
            for i=1:numModules
                ex = this.getModule(i);
                profiledElement = [];
                if isempty(ex.OriginalComponentName)
                    profiledElement.ModelName = ex.ModelForProfilingTopSettings;
                else
                    profiledElement.ModelName = ex.OriginalComponentName;
                end
                profiledElement.Component = ex.SimulationComponent;
                profiledElement.SID = ex.SimulationComponentSID;
                profiledElement.Path = ex.SimulationComponentAncestors;
                profiledElement.Idx=i;
                profiledElements(end+1) = profiledElement; %#ok<AGROW> 
            end
            res.ProfiledElements = profiledElements;
        end

    end
    
    methods (Access=protected)
        
        % template method to let inherited classed extend the report by adding extra sections
        function addAdditionalSection(this, lExecTimeReport, reportName) %#ok<*INUSD>
        end
        
        % add the section with the Capacity description
        function [capacityHtml, definitionList] = addCapacitySection(this, ...
                     lReportFcn, definitionList, unit, numericFormat, scaleFactor, baselineObj)
            capacityHtml = '';

            rows = cell(0, 3);
            scaleFactor = eval(scaleFactor);
            for i=1:length(this.Sections)
                section = this.Sections(i);
                t = section.getTraceInfo;
                if t.isTask
                    rate = t.getSampleTime;
                    rate = rate(1);
                    if ~isinf(rate) && rate > 0
                        [name, avgUtil, tooltipAvg, maxUtil, tooltipMax] = ...
                            coder.profile.ExecTimeHelper.getTaskCapacity(section, rate, scaleFactor, lReportFcn, ....
                                unit, numericFormat, this.SimulationComponent, this.TimerTicksPerSecond);
                        sectionBaseline = this.getEquivalentSection(baselineObj, section);
                        if ~isempty(sectionBaseline)
                            [~, avgUtilCmp, ~, maxUtilCmp] = coder.profile.ExecTimeHelper.getTaskCapacity(...
                                sectionBaseline, rate, scaleFactor, lReportFcn, unit, ...
                                numericFormat, this.SimulationComponent, this.TimerTicksPerSecond);
                            avgUtilCmpStr = sprintf(' (%0.4g%%)', avgUtilCmp);
                            maxUtilCmpStr = sprintf(' (%0.4g%%)', maxUtilCmp);
                            avgColor = this.getColor(avgUtil, avgUtilCmp);
                            maxColor = this.getColor(maxUtil, maxUtilCmp);
                        else
                            avgUtilCmpStr = '';
                            maxUtilCmpStr = '';
                            avgColor = 'black';
                            maxColor = 'black';
                        end
                        row = {name, ...
                            sprintf('<a title="%s"><font color="%s">%0.4g%%</font>%s</a>', ...
                                tooltipAvg, avgColor, avgUtil, avgUtilCmpStr), ...
                            sprintf('<a title="%s"><font color="%s">%0.4g%%</font>%s</a>', ...
                                tooltipMax, maxColor, maxUtil, maxUtilCmpStr)};
                        rows(end+1, 1:3) = row; %#ok<AGROW>
                    end
                end
            end
            if ~isempty(rows)
                lIsSummaryOnly = ~strcmp(this.TimerData.getCodeProfilingSaveOptions, 'AllData');
                [avgTotal, maxTotal] = coder.profile.ExecTimeHelper.getTotalCapacity(...
                                           lIsSummaryOnly, this.Sections, this.TimerTicksPerSecond);
                avgCmp = '';
                maxCmp = '';
                avgHeaderTxt = DAStudio.message('CoderProfile:ExecutionTime:ReportAverageCPUUtilizationHeader');
                maxHeaderTxt = DAStudio.message('CoderProfile:ExecutionTime:ReportMaximumCPUUtilizationHeader');
                if ~isempty(baselineObj)
                    if isa(baselineObj, 'coder.profile.SectionRoot')
                        lIsSummaryOnly = false;
                        if isempty(baselineObj.getTimerTicksPerSecond)
                            baseline_tps = this.TimerTicksPerSecond;
                        else
                            baseline_tps = baselineObj.getTimerTicksPerSecond;
                        end
                        [avgTotalCmp, maxTotalCmp] = coder.profile.ExecTimeHelper.getTotalCapacity(...
                            lIsSummaryOnly, baselineObj, baseline_tps);
                    else
                        lIsSummaryOnly = ~strcmp(baselineObj.TimerData.getCodeProfilingSaveOptions, 'AllData');
                        [avgTotalCmp, maxTotalCmp] = coder.profile.ExecTimeHelper.getTotalCapacity(...
                           lIsSummaryOnly, baselineObj.Sections, baselineObj.TimerTicksPerSecond);
                    end
                    avgCmp = sprintf(' (%0.4g%%)', avgTotalCmp);
                    maxCmp = sprintf(' (%0.4g%%)', maxTotalCmp);
                    avgColor = this.getColor(avgTotal, avgTotalCmp);
                    maxColor = this.getColor(maxTotal, maxTotalCmp);
                    % append baseline note
                    avgHeaderTxt = sprintf('%s<br/>%s', avgHeaderTxt, 'Baseline in brackets');
                    maxHeaderTxt = sprintf('%s<br/>%s', maxHeaderTxt, 'Baseline in brackets');
                else
                    avgColor = 'black';
                    maxColor = 'black';
                end
                tooltipTotalAvg = DAStudio.message(...
                    'CoderProfile:ExecutionTime:ReportAverageOverallCPUUtilizationTooltip');
                tooltipTotalMax = DAStudio.message(...
                    'CoderProfile:ExecutionTime:ReportMaximumOverallCPUUtilizationTooltip');
                rows(end+1, 1:3) = {DAStudio.message('CoderProfile:ExecutionTime:ReportOverallCPUUtilization'), ...
                                    sprintf('<a title="%s"><font color="%s">%.4g%%</font>%s</a>', ...
                                        tooltipTotalAvg, avgColor, avgTotal, avgCmp), ...
                                    sprintf('<a title="%s"><font color="%s">%.4g%%</font>%s</a>', ...
                                        tooltipTotalMax, maxColor, maxTotal, maxCmp)};
                avgHeader = sprintf('<a title="%s">%s</a>', ...
                        DAStudio.message('CoderProfile:ExecutionTime:ReportAverageCPUUtilizationHeaderTooltip'), ...
                        avgHeaderTxt);
                maxHeader = sprintf('<a title="%s">%s</a>', ...
                    DAStudio.message('CoderProfile:ExecutionTime:ReportMaximumCPUUtilizationHeaderTooltip'), ...
                    maxHeaderTxt);
                headings = {'Task', avgHeader, maxHeader};
                colAlignment = {'left', 'right', 'right'};
                colWidthsInPercent = [50 25 25];
    
                capacityHtml = coder.profile.ExecutionTime.getTableHtml(headings, ...
                    colWidthsInPercent, colAlignment, rows);
                k = DAStudio.message('CoderProfile:ExecutionTime:ReportDefinitionCPUUtilization');
                v = DAStudio.message('CoderProfile:ExecutionTime:ReportDefinitionCPUUtilizationText');
                definitionList(k) = v;
            end
        end
        
        % add the section with the Capacity description
        function [summaryChecksHtml, definitionList] = getSummaryChecks(this, definitionList)
            summaryChecksHtml = '';

            rows = cell(0, 6);
            % for each task, extract a 1x6 cell array with the following
            % information
            % - cell(1): task name
            % - cell(2): average execution time (in seconds)
            % - cell(3): maximum execution time (in seconds)
            % - cell(4): average CPU utilization
            % - cell(5): maximum CPU utilization
            % - cell(6): rate (in seconds)
            for i=1:length(this.Sections)
                section = this.Sections(i);
                t = section.getTraceInfo;
                if t.isTask
                    rate = t.getSampleTime;
                    rate = rate(1);
                    if ~isinf(rate) && rate > 0
                        tps = double(this.TimerTicksPerSecond);
                        numCalls = double(section.NumCalls);
                        avgExec = mean(double(section.TotalExecutionTimeInTicks) / numCalls) / tps;
                        maxExec = double(section.MaximumExecutionTimeInTicks) / tps;
                        avgUtil = (avgExec / rate) * 100;
                        maxUtil = (maxExec / rate) * 100;
                        rows(end+1, 1:6) = {section.Name, avgExec, maxExec, avgUtil, maxUtil, rate}; %#ok<AGROW>
                    end
                end
            end
            
            if isempty(rows)
                return;
            end
            
            resultRows = cell(0, 2);

            % check avg/max CPU utilization isn't higher than threshold
            if ~isempty(this.CPUUtilization)
                avgUtilSum = sum([rows{:,4}]);
                percU = this.CPUUtilization*100;
                percUStr = sprintf('%.4g', percU);
                resultRows{end+1, 1} = sprintf('<a>%s</a>', ...
                    DAStudio.message('CoderProfile:ExecutionTime:AvgCPUUtilCheck', percUStr));
                actAvgU = sprintf('%.2g', avgUtilSum);
                tooltip = DAStudio.message('CoderProfile:ExecutionTime:AvgCPUUtilCheckTooltip', actAvgU);
                resultRows{end, 2} = this.getPassFailTxt(avgUtilSum < percU, tooltip);
                maxUtilSum = sum([rows{:,5}]);
                resultRows{end+1, 1} = sprintf('<a>%s</a>', ...
                    DAStudio.message('CoderProfile:ExecutionTime:MaxCPUUtilCheck', percUStr));
                maxAvgU = sprintf('%.2g', maxUtilSum);
                tooltip = DAStudio.message('CoderProfile:ExecutionTime:MaxCPUUtilCheckTooltip', maxAvgU);
                resultRows{end, 2} = this.getPassFailTxt(maxUtilSum < percU, tooltip);
            end
            k = DAStudio.message('CoderProfile:ExecutionTime:ThresholdCPUUtilDefinition');
            v = DAStudio.message('CoderProfile:ExecutionTime:ThresholdCPUUtilDefinitionText');
            definitionList(k) = v;
            
            % check that avg/max execution times don't take longer than
            % their periods
            lSize = size(rows);
            numRows = lSize(1);
            avgExecPassed = true;
            maxExecPassed = true;
            avgExecFailList = '';
            maxExecFailList = '';
            for i = 1:numRows
                avgExec = rows{i,2};
                maxExec = rows{i,3};
                rate = rows{i,6};
                if avgExec > rate
                    avgExecPassed = false;
                    avgExecFailList = sprintf('%s\n%s', avgExecFailList, rows{i,1});
                end
                if maxExec > rate
                    maxExecPassed = false;
                    maxExecFailList = sprintf('%s\n%s', maxExecFailList, rows{i,1});
                end
            end
            resultRows{end+1, 1} = sprintf('<a>%s</a>', ...
                DAStudio.message('CoderProfile:ExecutionTime:AvgPeriodCheck'));
            tooltip = '';
            if ~avgExecPassed
                tooltip = DAStudio.message('CoderProfile:ExecutionTime:PeriodCheckTooltip', avgExecFailList);
            end
            resultRows{end, 2} = this.getPassFailTxt(avgExecPassed, tooltip);
            resultRows{end+1, 1} = sprintf('<a>%s</a>', ...
                DAStudio.message('CoderProfile:ExecutionTime:MaxPeriodCheck'));
            tooltip = '';
            if ~avgExecPassed
                tooltip = DAStudio.message('CoderProfile:ExecutionTime:PeriodCheckTooltip', maxExecFailList);
            end
            resultRows{end, 2} = this.getPassFailTxt(maxExecPassed, tooltip);

            % generate table
            headings = {'Check Description', 'Results'};
            colAlignment = {'left', 'center'};
            colWidthsInPercent = [75 25];
            summaryChecksHtml = coder.profile.ExecutionTime.getTableHtml(headings, ...
                colWidthsInPercent, colAlignment, resultRows);
        end
        
        function res = getPassFailTxt(~, cond, tooltip)
            if cond
                msg = DAStudio.message('CoderProfile:ExecutionTime:FeasibilityCheckPassed');
                color = 'green';
            else
                msg = DAStudio.message('CoderProfile:ExecutionTime:FeasibilityCheckFailed');
                color = 'red';
            end
            res = sprintf('<a title="%s"><font color="%s">%s</font></a>', tooltip, color, msg);
            
        end
    end
    
    methods (Access=public)
        
        function annotate(this, varargin)
            if this.IsSynthetic
                return;
            end

            if isempty(this.SimulationComponent)
                % Nothing to annotate
                return;
            else
                % Model for the main simulation component must exist
                topModel = regexp(this.SimulationComponent, ...
                    '.*?(?=((\/)|$))','match', 'once');
                if exist(topModel)~=4 %#ok<EXIST>
                    DAStudio.error...
                        ('CoderProfile:ExecutionTime:NoModelToAnnotate', topModel);
                end
                % First call to open system ensures block diagram is loaded
                open_system(topModel);
                % Open block diagram to show simulation component
                open_system(this.SimulationComponent)
            end
            % Make sure this object is initialized
            initialize(this);
            lAnnotateManager = coder.profile.AnnotateManager.getAnnotateManagerInstance;
            lAnnotateManager.showProfilingResults(this, varargin{:});
        end
        
        function [varargout] =  timeline(this, varargin)
            assert(~this.IsSynthetic, 'Timeline not supported with synthetic results');

            % Throw an error if only summary data is available
            this.TimerData.errorIfSummaryDataOnly;
            % Make sure this object is initialized
            this.initialize;
            if (this.RealTimeXCP && coder.profile.private.featureOn('XCPScheduling')) || ...
                this.RealTime || (~(this.RealTimeXCP || this.RealTime))
                % advertise the schedule method instead
                execProfObjectName = inputname(1);
                MSLDiagnostic('CoderProfile:ExecutionTime:ProfilingScheduleAdvertizeMethod', execProfObjectName).reportAsWarning;
            end
            % create controller
            if (isempty(this.ExecutionTimelineController))
                this.ExecutionTimelineController = coder.profile.ExecutionTimePlotController(this.TimerData, @coder.profile.ExecutionTimelineViewModel);
            end
            % open view
            hf = addView(this.ExecutionTimelineController, varargin{:});            
            if nargout > 0
                varargout = {hf};
            end
        end
         
        function schedule(this, varargin)
            assert(~this.IsSynthetic, 'Schedule not supported with synthetic results');

            if ~isempty(this.CheckScheduleSupport) && this.CheckScheduleSupport
               % check if Simulink is available
                if ~this.isSimulinkInstalled()
                    DAStudio.error('CoderProfile:ExecutionTime:ProfilingScheduleNoSimulink');
                end
            end
            lIsXIL = ~(this.RealTimeXCP || this.RealTime);
            if this.RealTimeXCP && ~coder.profile.private.featureOn('XCPScheduling')
                % SLRT/XCP simulation: no action if scheduling isn't allowed
                DAStudio.error('CoderProfile:ExecutionTime:ProfilingScheduleOnlyForXIL');
            end
            
            % Throw an error if only summary data is available
            this.TimerData.errorIfSummaryDataOnly;
            % Make sure this object is initialized
            this.initialize;
            
            p = inputParser;
            p.addOptional('MaxNumPoints', 0, @(x)isnumeric(x));
            if lIsXIL
                % additional parameters for XIL only
                p.addOptional('ShowTasksOnly', false, @(x)islogical(x));
                p.addOptional('StartSimTime', -1, @(x)isnumeric(x));
                p.addOptional('StopSimTime', -1, @(x)isnumeric(x));
            end
            p.parse(varargin{:});
            
            if lIsXIL
                showTasksOnly = p.Results.ShowTasksOnly;
                startTime = p.Results.StartSimTime;
                stopTime = p.Results.StopSimTime;
            else
                % for SLRT or XCP, we only show tasks - no internal 
                % function calls
                showTasksOnly = true;
                startTime = -1;
                stopTime = -1;
            end
            
            if isempty(this.ExecutionScheduleController)
                if isempty(this.TimerTicksPerSecond) || this.TimerTicksPerSecond == 0
                    tps = double(0);
                else
                    tps = double(this.TimerTicksPerSecond);
                end
                if lIsXIL
                    this.ExecutionScheduleController = ...
                        coder.profile.ExecutionTimeXILScheduleController(...
                        this.ModelForProfilingTopSettings, this.TimerData, tps);
                else
                    modelName = this.ModelForProfilingTopSettings;
                    if isempty(modelName)
                        modelName = this.OriginalComponentName;
                    end
                    this.ExecutionScheduleController = ...
                        coder.profile.ExecutionTimeRealTimeScheduleController(...
                        modelName, this.TimerData, tps);
                end
            end
            if p.Results.MaxNumPoints > 0
                this.ExecutionScheduleController.MaxNumPointsToProcess = ...
                    p.Results.MaxNumPoints;
            else
                this.ExecutionScheduleController.MaxNumPointsToProcess = ...
                    coder.profile.ExecutionTimeScheduleController.CntMaxNumPointsToProcess;
            end
            
            this.ExecutionScheduleController.plot(showTasksOnly, startTime, stopTime);
            this.ExecutionScheduleController.refresh();
        end
        
        function htmlFile = report(this, varargin)
            assert(~this.IsSynthetic, 'Report not supported with synthetic results');

            % process function inputs
            [unitsArgNorm, unitSIArgNorm, displayInSeconds, formatArg, ...
             scaleFactorArgNorm, baselineObj, lVarName] = ...
                coder.profile.processReportArgs(this.TimerTicksPerSecond, ...
                    this.TimerHWCounterUnit, varargin{:});
            % object name
            if isempty(lVarName)
                execProfObjectName = inputname(1);
            else
                execProfObjectName = lVarName;
            end
            % model name
            if ~isempty(this.SimulationComponent)
                reportName = this.SimulationComponent;
            else 
                reportName = this.OriginalComponentName;
            end
            % process baseline object name
            baselineObjectName = '';
            if ~isempty(baselineObj) && ~this.validateComparisonInput(baselineObj)
                baselineObj = '';
            end
            if ~isempty(baselineObj)
                for i=1:length(varargin)
                    if strcmp(varargin{i}, 'baseline')
                        baselineObjectName = inputname(1+i+1);
                        break;
                    end
                end
            end

            reportTitle =  DAStudio.message('CoderProfile:ExecutionTime:ProfReportTitle',...
                reportName);

            % Contain the list of definition to detail in the Definition
            % section. The key is the name and the value is the description
            definitionList = containers.Map('KeyType', 'char' ,'ValueType', 'any');
            
            reportArgsNormalized = {unitsArgNorm, unitSIArgNorm, scaleFactorArgNorm, formatArg};
            
            htmlFile = this.getReportFileName(reportArgsNormalized{:});
            
            % First create object that provides e.g. MATLAB Coder or Simulink
            % Coder context
            context = feval(this.ReportFcn);
            
            % Now invoke the factory method to get the appropriate report
            % object
            lExecTimeReport = getReportObject(context, htmlFile, ...
                this.getBuildDir);
            
            if ~isempty(this.DocumentationLinkParameters)
                % When instance specific documentation link parameters are available, we
                % overwrite the default settings
                setHelpButtonHelpArgs(lExecTimeReport, this.DocumentationLinkParameters);
                setIntroTextHelpArgs(lExecTimeReport, this.DocumentationLinkParameters);
            end
            
            docHyperlink = getIntroHelpHyperlink(lExecTimeReport);

            if this.RealTime || this.RealTimeXCP
                introHtml = DAStudio.message(...
                    'CoderProfile:ExecutionTime:ProfReportIntroRealTime',...
                    docHyperlink);
            else
                introHtml = DAStudio.message('CoderProfile:ExecutionTime:ProfReportIntroSILPIL', ...
                    docHyperlink);
            end
            
            introHtml = sprintf('<p>%s</p>\n', introHtml);
            
            if this.TimerData.isOnTargetData
                extraIntro = DAStudio.message('CoderProfile:ExecutionTime:ProfReportExtraIntroCaptureMode');
                introHtml = sprintf('%s<p>%s</p>\n', introHtml, extraIntro);
            end
            
            lExecTimeReport.setTitle(reportTitle);
            lExecTimeReport.addIntro(introHtml);
            
            % Select between manual Baseline object or LatestResults object for the baselineObj
            % Give priority to manual Baseline object provided by API
            baselineObj_temp = [];
            if coder.profile.private.automaticComparison()
                baselineObj_temp = this.PreviousResults;
            end
            if ~isempty(baselineObj)
                baselineObj_temp = baselineObj;
            end            
            % generate summary section
            summarySectionHtml = this.getSummarySectionHtml...
                (displayInSeconds, execProfObjectName, reportArgsNormalized{:}, ...
                baselineObjectName, baselineObj_temp);
            if coder.profile.private.featureOn('MultiModelBlocks') && ... 
                    this.getModuleNumber() > 1
                reportSectionHtml = this.getAllReportSection(...
                    this.ModelForProfilingTopSettings, this.ReportFcn);
                if ~isempty(reportSectionHtml)
                    summarySectionHtml = sprintf("%s\n%s", summarySectionHtml, reportSectionHtml);
                end
            end
            lExecTimeReport.addCustomizedSection('Summary', summarySectionHtml, false, true, 1.0);

            if ~(this.RealTime || this.RealTimeXCP) && ...
                    ~isempty(this.TimerTicksPerSecond) && this.TimerTicksPerSecond > 0
                [summaryCheckHtml, definitionList] = this.getSummaryChecks(definitionList);
                if ~isempty(summaryCheckHtml)
                    lExecTimeReport.addCustomizedSection('Feasibility Checks', summaryCheckHtml, true, true, 1.0);
                end
            end
            
            % generate result section
            showFunctionName = this.TimerData.isOnTargetData;
            [resultsSectionHtml, definitionList] = this.getResultsSectionHtml...
                (lExecTimeReport, displayInSeconds, definitionList, reportArgsNormalized{:}, ...
                      this.ModelForProfilingTopSettings, showFunctionName);
            lExecTimeReport.addCustomizedSection('Profiled Sections of Code', resultsSectionHtml, false, true, 1.0);
            
            % generate function contribution
            if ~(this.RealTime || this.RealTimeXCP)
                [resultsSectionHtml, definitionList] = this.getFunctionPercentageSectionHtml(...
                    lExecTimeReport, definitionList);
                lExecTimeReport.addCustomizedSection('Execution Times in Percentages', resultsSectionHtml, true, true, 1.0);
            end
            
            % generate comparison section
            if ~isempty(baselineObj_temp)
                [resultsSectionHtml, definitionList] = this.getCompareResultsSectionHtml...
                    (lExecTimeReport, displayInSeconds, definitionList, reportArgsNormalized{:}, ...
                    this.ModelForProfilingTopSettings, baselineObj_temp);
                lExecTimeReport.addCustomizedSection('Comparison with Baseline Performance', resultsSectionHtml, true, true, 1.0);
            end
            
            % generate CPU Utilization section
            if ~isempty(this.TimerTicksPerSecond)
                [capacitySectionHtml, definitionList] = ...
                    this.addCapacitySection(this.ReportFcn, definitionList, ...
                    unitSIArgNorm, formatArg, scaleFactorArgNorm, baselineObj_temp);
                if ~isempty(capacitySectionHtml)
                    lExecTimeReport.addCustomizedSection('CPU Utilization', capacitySectionHtml, true, true, 1.0);
                end
            end
            
            % template method to let inherited classed extend the report by adding extra sections
            this.addAdditionalSection(lExecTimeReport, reportName);
            
            % generate definition section
            if ~isempty(definitionList)
                definitionSectionHtml = this.getDefinitionSectionHtml(definitionList);
                lExecTimeReport.addSection('Definitions', definitionSectionHtml);
            end
            
            fid = fopen(htmlFile,'w', 'n', getEncoding(lExecTimeReport));
            fwrite(fid, lExecTimeReport.emitHTML, 'char');
            fclose(fid);
            coder.profile.ExecTimeHelper.copyAllIcons(htmlFile);
            
            dialogTitle = 'Code Execution Profiling Report';
            lExecTimeReport.launchReport(htmlFile, dialogTitle);
            this.ExecTimeReport = lExecTimeReport;
        end
        
        function display(this) %#ok<DISPLAY>
            
            numProf = this.getNumSectionProfiles;
            
            assert(numProf ~= 0, ...
                   'No execution profiling data was found.');

            if coder.profile.private.featureOn('ProfilingApp')
                cmdHyperlink = sprintf...
                    ('<a href="matlab: coder.profile.show(%s)">coder.profile.show(%s)</a>',...
                     inputname(1), inputname(1));
                dispStr = DAStudio.message('CoderProfile:ExecutionTime:ExecutionProfilingAppDisp', ...
                    this.TimerData.getOriginalComponentName, cmdHyperlink);
            else
                cmdHyperlink = sprintf('<a href="matlab: report(%s)">report(%s)</a>',...
                    inputname(1), inputname(1));
                dispStr = DAStudio.message('CoderProfile:ExecutionTime:ExecutionProfilingDisp', ...
                    this.TimerData.getOriginalComponentName, cmdHyperlink);
            end
            
            disp(' ')
            disp(dispStr)
            disp(' ')
        end

        function profVar = getModule(this, i)
            if isempty(this.ParentModule)
                if i==1
                    profVar = this;
                else
                    profVar = this.Modules(i - 1);
                end
            else
                profVar = this.ParentModule.getModule(i);
            end
        end
        
        function n = getModuleNumber(this)
            if isempty(this.ParentModule)
                n = length(this.Modules) + 1;
            else
                n = this.ParentModule.getModuleNumber();
            end
        end
    end

    methods (Hidden=true, Access=public)
        
        function delete(this)
            if ~isempty(this.ExecTimeReport)
                delete(this.ExecTimeReport);
            end
        end

        function this = ExecutionTime(profData, lReportFcn, lDocumentationLinkParameters)

            if nargin<2
                % xxx(g926289)
                lReportFcn = 'coder.internal.SLCExecTimeReport';
            end
            if nargin > 2
                % Documentation link parameters, if provided, will override the default
                % documentation links
                this.DocumentationLinkParameters = lDocumentationLinkParameters;
            end
            this.ReportFcn = lReportFcn;
            this.TimerData = coder.profile.ExecTimeData(profData);
            this.TimeCreated = string(datetime("now"));
            this.SimTimeAvailable = ~isempty(profData.simTime);
            this.SerializeObjectVersion = ...
                coder.profile.ExecutionTime.CurrentObjectVersion;
            this.CheckScheduleSupport = false;
        end
        
        function res = isScheduleSupported(this)
            res = ~this.RealTime && ~this.RealTimeXCP && ...
                strcmp(this.TimerData.getCodeProfilingSaveOptions(), 'AllData');
        end
        
        function [isOn, isBenchmark] = getOverheadFiltering(this)
            isOn = this.GlobalRegistry.FilterProfilingOverhead;
            isBenchmark = this.GlobalRegistry.RunOverheadBenchmark;
        end

        function res = getOverheadBenchmarkData(this)
            [lTskOv, lFcnOv, lTmpOv, lFrzOv] = this.TimerData.getOverhead();
            res = [];
            
            if ~isempty(lTmpOv)
                xTsk = struct('Data', lTskOv', 'NumSamples', length(lTskOv), 'Average', mean(lTskOv));
                xFcn = struct('Data', lFcnOv', 'NumSamples', length(lFcnOv), 'Average', mean(lFcnOv));
                xTmp = struct('Data', lTmpOv', 'NumSamples', length(lTmpOv), 'Average', mean(lTmpOv));
                xFrz = struct('Data', lFrzOv', 'NumSamples', length(lFrzOv), 'Average', mean(lFrzOv));
                
                res = struct('TimestampOverhead', xTmp, ...
                             'TaskOverhead', xTsk, ...
                             'FunctionOverhead', xFcn, ...
                             'FreezingOverhead', xFrz);
            end
        end
        
        function [unit, isTime] = getUnitOfMeasurement(this)
            if ~isempty(this.TimerHWCounterUnit)
                % non-conventional timer is in use (i.e.: HW counters)
                unit = this.TimerHWCounterUnit;
                isTime = false;
            elseif ~isempty(this.TimerTicksPerSecond) && this.TimerTicksPerSecond > 0
                % profiling data is in seconds
                unit = DAStudio.message('CoderProfile:ExecutionTime:ReportUnitsSeconds');
                isTime = true;
            else
                % profiling data is in ticks
                unit = DAStudio.message('CoderProfile:ExecutionTime:ReportUnitsTimerTicks');
                isTime = false;
            end
        end
        
        function res = isStandardSimulation(this)
            res = ~this.RealTime;
        end
        
        function res = isSILPILSimulation(this)
            res = ~this.RealTime && ~this.RealTimeXCP;
        end

        %caches save settings in executionProfile object
        function res = getCodeProfilingSaveOptions(this)
            res = this.TimerData.getCodeProfilingSaveOptions;
        end

        function res = areComparable(this, execProf)
            res = false;
            if ~isempty(this.SimulationComponent)
                nameOrg = this.SimulationComponent;
                nameCmp = execProf.SimulationComponent;
            else 
                nameOrg = this.OriginalComponentName;
                nameCmp = execProf.OriginalComponentName;
            end
            if ~(isempty(nameOrg) && isempty(nameCmp)) && ~strcmp(nameOrg, nameCmp)
                return;
            end
            if this.CurrentObjectVersion ~= execProf.CurrentObjectVersion
                return;
            end    
            if this.RealTime ~= execProf.RealTime || this.RealTimeXCP ~= execProf.RealTimeXCP
                return;
            end
            if this.SimTimeAvailable ~= execProf.SimTimeAvailable
                return;
            end
            isHWMetricsEmpty = isempty(this.TimerHWCounterUnit);
            if isHWMetricsEmpty ~= isempty(execProf.TimerHWCounterUnit) || ...
                   (~isHWMetricsEmpty && ...
                       ~strcmp(this.TimerHWCounterUnit, execProf.TimerHWCounterUnit))
                return;
            end
            if this.IsSimulinkWorkflow ~= execProf.IsSimulinkWorkflow
                return;
            end
            if this.IsSynthetic ~= execProf.IsSynthetic
                return;
            end
            res = true;
        end

        function [tblValues,tblTooltips, tblColumnNames, tblColumnTooltips] = getTaskTableSummary(this)
            columns = this.getResultColumns([]);
            fullData = this.isTimeSeriesDataAvailable();
            tps = this.TimerTicksPerSecond;
            reportFcn = this.ReportFcn;
            [tblValues,tblTooltips, tblColumnNames, tblColumnTooltips] = ...
                coder.profile.exportTaskSummary(...
                tps, columns, fullData, this, reportFcn);
        end

        function res = getIsSimulinkWorkflow(this)
            res = this.IsSimulinkWorkflow;
        end

        function res = getNumSamples(this)
            res = this.TimerData.getNumSamples();
            if isempty(res)
                res = length(this.TimerData.getTimerValues());
            end
        end

        function res = getOverrunCount(this, taskName)
            res = this.TimerData.getOverrunCount(taskName);
        end

        function res = hasOverrunCount(this)
            res = this.TimerData.hasOverrunCount();
        end
    end
end
