## Copyright (C) 2015 Daniel Kraft
##
## This file is part of Octave.
##
## Octave is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 3 of the License, or (at
## your option) any later version.
##
## Octave is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Octave; see the file COPYING. If not, see
## .
## -*- texinfo -*-
## @deftypefn {Function File} {} profexport (@var{dir}, @var{name}, @var{data})
## @deftypefnx {Function File} {} profexport (@var{dir}, @var{name})
## @deftypefnx {Function File} {} profexport (@var{dir}, @var{data})
## @deftypefnx {Function File} {} profexport (@var{dir})
##
## Export profiler data as HTML.
##
## Export the profiling data in @var{data} into a series of HTML
## files in the folder @var{dir}. The initial file will be
## @address@hidden/index.html}.
##
## If @var{name} is specified, it must be a string that contains a ``name''
## for the profile being exported. This name is included in the HTML.
##
## The input @var{data} is the structure returned by @code{profile ("info")}.
## If unspecified, @code{profexport} will use the current profile dataset.
##
## @seealso{profshow, profexplore, profile}
## @end deftypefn
## Built-in profiler.
## Author: Daniel Kraft
function profexport (dir, name, data)
if (nargin > 3)
print_usage ();
endif
if (!ischar (dir))
print_usage ();
endif
if (nargin == 1)
data = profile ("info");
name = "";
elseif (nargin == 2)
if (isstruct (name))
data = name;
name = "";
else
if (!ischar (name))
print_usage ();
endif
data = profile ("info");
endif
endif
if (!exist (dir, "dir"))
ok = mkdir (dir);
if (!ok)
error ("failed to create output directory '%s'", dir);
endif
endif
if (length (name) > 0)
% FIXME: Escape!
name = sprintf ("Profile - %s", name);
else
name = "Profile";
endif
__writeFlat (fullfile (dir, "index.html"), name, data.FunctionTable);
for i = 1 : length (data.FunctionTable)
__writeFunc (fullfile (dir, sprintf ("func-%d.html", i)), name, ...
data.FunctionTable, i);
endfor
top = struct ("name", "Top");
__writeHierarchical (dir, name, data.FunctionTable, ...
{top}, data.Hierarchical, 1);
endfunction
% TODO: Tests and demos.
################################################################################
# Write flat profile.
function __writeFlat (file, name, table)
template = "\
\n\
\n\
\n\
%title\n\
\n\
\n\
Flat %title
\n\
hierarchical profile
\n\
\n\
\n\
\n\
Function | \n\
Time (s) | \n\
Time (%) | \n\
Calls | \n\
\n\
\n\
\n\
%entries\n\
\n\
\n\
\n\
\n";
entryTemplate = "\
\n\
%name | \n\
%timeabs | \n\
%timerel | \n\
%calls | \n\
\n";
% Construct the entries string. This follows the same logic
% that is used in profshow.
times = [ table.TotalTime ];
totalTime = sum (times);
[~, p] = sort (times, "descend");
entries = "";
for i = 1 : length (table)
row = table(p(i));
cur = entryTemplate;
cur = strrep (cur, "%num", sprintf ("%d", p(i)));
% FIXME: Implement escaping!
cur = strrep (cur, "%name", row.FunctionName);
cur = strrep (cur, "%timeabs", sprintf ("%.3f", row.TotalTime));
cur = strrep (cur, "%timerel", ...
sprintf ("%.2f", 100 * row.TotalTime / totalTime));
cur = strrep (cur, "%calls", sprintf ("%d", row.NumCalls));
entries = [entries, cur];
endfor
% Build full page content.
res = template;
res = strrep (res, "%title", name);
res = strrep (res, "%entries", entries);
% Write out the file.
__writeToFile (file, res);
endfunction
################################################################################
# Write "function profile" pages.
function __writeFunc (file, name, table, ind)
row = table(ind);
template = "\
\n\
\n\
\n\
%title\n\
\n\
\n\
Function %name
\n\
flat profile
\n\
\n\
- Attributes:
\n\
- %attr
\n\
- Total time:
\n\
- %timeabs seconds
\n\
- Number of calls:
\n\
- %calls
\n\
- Called by:
\n\
- %parents
\n\
- Calling:
\n\
- %children
\n\
\n\
\n\
\n";
% Fill in basic data.
res = template;
res = strrep (res, "%title", name);
% FIXME: Escape!
res = strrep (res, "%name", row.FunctionName);
res = strrep (res, "%timeabs", sprintf ("%.3f", row.TotalTime));
res = strrep (res, "%calls", sprintf ("%d", row.NumCalls));
% Build up attribute list.
attr = "";
if (row.IsRecursive)
attr = "recursive";
endif
res = strrep (res, "%attr", attr);
% Add parent and child list.
parents = __buildParentOrChildList (table, row.Parents);
res = strrep (res, "%parents", parents);
children = __buildParentOrChildList (table, row.Children);
res = strrep (res, "%children", children);
% Write out the file.
__writeToFile (file, res);
endfunction
function lst = __buildParentOrChildList (table, inds)
if (length (inds) == 0)
lst = "none";
return;
endif
template = "%name";
lst = "";
for i = 1 : length (inds)
if (i > 1)
lst = [lst, ", "];
endif
cur = template;
cur = strrep (cur, "%num", sprintf ("%d", inds(i)));
% FIXME: Escape!
cur = strrep (cur, "%name", table(inds(i)).FunctionName);
lst = [lst, cur];
endfor
endfunction
################################################################################
# Write a hierarchical profile page.
% In order to generate unique filenames for the pages, we keep a running
% counter that is passed through and updated by the recursive calls.
% The function returns two counter values: The one that is chosen
% for its own page (so that parent nodes can link down to them)
% and the next value to be passed to the next call.
function [mine, cnt] = __writeHierarchical (dir, name, funcs, ...
parents, children, cnt)
template = "\
\n\
\n\
\n\
%title\n\
\n\
\n\
Hierarchical %title
\n\
flat profile
\n\
%parents
\n\
\n\
\n\
\n\
Function | \n\
Total (s) | \n\
Self (s) | \n\
Calls | \n\
\n\
\n\
\n\
%entries\n\
\n\
\n\
\n\
\n";
entryTemplate = "\
\n\
%name | \n\
%total | \n\
%self | \n\
%calls | \n\
\n";
% Fill in basic data and parent breadcrumbs.
res = template;
res = strrep (res, "%title", name);
parentsStr = __hierarchicalParents (parents);
res = strrep (res, "%parents", parentsStr);
% Set this page's counter and update parents struct with it.
mine = cnt++;
parents{end}.cnt = mine;
file = sprintf ("%s/hierarchy-%d.html", dir, mine);
% Sort children by time.
times = -[ children.TotalTime ];
[~, p] = sort (times);
children = children(p);
% Recurse on children and construct entry list.
entries = "";
for i = 1 : length (children)
cur = children(i);
curName = funcs(cur.Index).FunctionName;
newParents = parents;
newParents{end + 1} = struct ("name", curName);
[childCnt, cnt] = __writeHierarchical (dir, name, funcs, ...
newParents, cur.Children, cnt);
str = entryTemplate;
str = strrep (str, "%cnt", sprintf ("%d", childCnt));
% FIXME: Escape!
str = strrep (str, "%name", curName);
str = strrep (str, "%total", sprintf ("%.3f", cur.TotalTime));
str = strrep (str, "%self", sprintf ("%.3f", cur.SelfTime));
str = strrep (str, "%calls", sprintf ("%d", cur.NumCalls));
entries = [entries, str];
endfor
res = strrep (res, "%entries", entries);
% Write out the file.
__writeToFile (file, res);
endfunction
function str = __hierarchicalParents (parents)
% We always have at least the "Top" entry!
assert (length (parents) > 0);
template = "%name";
lastTemplate = "%name";
str = "";
for i = 1 : length (parents) - 1
cur = template;
cur = strrep (cur, "%cnt", sprintf ("%d", parents{i}.cnt));
% FIXME: Escape!
cur = strrep (cur, "%name", parents{i}.name);
str = [str, cur, " > "];
endfor
cur = lastTemplate;
% FIXME: Escape!
cur = strrep (cur, "%name", parents{end}.name);
str = [str, cur];
endfunction
################################################################################
# General helper functions.
function __writeToFile (file, str)
fid = fopen (file, "w");
if (fid < 0)
error ("failed to open '%s' for writing", file);
endif
fputs (fid, str);
fclose (fid);
endfunction