1########################################################################
2##
3## Copyright (C) 2015-2021 The Octave Project Developers
4##
5## See the file COPYRIGHT.md in the top-level directory of this
6## distribution or <https://octave.org/copyright/>.
7##
8## This file is part of Octave.
9##
10## Octave is free software: you can redistribute it and/or modify it
11## under the terms of the GNU General Public License as published by
12## the Free Software Foundation, either version 3 of the License, or
13## (at your option) any later version.
14##
15## Octave is distributed in the hope that it will be useful, but
16## WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20## You should have received a copy of the GNU General Public License
21## along with Octave; see the file COPYING.  If not, see
22## <https://www.gnu.org/licenses/>.
23##
24########################################################################
25
26## -*- texinfo -*-
27## @deftypefn  {} {} profexport (@var{dir})
28## @deftypefnx {} {} profexport (@var{dir}, @var{data})
29## @deftypefnx {} {} profexport (@var{dir}, @var{name})
30## @deftypefnx {} {} profexport (@var{dir}, @var{name}, @var{data})
31##
32## Export profiler data as HTML.
33##
34## Export the profiling data in @var{data} into a series of HTML files in
35## the folder @var{dir}.  The initial file will be
36## @file{@var{data}/index.html}.
37##
38## If @var{name} is specified, it must be a string that contains a ``name''
39## for the profile being exported.  This name is included in the HTML.
40##
41## The input @var{data} is the structure returned by @code{profile ("info")}.
42## If unspecified, @code{profexport} will use the current profile dataset.
43##
44## @seealso{profshow, profexplore, profile}
45## @end deftypefn
46
47## Built-in profiler.
48function profexport (dir, name = "", data)
49
50  if (nargin < 1 || nargin > 3)
51    print_usage ();
52  endif
53
54  if (! ischar (dir))
55    error ("profexport: DIR must be a string");
56  endif
57
58  if (nargin == 1)
59    data = profile ("info");
60  elseif (nargin == 2)
61    if (isstruct (name))
62      data = name;
63      name = "";
64    else
65      if (! ischar (name))
66        error ("profexport: NAME must be a string");
67      endif
68      data = profile ("info");
69    endif
70  endif
71
72  if (! isfolder (dir))
73    ok = mkdir (dir);
74    if (! ok)
75      error ("profexport: failed to create output directory '%s'", dir);
76    endif
77  endif
78
79  if (! copyfile (__dataFilename ("style.css"), dir))
80    error ("profexport: failed to copy data file to directory '%s'", dir)
81  endif
82
83  if (isempty (name))
84    name = "Profile";
85  else
86    name = ["Profile - " __escapeHtml(name)];
87  endif
88
89  __writeFlat (fullfile (dir, "index.html"), name, data.FunctionTable);
90  for i = 1 : length (data.FunctionTable)
91    __writeFunc (fullfile (dir, sprintf ("func-%d.html", i)), name, ...
92                 data.FunctionTable, i);
93  endfor
94
95  top = struct ("name", "Top");
96  __writeHierarchical (dir, name, data.FunctionTable, ...
97                       {top}, data.Hierarchical, 1);
98
99endfunction
100
101################################################################################
102## Write flat profile.
103
104function __writeFlat (file, name, table)
105
106  template = __readTemplate ("flat.html");
107  entryTemplate = __readTemplate ("flat_entry.html");
108
109  ## Construct the entries string.
110  ## This follows the same logic that is used in profshow.
111  times = [ table.TotalTime ];
112  totalTime = sum (times);
113  [~, p] = sort (times, "descend");
114  entries = "";
115  for i = 1 : length (table)
116    row = table(p(i));
117
118    cur = entryTemplate;
119    cur = strrep (cur, "%num", sprintf ("%d", p(i)));
120    cur = strrep (cur, "%name", __escapeHtml (row.FunctionName));
121    cur = strrep (cur, "%timeabs", sprintf ("%.3f", row.TotalTime));
122    cur = strrep (cur, "%timerel", ...
123                  sprintf ("%.2f", 100 * row.TotalTime / totalTime));
124    cur = strrep (cur,  "%calls", sprintf ("%d", row.NumCalls));
125
126    entries = [entries, cur];
127  endfor
128
129  ## Build full page content.
130  res = template;
131  res = strrep (res, "%title", name);
132  res = strrep (res, "%entries", entries);
133
134  ## Write out the file.
135  __writeToFile (file, res);
136
137endfunction
138
139################################################################################
140## Write "function profile" pages.
141
142function __writeFunc (file, name, table, ind)
143
144  template = __readTemplate ("function.html");
145  row = table(ind);
146
147  ## Fill in basic data.
148  res = template;
149  res = strrep (res, "%title", name);
150  res = strrep (res, "%name", __escapeHtml (row.FunctionName));
151  res = strrep (res, "%timeabs", sprintf ("%.3f", row.TotalTime));
152  res = strrep (res, "%calls", sprintf ("%d", row.NumCalls));
153
154  ## Build up attribute list.
155  attr = "";
156  if (row.IsRecursive)
157    attr = "recursive";
158  endif
159  res = strrep (res, "%attr", attr);
160
161  ## Add parent and child list.
162  parents = __buildParentOrChildList (table, row.Parents);
163  res = strrep (res, "%parents", parents);
164  children = __buildParentOrChildList (table, row.Children);
165  res = strrep (res, "%children", children);
166
167  ## Write out the file.
168  __writeToFile (file, res);
169
170endfunction
171
172function lst = __buildParentOrChildList (table, inds)
173
174  if (length (inds) == 0)
175    lst = "none";
176    return;
177  endif
178
179  template = "<a href='func-%num.html'>%name</a>";
180
181  lst = "";
182  for i = 1 : length (inds)
183    if (i > 1)
184      lst = [lst, ", "];
185    endif
186
187    cur = template;
188    cur = strrep (cur, "%num", sprintf ("%d", inds(i)));
189    cur = strrep (cur, "%name", __escapeHtml (table(inds(i)).FunctionName));
190    lst = [lst, cur];
191  endfor
192
193endfunction
194
195################################################################################
196## Write a hierarchical profile page.
197
198## In order to generate unique filenames for the pages, we keep a running
199## counter that is passed through and updated by the recursive calls.
200## The function returns two counter values:  The one that is chosen
201## for its own page (so that parent nodes can link down to them)
202## and the next value to be passed to the next call.
203
204function [mine, cnt] = __writeHierarchical (dir, name, funcs, ...
205                                            parents, children, cnt)
206
207  template = __readTemplate ("hierarchical.html");
208  entryTemplate = __readTemplate ("hierarchical_entry.html");
209
210  % Fill in basic data and parent breadcrumbs.
211  res = template;
212  res = strrep (res, "%title", name);
213  parentsStr = __hierarchicalParents (parents);
214  res = strrep (res, "%parents", parentsStr);
215
216  % Set this page's counter and update parents struct with it.
217  mine = cnt++;
218  parents{end}.cnt = mine;
219  file = sprintf ("%s/hierarchy-%d.html", dir, mine);
220
221  % Sort children by time.
222  times = -[ children.TotalTime ];
223  [~, p] = sort (times);
224  children = children(p);
225
226  ## Recurse on children and construct entry list.
227  entries = "";
228  for i = 1 : length (children)
229    cur = children(i);
230    curName = funcs(cur.Index).FunctionName;
231
232    newParents = parents;
233    newParents{end + 1} = struct ("name", curName);
234    [childCnt, cnt] = __writeHierarchical (dir, name, funcs, ...
235                                           newParents, cur.Children, cnt);
236
237    str = entryTemplate;
238    str = strrep (str, "%cnt", sprintf ("%d", childCnt));
239    str = strrep (str, "%name", __escapeHtml (curName));
240    str = strrep (str, "%total", sprintf ("%.3f", cur.TotalTime));
241    str = strrep (str, "%self", sprintf ("%.3f", cur.SelfTime));
242    str = strrep (str, "%calls", sprintf ("%d", cur.NumCalls));
243
244    entries = [entries, str];
245  endfor
246  res = strrep (res, "%entries", entries);
247
248  ## Write out the file.
249  __writeToFile (file, res);
250
251endfunction
252
253function str = __hierarchicalParents (parents)
254
255  ## We always have at least the "Top" entry!
256  assert (length (parents) > 0);
257
258  template = "<a href='hierarchy-%cnt.html'>%name</a>";
259  lastTemplate = "<strong>%name</strong>";
260
261  str = "";
262  for i = 1 : length (parents) - 1
263    cur = template;
264    cur = strrep (cur, "%cnt", sprintf ("%d", parents{i}.cnt));
265    cur = strrep (cur, "%name", __escapeHtml (parents{i}.name));
266    str = [str, cur, " > "];
267  endfor
268
269  cur = lastTemplate;
270  cur = strrep (cur, "%name", __escapeHtml (parents{end}.name));
271  str = [str, cur];
272
273endfunction
274
275################################################################################
276## General helper functions.
277
278function __writeToFile (file, str)
279  fid = fopen (file, "w");
280  if (fid < 0)
281    error ("profexport: failed to open '%s' for writing", file);
282  endif
283  fputs (fid, str);
284  fclose (fid);
285endfunction
286
287function fn = __dataFilename (name)
288  etcdir = __octave_config_info__ ("octetcdir");
289  fn = fullfile (etcdir, "profiler", name);
290endfunction
291
292function str = __readTemplate (name)
293  fn = __dataFilename (name);
294  str = fileread (fn);
295endfunction
296
297function str = __escapeHtml (str)
298  str = strrep (str, '&', "&amp;");
299  str = strrep (str, '<', "&lt;");
300  str = strrep (str, '>', "&gt;");
301  str = strrep (str, '"', "&quot;");
302endfunction
303
304################################################################################
305## Tests and demo.
306
307%!demo
308%! profile on;
309%! A = rand (100);
310%! B = expm (A);
311%! profile off;
312%! dir = tempname ();
313%! profexport (dir, "Example Profile");
314%! open (fullfile (dir, "index.html"));
315
316## Test input validation
317%!error profexport ()
318%!error profexport (1)
319%!error profexport (1, 2, 3, 4)
320%!error <DIR must be a string> profexport (5)
321%!error <NAME must be a string> profexport ("dir", 5)
322