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, '&', "&"); 299 str = strrep (str, '<', "<"); 300 str = strrep (str, '>', ">"); 301 str = strrep (str, '"', """); 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