# HG changeset patch
# User Daniel Kraft <dkraft@google.com>
# Date 1462817823 -7200
#      Mon May 09 20:17:03 2016 +0200
# Node ID 5e4d1adc519b6fd24741a8bf625e3903297feb72
# Parent  fa0462ab5c201941e3adc74cf8dcb331e8510594
Add profexport to write profiler output as HTML.

* scripts/profiler/profexport.m: New function.
* scripts/profiler/html: New directory with supporting templates/data.
* scripts/profiler/module.mk: Add necessary files to build system.

diff -r fa0462ab5c20 -r 5e4d1adc519b scripts/profiler/html/flat.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/profiler/html/flat.html	Mon May 09 20:17:03 2016 +0200
@@ -0,0 +1,25 @@
+<!-- This file was generated by GNU Octave's profiler.  -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>%title</title>
+    <link rel="stylesheet" type="text/css" href="style.css">
+  </head>
+  <body>
+    <h1>Flat %title</h1>
+    <p><a href="hierarchy-1.html">hierarchical profile</a></p>
+    <table>
+      <thead>
+        <tr>
+          <th>Function</th>
+          <th>Time (s)</th>
+          <th>Time (%)</th>
+          <th>Calls</th>
+        </tr>
+      </thead>
+      <tbody>
+%entries
+      </tbody>
+    </table>
+  </body>
+</html>
diff -r fa0462ab5c20 -r 5e4d1adc519b scripts/profiler/html/flat_entry.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/profiler/html/flat_entry.html	Mon May 09 20:17:03 2016 +0200
@@ -0,0 +1,6 @@
+<tr>
+  <td><a href="func-%num.html">%name</a></td>
+  <td>%timeabs</td>
+  <td>%timerel</td>
+  <td>%calls</td>
+</tr>
diff -r fa0462ab5c20 -r 5e4d1adc519b scripts/profiler/html/function.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/profiler/html/function.html	Mon May 09 20:17:03 2016 +0200
@@ -0,0 +1,24 @@
+<!-- This file was generated by GNU Octave's profiler.  -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>%title</title>
+    <link rel="stylesheet" type="text/css" href="style.css">
+  </head>
+  <body>
+    <h1>Function %name</h1>
+    <p><a href="index.html">flat profile</a></p>
+    <dl>
+      <dt>Attributes:</dt>
+      <dd>%attr</dd>
+      <dt>Total time:</dt>
+      <dd>%timeabs seconds</dd>
+      <dt>Number of calls:</dt>
+      <dd>%calls</dd>
+      <dt>Called by:</dt>
+      <dd>%parents</dd>
+      <dt>Calling:</dt>
+      <dd>%children</dd>
+    </dl>
+  </body>
+</html>
diff -r fa0462ab5c20 -r 5e4d1adc519b scripts/profiler/html/hierarchical.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/profiler/html/hierarchical.html	Mon May 09 20:17:03 2016 +0200
@@ -0,0 +1,26 @@
+<!-- This file was generated by GNU Octave's profiler.  -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>%title</title>
+    <link rel="stylesheet" type="text/css" href="style.css">
+  </head>
+  <body>
+    <h1>Hierarchical %title</h1>
+    <p><a href="index.html">flat profile</a></p>
+    <p>%parents</p>
+    <table>
+      <thead>
+        <tr>
+          <th>Function</th>
+          <th>Total (s)</th>
+          <th>Self (s)</th>
+          <th>Calls</th>
+        </tr>
+      </thead>
+      <tbody>
+%entries
+      </tbody>
+    </table>
+  </body>
+</html>
diff -r fa0462ab5c20 -r 5e4d1adc519b scripts/profiler/html/hierarchical_entry.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/profiler/html/hierarchical_entry.html	Mon May 09 20:17:03 2016 +0200
@@ -0,0 +1,6 @@
+<tr>
+  <td><a href="hierarchy-%cnt.html">%name</a></td>
+  <td>%total</td>
+  <td>%self</td>
+  <td>%calls</td>
+</tr>
diff -r fa0462ab5c20 -r 5e4d1adc519b scripts/profiler/html/style.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/profiler/html/style.css	Mon May 09 20:17:03 2016 +0200
@@ -0,0 +1,24 @@
+/*
+Copyright (C) 2016 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
+<http://www.gnu.org/licenses/>.
+*/
+
+tr:nth-of-type(odd)
+{
+  background-color: #ccc;
+}
diff -r fa0462ab5c20 -r 5e4d1adc519b scripts/profiler/module.mk
--- a/scripts/profiler/module.mk	Thu May 05 11:19:59 2016 +0200
+++ b/scripts/profiler/module.mk	Mon May 09 20:17:03 2016 +0200
@@ -3,12 +3,21 @@
 
 scripts_profiler_FCN_FILES = \
   scripts/profiler/profexplore.m \
