classdef ExecStackData < handle
    %EXECSTACKDATA stores stack profiling data: both raw samples and processed 
    % data

    % Copyright 2021-2023 The MathWorks, Inc.
    
    properties(Access=private)
        IdUint64;
        SectionId;
        StackUsage;
        SimulationTime;
        UniqueIdUint64 = [];
        
        % store task/function sections
        TaskSections = [];
        FunctionSections = [];

        % task/function summary from profiling engine
        TaskExecution = [];
        FunctionExecution = [];
        
        % execution tree (matrix format) from profiling engine
        RawTreeExecution = [];
        % processed execution tree
        TreeExecution = [];
        RawTreeSimTimes = [];
        RawTreeSimMem = [];
    end
    
    methods
        function this = ExecStackData(profData)
            this.TaskExecution = profData.TaskExecution;
            this.FunctionExecution = profData.FunctionExecution;
            this.SectionId = profData.sectionId;
            this.StackUsage = profData.stackUsage;
            this.SimulationTime = profData.simTime;
            this.RawTreeExecution = profData.treeExecution;
            this.RawTreeSimTimes = profData.treeSimTime;
            this.RawTreeSimMem = profData.treeSimMem;
            if ~isempty(profData.UniqueIDs)
                this.UniqueIdUint64 = containers.Map('KeyType', 'uint64', 'ValueType', 'uint64');
                % profData.UniqueIDs is a matrix (nx2) where n is the
                % number of tasks. Use size instead of length to deal with
                % the case of single task (length returns 2, we need 1
                % instead)
                lSize = size(profData.UniqueIDs);
                nRows = lSize(1);
                for i=1:nRows
                    this.UniqueIdUint64(profData.UniqueIDs(i, 1)) = profData.UniqueIDs(i, 2);
                end
            else
                this.UniqueIdUint64 = [];
            end
        end
        
        function initialize(this, lGlobalRegistry)
            if isempty(lGlobalRegistry)
                this.TaskSections = coder.profile.StackSection.empty;
                this.FunctionExecution = coder.profile.StackSection.empty;
            else
                numStackSections = lGlobalRegistry.getTotalNumberOfStackProbes();
                numSections = length(lGlobalRegistry.getAllProbes);
                lSectionIds = 1:numSections;
                lTaskSections = coder.profile.StackSection.empty;
                lFunctionSections = coder.profile.StackSection.empty;
                
                lSectionTraceInfos = lGlobalRegistry.getInfoForSectionId(lSectionIds);
                
                mapIDvsNum = containers.Map('KeyType', 'uint32', 'ValueType', 'uint32');
                idxTask = 1;
                idxFunction = 1;
                for i=1:numStackSections
                    lSectionTraceInfo = lSectionTraceInfos{i};
                    if isempty(lSectionTraceInfo)
                        continue;
                    end
                    if lSectionTraceInfo.isTask()
                        if ~any(this.TaskExecution.sectionId==i)
                            % task never executed
                            continue;
                        end
                        uniqueId = [];
                        if ~isempty(this.UniqueIdUint64) && this.UniqueIdUint64.isKey(i)
                            uniqueId = this.UniqueIdUint64(i);
                        end
                        lTaskSections(end+1) = ...
                            coder.profile.StackTaskSection(...
                                this, idxTask, i, lSectionTraceInfo, uniqueId); %#ok<AGROW>
                        mapIDvsNum(i) = idxTask;
                        idxTask = idxTask + 1;
                    else
                        if ~any(this.FunctionExecution.sectionId==i)
                            % function never executed
                            continue;
                        end
                        lFunctionSections(end+1) = ...
                            coder.profile.StackFunctionSection(...
                            this, idxFunction, i, lSectionTraceInfo); %#ok<AGROW>
                        mapIDvsNum(i) = idxFunction;
                        idxFunction = idxFunction + 1;
                    end
                end
                this.TaskSections = lTaskSections;
                this.FunctionSections = lFunctionSections;

                % process execution tree
                columnIDNodeNumber = 1;
                columnIDUniqueSectionID = 2;
                columnIDParentNodeNumber = 3;
                columnIDIsTask = 4;
                nCols = size(this.RawTreeExecution, columnIDUniqueSectionID);
                for i=2:nCols
                    sectionId = this.RawTreeExecution(columnIDUniqueSectionID, i);
                    this.RawTreeExecution(columnIDUniqueSectionID, i) = mapIDvsNum(sectionId);
                end
                this.TreeExecution = coder.profile.StackTree();

                % the first element must be a synthetic node collection all
                % real nodes
                assert(this.RawTreeExecution(columnIDParentNodeNumber, 1) == 0);
                % map to quickly locate parents given their index in 
                % RawTreeExecution
                parentMap = containers.Map('KeyType', 'uint32', 'ValueType', 'any');
                parentMap(uint32(0)) = this.TreeExecution;
                % process raw execution tree
                for i=2:nCols
                    nodeNumber = this.RawTreeExecution(columnIDNodeNumber, i);
                    parentIdx = this.RawTreeExecution(columnIDParentNodeNumber, i);
                    parent = parentMap(parentIdx);
                    if this.RawTreeExecution(columnIDIsTask, i)
                        tmp = coder.profile.StackTaskTree();
                        tmp.Data = this.TaskSections(...
                            this.RawTreeExecution(columnIDUniqueSectionID, i));
                        tmp.Time = this.RawTreeSimTimes(i);
                    else
                        tmp = coder.profile.StackFunctionTree();
                        tmp.Data = this.FunctionSections(...
                            this.RawTreeExecution(columnIDUniqueSectionID, i));
                        tmp.Memory = this.RawTreeSimMem(i);
                    end
                    parentMap(nodeNumber)  = tmp;
                    tmp.Parent = parent;
                    parent.Children(end+1) = tmp;
                 end
            end
            
            % Generate a 64-bit identifier to uniquely identify the data associated
            % with this object
            this.generateIdUint64;
        end
    end

    methods(Access=public, Hidden=true)
        function res = getTaskUsage(this, id)
            idx = find(this.TaskExecution.sectionId==id);
            res.minUsage = this.TaskExecution.minUsage(idx);
            res.minSimTime = this.TaskExecution.minSimTime(idx);
            res.avgUsage = this.TaskExecution.avgUsage(idx);
            res.maxUsage = this.TaskExecution.maxUsage(idx);
            res.maxSimTime = this.TaskExecution.maxSimTime(idx);
            res.numCalls = this.TaskExecution.numCalls(idx);
            res.minDepth = this.TaskExecution.minDepth(idx);
            res.maxDepth = this.TaskExecution.maxDepth(idx);
            res.minDepthTime = this.TaskExecution.minDepthTime(idx);
            res.maxDepthTime = this.TaskExecution.maxDepthTime(idx);
            idxs = this.SectionId == id;
            res.stackUsage = this.StackUsage(idxs);
            res.stackTime = this.SimulationTime(idxs);
        end

        function res = getFunctionUsage(this, id)
            idx = find(this.FunctionExecution.sectionId==id);
            res.memory = this.FunctionExecution.memory(idx);
            res.numCalls = this.FunctionExecution.numCalls(idx);
            res.flag = this.FunctionExecution.flag(idx);
            res.maxMemory = this.FunctionExecution.maxMemory(idx);
        end
        
        function lUsage = getAllStackUsage(this, id)
            idxs = this.SectionId == id;
            lUsage = this.StackUsage(idxs);
        end
        
        function taskSections = getTaskSections(this)
            taskSections = [];
            if ~isempty(this.TaskSections)
                taskSections = this.TaskSections;
            end
        end

        function functionSections = getFunctionSections(this)
            functionSections = [];
            if ~isempty(this.TaskSections)
                functionSections = this.FunctionSections;
            end
        end
        
        function id = getIdUint64(this)
            id = this.IdUint64;
        end

        function root = getExecutionTreeRoot(this)
            root = this.TreeExecution;
        end
    end
    
    methods(Access=private)
        function generateIdUint64(this)
            % Generate a 64-bit random number to serve as a unique identifier
            s = rng;
            rng('shuffle', 'twister');
            imax32 = intmax('uint32');
            xrand = uint64(randi(imax32, 2, 'uint32'));
            this.IdUint64 = xrand(1)+xrand(2)*(uint64(imax32)+1);
            rng(s);
        end
    end
end
