classdef (Hidden = true) ProfilingUtils
    %PROFILINGUTILS contains stateless code to configure profiling for XIL
    % simulations

    %   Copyright 2021-2022 The MathWorks, Inc.
    
    methods(Static=true)
        
        function [lGlobalRegistry, profilingChecksum] = createGlobalRegistry(silPilInterface, wrapperUtils)
            lGlobalRegistry = silPilInterface.getClientInterface.createExecTimeGlobalReg(silPilInterface);
            profilingChecksum = [];
            if ~isempty(lGlobalRegistry)
                wrapperUtils.setCodeInstrumentationEnabled(...
                    lGlobalRegistry.getTotalNumberOfProbes > 0 ||...
                        wrapperUtils.isProfilingEnabled);
                % Evaluate checksum prior to adding the task registry if execution profiling is enabled
                profilingChecksum.CodeInstrumentation = ...
                    lGlobalRegistry.getModelInstrumentationChecksum;
                profilingChecksum.GranularityOn = ...
                    lGlobalRegistry.getFeatureChecksum('GranularityOn');
                profilingChecksum.GranularityOff = ...
                    lGlobalRegistry.getFeatureChecksum('GranularityOff');
            end
        end
        
        function createTopLevelRegistries(lGlobalRegistry, profilingService, ...
                componentName, componentRef, wrapperUtils, lCodeFolderRelative)
            
            lTargetWordSize = wrapperUtils.getParam('TargetWordSize');
            lMaxIdLength = wrapperUtils.getParam('MaxIdLength'); 
            
            lTaskRegistry = [];
            lStackTaskRegistry = [];
            if wrapperUtils.StackProfilingEnabled
                lStackTaskRegistry = coder.profile.StackProbeComponentRegistry(...
                    componentName, componentRef, lTargetWordSize, ...
                    lMaxIdLength, lCodeFolderRelative);
                lGlobalRegistry.addRegistries({lStackTaskRegistry});
            else
                lTaskRegistry = coder.profile.TimeProbeComponentRegistry(...
                    componentName, componentRef, lTargetWordSize, [],...
                    lMaxIdLength, lCodeFolderRelative);
                lGlobalRegistry.addRegistries({lTaskRegistry});
            end
            
            lTimeProfiling = wrapperUtils.isTimeProfilingEnabled();
            lIsProfilingCaptureModeEnabled = lTimeProfiling && ...
                strcmp(wrapperUtils.CodeProfilingSaveOptions, 'MetricsOnly');
            lIsProfilingOverheadFilterEnabled = lTimeProfiling && ...
                profilingService.getProfilingService().isOverheadFilteringActive() && ...
                ~lIsProfilingCaptureModeEnabled;
            
            if lIsProfilingCaptureModeEnabled
                lCaptureRegistry = coder.profile.CaptureProbeComponentRegistry(...
                    componentName, componentRef, lTargetWordSize,...
                    [], lCodeFolderRelative);
                lGlobalRegistry.addRegistries({lCaptureRegistry});
                lGlobalRegistry.UploadDataInRealTime = false;
            else
                lCaptureRegistry = [];
                lGlobalRegistry.UploadDataInRealTime = true;
                lGlobalRegistry.TotalTimeProbe = 0;
            end
            
            % if profiling is on, check if the overhead filtering is active
            if lIsProfilingOverheadFilterEnabled && ...
                    profilingService.getProfilingService().isOverheadBenchmarkExecuted()
                lOverheadRegistry = coder.profile.OverheadProbeComponentRegistry(...
                    componentName, componentRef, lTargetWordSize,...
                    [], lMaxIdLength, lCodeFolderRelative);
                lGlobalRegistry.addRegistries({lOverheadRegistry});
                lRunBenchmarkOverhead = true;
            else
                lOverheadRegistry = [];
                lRunBenchmarkOverhead = false;
            end
            
            % The content of the task profiling registry must persist
            wrapperUtils.setProfilingTaskRegistry(lTaskRegistry);
            wrapperUtils.setProfilingStackTaskRegistry(lStackTaskRegistry);
            wrapperUtils.setProfilingCaptureRegistry(lCaptureRegistry);
            wrapperUtils.setProfilingOverheadRegistry(lOverheadRegistry);
            
            lGlobalRegistry.FilterProfilingOverhead = lIsProfilingOverheadFilterEnabled;
            lGlobalRegistry.RunOverheadBenchmark = lRunBenchmarkOverhead;
            lTimer = wrapperUtils.getProfilingTimer();
            lGlobalRegistry.ProfilingTimer = lTimer;
            [lStack, lGenericDriver] = wrapperUtils.getProfilingStackDriver();
            lGlobalRegistry.ProfilingStackDriver = lStack;
            lGlobalRegistry.IsGenericStackDriver = lGenericDriver;
        end
        
        function configureGlobalRegistry(lGlobalRegistry, wrapperUtils, uploadFcn, isSil, appDir)
            % For SIL and PIL, the application runs in a single thread therefore it can use
            % a (more accurate) timing method that is not thread-safe.
            lGlobalRegistry.SingleThreadTiming = true;
            
            lGlobalRegistry.TargetCollectDataFcnName = uploadFcn;
            
            % Set names of header and source file for instrumentation utility functions
            targetLang = wrapperUtils.getParam('TargetLang');
            [lSourceFile, lHeaderFile] = rtw.pil.ProfilingUtils.getSourceFiles(targetLang, appDir);
            lGlobalRegistry.SourceFileTargetInterface = lSourceFile;
            lGlobalRegistry.HeaderFileTargetInterface = lHeaderFile;
            if isSil
                lGlobalRegistry.TimerHWCounterUnit = coder.profile.private.getHWCounterUnit();
            end
        end
        
        function generateInstrumentationAPI(wrapperUtils, lGlobalRegistry, lTargetWordSize)
            if wrapperUtils.isTimeProfilingEnabled && ...
                    strcmp(wrapperUtils.CodeProfilingSaveOptions, 'MetricsOnly')
                lGlobalRegistry.updateNumberOfTimeProbes();
                lCaptureRegistry = wrapperUtils.getProfilingCaptureRegistry;
                lCaptureRegistry.requestCaptureProbes(lGlobalRegistry.TotalTimeProbe);
            end
            lGlobalRegistry.writeTargetInterface(lTargetWordSize);
        end
        
        function wrapperInfo = extendWrapperInfo(wrapperInfo, wrapperUtils, lGlobalRegistry, checksum)
            wrapperInfo.isExecutionProfilingEnabled = wrapperUtils.isProfilingEnabled;
            wrapperInfo.isTimeProfilingEnabled = wrapperUtils.isTimeProfilingEnabled;
            wrapperInfo.isStackProfilingEnabled = wrapperUtils.isStackProfilingEnabled;
            wrapperInfo.profilingSaveOpts = wrapperUtils.CodeProfilingSaveOptions;
            
            wrapperInfo.lGlobalRegistry = [];
            wrapperInfo.lCodeInstrumentationChecksum = [];
            wrapperInfo.lProfilingGranularityOnChecksum = [];
            wrapperInfo.lProfilingGranularityOffChecksum = [];
            wrapperInfo.lTaskRegistry = [];
            wrapperInfo.lProfilingStackTaskRegistry = [];
            
            if wrapperUtils.isCodeInstrumentationEnabled
                wrapperInfo.lGlobalRegistry = lGlobalRegistry;
                wrapperInfo.lCodeInstrumentationChecksum = checksum.CodeInstrumentation;
                wrapperInfo.lProfilingGranularityOnChecksum = checksum.GranularityOn;
                wrapperInfo.lProfilingGranularityOffChecksum = checksum.GranularityOff;
            end

            if wrapperInfo.isExecutionProfilingEnabled
                wrapperInfo.lProfilingTaskRegistry = wrapperUtils.getProfilingTaskRegistry();
                wrapperInfo.lProfilingStackTaskRegistry = wrapperUtils.getProfilingStackTaskRegistry();
                wrapperInfo.lProfilingOverheadRegistry = wrapperUtils.getProfilingOverheadRegistry();
                wrapperInfo.lProfilinglCaptureRegistry = wrapperUtils.getProfilingCaptureRegistry();
            end
        end
        
        function serializeProfiling(lGlobalRegistry, wrapperUtils, lTargetWordSize, appDir)
            instrInfoData.lGlobalRegistry = lGlobalRegistry;
            instrInfoData.profChecksum = lGlobalRegistry.getModelInstrumentationChecksum;
            
            % generates target specific information
            if wrapperUtils.isTimeProfilingEnabled
                % summary metrics
                saveOpts = wrapperUtils.CodeProfilingSaveOptions;
                if ~isempty(saveOpts)
                    switch saveOpts
                        case 'MetricsOnly'
                            summaryOnly = true;
                            onTarget = true;
                        case 'SummaryOnly'
                            summaryOnly = true;
                            onTarget = false;
                        case 'AllData'
                            summaryOnly = false;
                            onTarget = false;
                        otherwise
                            assert(false, 'Invalid value for execution profiling save options');
                    end
                    instrInfoData.targetInfo.summaryOnly = summaryOnly;
                    instrInfoData.targetInfo.onTargetOnly = onTarget;
                end
                
                % maximum size for timer value
                lTimer = wrapperUtils.getProfilingTimer();
                lTimerType = lTimer.getTimerType;
                instrInfoData.targetInfo.timerTypeMaxValue = intmax(lTimerType);
                
                timerInBytes = rtw.connectivity.TypeUtils.builtinHostByteSize(lTimerType);
                instrInfoData.targetInfo.timerInBytes = uint8(timerInBytes);
                timerTypeName = lTimerType;
                instrInfoData.targetInfo.timerTypeName = timerTypeName;
                tps = lTimer.getTicksPerSecond();
                if isempty(tps)
                    tps = 0;
                end
                if ~isempty(lGlobalRegistry.TimerHWCounterUnit)
                    instrInfoData.targetInfo.HWCounterUnit = lGlobalRegistry.TimerHWCounterUnit;
                end
                instrInfoData.targetInfo.ticksPerSecond = uint64(tps);
            end
            if wrapperUtils.isStackProfilingEnabled
                lDriver = wrapperUtils.getProfilingStackDriver();
                timerInBytes = rtw.connectivity.TypeUtils.builtinHostByteSize(lDriver.PtrDataType);
                instrInfoData.targetInfo.ptrInBytes = uint64(timerInBytes);
                instrInfoData.targetInfo.ptrTypeName = lDriver.PtrDataType;
                lProdBitPerChar = wrapperUtils.getParam('TargetBitPerChar');
                assert(any(lProdBitPerChar == [8, 16, 32]), 'Allowed value for ProdBitPerChar are 8, 16, 32');
                instrInfoData.targetInfo.charInBits = uint32(lProdBitPerChar);
            end
            
            % in common between stack and time profiling
            identifierMaxVal = coder.profile.ExecTimeConfig.getIdentifierMaxValueFromTargetWordSize(lTargetWordSize);
            instrInfoData.targetInfo.identifierMaxVal = identifierMaxVal;
            
            coder.profile.CoderInstrumentationInfo.writeInfo(appDir, instrInfoData);
        end
        
        function upToDated = isProfilingUpdated(silPilInterface, wrapperUtils, wrapperInfo)
            lGlobalRegistry = silPilInterface.getClientInterface.createExecTimeGlobalReg(silPilInterface);
            isExecutionProfilingEnabled = wrapperUtils.isProfilingEnabled;
            isTimeProfilingEnabled = wrapperUtils.isTimeProfilingEnabled;
            isStackProfilingEnabled = wrapperUtils.isStackProfilingEnabled;
            isCodeInstrumentationEnabled = wrapperUtils.isCodeInstrumentationEnabled;
            
            triggerRebuild =...
                (isCodeInstrumentationEnabled && ...
                ~isempty(lGlobalRegistry) &&...
                ~isequal(lGlobalRegistry.getModelInstrumentationChecksum, wrapperInfo.lCodeInstrumentationChecksum)) || ...
                ~(isExecutionProfilingEnabled == wrapperInfo.isExecutionProfilingEnabled) || ...
                ~(isTimeProfilingEnabled == wrapperInfo.isTimeProfilingEnabled) || ...
                ~(isStackProfilingEnabled == wrapperInfo.isStackProfilingEnabled);
            if ~triggerRebuild && isTimeProfilingEnabled
                triggerRebuild  = ...
                    ~isequal(wrapperUtils.CodeProfilingSaveOptions, wrapperInfo.profilingSaveOpts) || ...
                    ~isequal(lGlobalRegistry.getFeatureChecksum('GranularityOn'), wrapperInfo.lProfilingGranularityOnChecksum) || ...
                    ~isequal(lGlobalRegistry.getFeatureChecksum('GranularityOff'), wrapperInfo.lProfilingGranularityOffChecksum);
            end
            upToDated = ~triggerRebuild;
        end
    end
    
    methods(Access=private, Static)
        function [srcFullPath, headerFullPath] = getSourceFiles(targetLang, srcPath)
            [~, srcExt, sourceFilenameProfiling, hdrExt] = rtw.pil.SILPILInterface.getWrapperSourceFilenameAndExt(targetLang);
            srcShort = [sourceFilenameProfiling srcExt];
            srcFullPath = fullfile(srcPath, srcShort);
            headerFullPath = fullfile(srcPath, [sourceFilenameProfiling hdrExt]);
        end
    end
end
