% PoolProfileResults Results of a pool profiling session

% Copyright 2017 The MathWorks, Inc.

classdef PoolProfileResults

    properties (SetAccess = immutable)
        MessageLog
        MatlabLog
        DispatchLog
        LanguageConstructLog
        CombinedLog
    end

    methods
        function obj = PoolProfileResults(msg, m, d, l)
            obj.MessageLog = msg;
            obj.MatlabLog  = m;
            obj.DispatchLog = d;
            obj.LanguageConstructLog = l;

            msg.Info = rowfun(@(varargin) "Bytes: " + varargin{5} + ", Other: " + varargin{3}, ...
                              msg, 'OutputFormat', 'uniform');
            m.Info   = rowfun(@(varargin) "Function: " + varargin{3}, ...
                              m, 'OutputFormat', 'uniform');
            d.Info   = rowfun(@(varargin) "Class: " + varargin{4}, ...
                              d, 'OutputFormat', 'uniform');
            l.Info   = string(NaN(height(l), 1));
            varsToCombine = {'Types', 'When', 'LabIndex', 'Info'};
            obj.CombinedLog = sortrows([ msg(:, varsToCombine); 
                                m(:, varsToCombine); 
                                d(:, varsToCombine); 
                                l(:, varsToCombine) ]);
        end
        
        function plot(obj, axh, procs)
            if nargin < 2 || isempty(axh)
                axh = gca;
            end
            if nargin < 3 || isempty(procs)
                procs = unique(obj.CombinedLog.LabIndex);
            end
            cla(axh);
            minTime = min(obj.CombinedLog.Times, [], 1, 'omitnan');
            
            fields = { 'MessageLog', 'MatlabLog', 'DispatchLog', ...
                       'LanguageConstructLog' };
            offset = [0 0.2 0.4 0.6];
            height = 0.2;
            for idx = 1:numel(fields)
                iPlotOne(obj.(fields{idx}), minTime, offset(idx), height, procs);
            end
            set(axh, 'YTick', 1:numel(procs), ...
                     'YTickLabels', strrep(cellstr(string(procs)), '-1', 'client'));
        end
    end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Aggregation function for UNSTACK call. Ensures a scalar output of the correct
% type by taking the first value if there are several, or creating a zero-valued
% scalar of the correct type if empty.
function out = iEnsureScalar(vals)
if numel(vals) >= 1
    out = vals(1);
else
    if isnumeric(vals)
        out = zeros('like', vals);
    elseif isduration(vals)
        out = seconds(0);
    end
end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Plot for a single log timetable
function iPlotOne(tt, startTime, offset, height, procs)

    t = timetable2table(tt);
    t.Times = t.Times - startTime;

    % We need 'OtherLabIndex' for unstack to disambiguate the message log -
    % but only if it's present
    wantVarsForUnstack = {'Times', 'Types', 'When', 'LabIndex', 'OtherLabIndex', 'Id'};
    varsForUnstack = intersect(wantVarsForUnstack, t.Properties.VariableNames);
    
    % Move the contents of 'Times' into variables corresponding to 'When'. Using
    % iEnsureScalar as the AggregationFunction collapses duplicate rows (where
    % necessary) by picking the first such row.
    t = unstack(t(:, varsForUnstack), 'Times', 'When', ...
                'AggregationFunction', @iEnsureScalar);

    [isin, modLabIndex] = ismember(t.LabIndex, procs);
    
    t = t(isin, :);
    
    x = seconds([t.STARTED, t.COMPLETED])';
    x = x([1 1 2 2], :);
    y = reshape(modLabIndex(isin), 1, []);
    y(y == -1) = 0;
    y = [y; y; y; y] + [offset; offset + height; offset + height; offset];
    c = double(t.Types).';
    c = offset + (c ./ max(c) .* height);
    p = patch(x, y, c);
    p.FaceVertexCData = reshape(c .* ones(4,1), [], 1);
    p.EdgeColor = 'flat';
end
