classdef HWCountConfigurationHostBasedTargets < handle
    %   Utility class to deal with HW counters on host-based targets
    %   only GLNXA64 for the moment

    %   Copyright 2014-2019 The MathWorks, Inc.
    
    methods (Static, Access = 'public')
        
        function out = use3PLibrary(in)
            persistent lUse3PLibrary;
            if isempty(lUse3PLibrary)
                lUse3PLibrary = false;
            end
            if nargin > 0
                lUse3PLibrary = in;
            end
            out = lUse3PLibrary;
        end
        
        function verifyHWCounter ()
            % check to see if we are on glnxa64
            if ~isequal(computer, 'GLNXA64')
                DAStudio.error('CoderProfile:ExecutionTime:HWCounterUnavailable', computer);
            end
            
            % check if a valid HW counter is selected
            settings = coder.profile.private.csaTimer();
            if ~settings.CSA || isempty(settings.HWCounter)
                DAStudio.error('CoderProfile:ExecutionTime:HWCounterNotSelected');
            end
            
            % find the PAPI event associated to the requested HW metric
            papiMetric = coder.profile.private.HWCountConfigurationHostBasedTargets.translateHWCounter(settings.HWCounter);
            
            % create temporary directory and file
            tempdir = tempname();
            if ~exist(tempdir, 'dir')
                mkdir(tempdir);
            end
            
            origFolder = cd(tempdir);
            restoreOrigFolder = onCleanup(@()cd(origFolder));
            rmtempdir = onCleanup(@()rmdir(tempdir, 's'));
            
            srcfileName = 'PAPITest.c';
            binFileName = 'PAPI';
            coder.profile.private.HWCountConfigurationHostBasedTargets.createTestSrcFile(srcfileName, papiMetric);
            
            buildStatus = coder.profile.private.HWCountConfigurationHostBasedTargets. ...
                compileTestSrcFile(srcfileName, binFileName);
            if ~buildStatus
                clear restoreOrigFolder;
                clear rmtempdir;
                DAStudio.error('CoderProfile:ExecutionTime:HWCounterPAPIPath');
            end
            
            lResult = eval([binFileName '()']);
            
            clear restoreOrigFolder;
            clear rmtempdir;
            
            switch lResult
                case 0
                    % all good
                    return;
                case 1
                    % the number of available counters (channels) is 0
                    % this may be related to the missing HW counter or a
                    % misconfiguration (due to security reason)
                    % sudo sh -c "echo -1 > /proc/sys/kernel/perf_event_paranoid"
                    DAStudio.error('CoderProfile:ExecutionTime:HWCounterPAPICountersZero');
                case 2
                    % the library is able to connect to the HW counter but
                    % the requested event is not available: possibly, not
                    % supported
                    DAStudio.error('CoderProfile:ExecutionTime:HWCounterPAPICounterUnavailable', settings.HWCounter);
                otherwise
                    % we should never reach this
                    assert(false, 'Unknown return result from PAPI test!');
            end
        end
        
        function [compileFlags, linkFlags] = getFlags
            [includePath3P, libPath3P] = coder.profile.private.papiRoot;
            
            % add compiler flag to locate PAPI headers. Use the 3p folder
            compileFlags = {};
            if ~isempty(includePath3P)
                compileFlags = {sprintf('-I%s', includePath3P); };
            end
            
            % add linked flag to locate the PAPI library
            libPath = '';
            libName = '';
            if coder.profile.private.HWCountConfigurationHostBasedTargets.use3PLibrary
                % use PAPI library in 3p folder
                if ~isempty(libPath3P)
                    libPath = libPath3P;
                    libName = 'papi';
                end
            else
                % locate the PAPI library installed on this machine
                cmd = 'ldconfig -p | grep libpapi.so';
                [ret, msg] = system(cmd);
                if ret == 0 && ~isempty(msg)
                    fpath = splitlines(msg);
                    fpath = strsplit(fpath{1}, '=>');
                    fpath = strtrim(fpath{2});
                    [libPath, lib, ext] = fileparts(fpath);
                    libName = [':', lib, ext];
                end
            end
            if ~isempty(libName)
                linkFlags = cell(3, 1);
                linkFlags{1} = sprintf('-L%s', libPath);
                linkFlags{2} = sprintf('-Wl,-rpath,%s', libPath);
                linkFlags{3} = sprintf('-l%s', libName);
            else
                % if the PAPI library is neither in the 3p folder nor
                % installed, try your best by using papi only
                linkFlags = {'-lpapi'};
            end
        end
        
        function papiCounter = translateHWCounter (hwCounter)
            % http://icl.cs.utk.edu/projects/papi/presets.html
            switch hwCounter
                case 'totinstr'
                    % measures number of instructions
                    papiCounter = 'PAPI_TOT_INS';
                case 'floatinsts'
                    % measures number of float point instructions
                    papiCounter = 'PAPI_FP_INS';
                case 'floatopts'
                    % measures number of float point operations
                    papiCounter = 'PAPI_FP_OPS';
                case 'intinstr'
                    % measures number of integer instructions
                    papiCounter = 'PAPI_INT_INS';
                case 'loadinstr'
                    % measures number of load instructions
                    papiCounter = 'PAPI_LD_INS';
                case 'storeinstr'
                    % measure number of store instructions
                    papiCounter = 'PAPI_SR_INS';
                case 'totcyc'
                    % measure number of processor cycles
                    papiCounter = 'PAPI_TOT_CYC';
                case 'l1dcm'
                    % measure Level 1 data cache misses
                    papiCounter = 'PAPI_L1_DCM';
                case 'l1icm'
                    % measure Level 1 instruction cache misses
                    papiCounter = 'PAPI_L1_ICM';
                case 'l2dcm'
                    % measure Level 2 data cache misses
                    papiCounter = 'PAPI_L2_DCM';
                case 'l2icm'
                    % measure Level 2 instruction cache misses
                    papiCounter = 'PAPI_L2_ICM';
                case 'tlbdm'
                    % measure number of data translation lookaside buffer misses
                    papiCounter = 'PAPI_TLB_DM';
                case 'tlbim'
                    % measure number of instruction translation lookaside buffer misses
                    papiCounter = 'PAPI_TLB_IM';
                case 'memstall'
                    % measure number of Cycles Stalled Waiting for memory accesses
                    papiCounter = 'PAPI_MEM_SCY';
                case 'totstall'
                    % measure number of Cycles stalled on any resource
                    papiCounter = 'PAPI_RES_STL';
                otherwise
                    DAStudio.error('CoderProfile:ExecutionTime:HWCounterUnknownOption', hwCounter);
            end
        end
    end
    
    methods (Static, Access = 'private')
        
        function createTestSrcFile (fileName, papiMetric)
            writer = rtw.connectivity.CodeWriter.create('language', 'C', 'filename', fileName);
            
            writer.wLine('#include "mex.h"');
            writer.wLine('#include <papi.h>');
            writer.wNewLine;
            
            writer.wBlockStart('void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])');
            writer.wLine('int rval = 0;');
            writer.wLine('int Events[] = { %s };', papiMetric);
            writer.wLine('long long values[] = { 0 };');
            writer.wNewLine;
            
            writer.wComment('Check total number of counters (counting channels)');
            writer.wBlockStart('if (PAPI_num_counters() <= PAPI_OK)');
            writer.wLine('rval = 1;');
            writer.wBlockMiddle('else');
            writer.wComment('Check if the event is supported');
            writer.wBlockStart('if (PAPI_start_counters(Events, 1) != PAPI_OK)');
            writer.wLine('rval = 2;');
            writer.wBlockEnd;
            writer.wBlockEnd;
            writer.wLine('PAPI_stop_counters(values, 1);');
            writer.wLine('plhs[0] = mxCreateDoubleScalar((double)rval);');
            writer.wBlockEnd;
            
            writer.close;
        end
        
        function buildStatus = compileTestSrcFile(srcFile, binFile)
            [headerflags, ldflags] = coder.profile.private.HWCountConfigurationHostBasedTargets.getFlags;
            headerflags = strjoin(headerflags);
            ldflags = strjoin(ldflags);
            
            buildStatus = true;
            try
                mex('-silent',...
                    ['LDFLAGS=$LDFLAGS ' ldflags],...
                    headerflags,...
                    srcFile,...
                    '-output', binFile);
            catch ME %#ok<NASGU>
                buildStatus = false;
            end
        end
        
    end
end

% LocalWords:  PAPI papi hwcount th LDFLAGS Wl rpath libpapi lpapi xil
% LocalWords:  totinstr usr floatinsts FP floatops intinstr loadinstr
% LocalWords:  storeinstr rval