+  scripts/profiler/profexport.m \
   scripts/profiler/profile.m \
   scripts/profiler/profshow.m
 
 scripts_profilerdir = $(fcnfiledir)/profiler
+scripts_profiler_DATA = $(scripts_profiler_FCN_FILES)
 
-scripts_profiler_DATA = $(scripts_profiler_FCN_FILES)
+scripts_profiler_htmldir = $(octetcdir)/profiler
+scripts_profiler_html_DATA = \
+  scripts/profiler/html/flat.html \
+  scripts/profiler/html/flat_entry.html \
+  scripts/profiler/html/function.html \
+  scripts/profiler/html/hierarchical.html \
+  scripts/profiler/html/hierarchical_entry.html \
+  scripts/profiler/html/style.css
 
 FCN_FILES += \
   $(scripts_profiler_FCN_FILES)
diff -r fa0462ab5c20 -r 5e4d1adc519b scripts/profiler/profexport.m
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/profiler/profexport.m	Mon May 09 20:17:03 2016 +0200
@@ -0,0 +1,303 @@
+## Copyright (C) 2015-2016 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
+## <http://www.gnu.org/licenses/>.
+
+## -*- 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
+## @file{@var{data}/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 <dkraft@google.com>
+
+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 (!copyfile (__dataFilename ("style.css"), dir))
+    error ("failed to copy data file to directory '%s'", dir)
+  endif
+
+  if (length (name) > 0)
+    name = sprintf ("Profile - %s", __escapeHtml (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
+
+################################################################################
+# Write flat profile.
+
+function __writeFlat (file, name, table)
+  template = __readTemplate ("flat.html");
+  entryTemplate = __readTemplate ("flat_entry.html");
+
+  % 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)));
+    cur = strrep (cur, "%name", __escapeHtml (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)
+  template = __readTemplate ("function.html");
+  row = table(ind);
+
+  % Fill in basic data.
+  res = template;
+  res = strrep (res, "%title", name);
+  res = strrep (res, "%name", __escapeHtml (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 = "<a href='func-%num.html'>%name</a>";
+
+  lst = "";
+  for i = 1 : length (inds)
+    if (i > 1)
+      lst = [lst, ", "];
+    endif
+
+    cur = template;
+    cur = strrep (cur, "%num", sprintf ("%d", inds(i)));
+    cur = strrep (cur, "%name", __escapeHtml (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 = __readTemplate ("hierarchical.html");
+  entryTemplate = __readTemplate ("hierarchical_entry.html");
+
+  % 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));
+    str = strrep (str, "%name", __escapeHtml (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 = "<a href='hierarchy-%cnt.html'>%name</a>";
+  lastTemplate = "<strong>%name</strong>";
+
+  str = "";
+  for i = 1 : length (parents) - 1
+    cur = template;
+    cur = strrep (cur, "%cnt", sprintf ("%d", parents{i}.cnt));
+    cur = strrep (cur, "%name", __escapeHtml (parents{i}.name));
+    str = [str, cur, " > "];
+  endfor
+
+  cur = lastTemplate;
+  cur = strrep (cur, "%name", __escapeHtml (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
+
+function fn = __dataFilename (name)
+  etcdir = __octave_config_info__ ("octetcdir");
+  fn = fullfile (etcdir, "profiler", name);
+endfunction
+
+function str = __readTemplate (name)
+  fn = __dataFilename (name);
+  str = fileread (fn);
+endfunction
+
+function str = __escapeHtml (str)
+  str = strrep (str, "&", "&amp;");
+  str = strrep (str, "<", "&lt;");
+  str = strrep (str, ">", "&gt;");
+  str = strrep (str, "\"", "&quot;");
+endfunction
+
+################################################################################
+# Tests and demo.
+
+%!demo
+%! profile on;
+%! A = rand (100);
+%! B = expm (A);
+%! profile off;
+%! dir = tempname ();
+%! profexport (dir, "Example Profile");
+%! printf ("Open %s/index.html to browse the profile.\n", dir);
+
+%!error profexport ()
+%!error profexport (1)
+%!error profexport (1, 2, 3, 4)
+%!error profexport ("dir", 5)
