/[drupal]/contributions/modules/visualize_backtrace/visualize_backtrace.module
ViewVC logotype

Contents of /contributions/modules/visualize_backtrace/visualize_backtrace.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.2 - (show annotations) (download) (as text)
Wed Jan 28 02:53:05 2009 UTC (9 months, 3 weeks ago) by kentbye
Branch: MAIN
CVS Tags: HEAD
Changes since 1.1: +489 -450 lines
File MIME type: text/x-php
#365303 which adds support for outputting an entire functional call stack from a Drupal page load into a nested krumo-styled output, also added support for dumping a full call stack to an output file so that you could do a diff of the Drupal functions called between two page loads, and some other miscellaneous cleanup
1 <?php
2 // $Id: visualize_backtrace.module,v 1.1.2.2.2.1 2008/11/07 02:10:45 kentbye Exp $
3
4 /**
5 * Implementation of hook_menu
6 */
7 function visualize_backtrace_menu() {
8 $items = array();
9
10 $items['view_source'] = array(
11 'title' => t('source'),
12 'page callback' => 'visualize_backtrace_view_source_code',
13 'access arguments' => array('administer visualize backtrace'),
14 'type' => MENU_CALLBACK,
15 );
16
17 $items['visualize_backtrace'] = array(
18 'title' => t('Backtrace Graphs of Drupal Function Calls'),
19 'page callback' => 'visualize_backtrace_generate',
20 'access arguments' => array('administer visualize backtrace'),
21 'type' => MENU_CALLBACK,
22 );
23
24 $items['view_traces'] = array(
25 'title' => t('XDebug Traces'),
26 'page callback' => 'visualize_backtrace_display_latest_traces',
27 'access arguments' => array('administer visualize backtrace'),
28 'type' => MENU_CALLBACK,
29 );
30
31 $items['test_xdebug'] = array(
32 'title' => t('Test to See if XDebug is Creating Trace Files'),
33 'page callback' => 'visualize_backtrace_test_xdebug',
34 'access arguments' => array('administer visualize backtrace'),
35 'type' => MENU_CALLBACK,
36 );
37
38 $items['admin/settings/visualize_backtrace'] = array(
39 'title' => 'Visualize Backtrace Settings',
40 'description' => 'Settings to display more function call information.',
41 'page callback' => 'drupal_get_form',
42 'page arguments' => array('visualize_backtrace_admin_settings'),
43 'access arguments' => array('administer site configuration'),
44 'type' => MENU_NORMAL_ITEM,
45 );
46
47 $items['visualize_backtrace_color_codes'] = array(
48 'title' => t('Color Code Key for the Drupal Function Filenames'),
49 'page callback' => 'visualize_backtrace_color_codes',
50 'access arguments' => array('administer visualize backtrace'),
51 'type' => MENU_CALLBACK,
52 );
53
54 return $items;
55 }
56
57 /**
58 * Implementation of hook_help().
59 */
60 function visualize_backtrace_help($path, $arg) {
61 switch ($path) {
62 case 'admin/settings/visualize_backtrace':
63 return '<p>'. t('First test to see if you have XDebug properly set up and generating trace files by visiting this !test_page. <br />More detailed set-up instructions can be found in the INSTALL.txt file. <br />The "Visualize Backtrace" can then be turned on via the !block page.<br />View the !colorcodepage for Drupal Functions in the Backtraces.', array('!test_page' => l(t('XDebug Test Page'), 'test_xdebug'), '!block' => l(t('block administration'), 'admin/build/block'), '!colorcodepage' => l(t('Color Code Page'), 'visualize_backtrace_color_codes'))) .'</p>';
64 case 'view_traces':
65 return '<p>'. t('The most recent trace files are listed at the top, and are shown in reverse chronological order. If these files are stored in your /tmp directory, then they may be purged if you reboot your computer. The generated *.dot files stored in the files directory will need to be manually purged from time to time.') .'</p>';
66 }
67 }
68
69 /**
70 * Implementation of hook_admin_settings().
71 */
72 function visualize_backtrace_admin_settings() {
73 $form['visualize_backtrace_execution_threshold'] = array(
74 '#type' => 'textfield',
75 '#title' => t('Highlight Drupal Functions Longer Than'),
76 '#description' => t('Enter an integer in milliseconds. Any Drupal function which takes longer than this many milliseconds will be highlighted red in the callstack table. This is also the threshold for marking the total time of the capture function and all of its children functions.'),
77 '#default_value' => variable_get('visualize_backtrace_execution_threshold', 5),
78 '#size' => 4,
79 '#maxlength' => 4,
80 );
81
82 $form['visualize_backtrace_contrib_module_filepath'] = array(
83 '#type' => 'textfield',
84 '#title' => t('Capture Contributed Module Functions at this Location'),
85 '#default_value' => variable_get('visualize_backtrace_contrib_module_filepath', 'sites/'),
86 '#description' => t('Enter the directory that contains the module functions that you want to capture through the shortcut link "Graph the Contributed Module Function Backtraces". The default directory will capture all contributed module functions, but it is possible to enter a specific module or theme directory here as well (i.e. "themes/garland" or "sites/all/modules/views").'),
87 '#size' => 40,
88 );
89
90 $form['visualize_backtrace_create_main_subsections'] = array(
91 '#type' => 'checkbox',
92 '#title' => t('Split the Main Section into Three Subsections'),
93 '#default_value' => variable_get('visualize_backtrace_create_main_subsections', 1),
94 '#description' => t("Sometimes the Main Section is too big to be plotted all at once, and this option splits up the main section into three equal sections, which makes it quicker and easier to plot in smaller segments. Turning this off will not create these three main subsection *.dot files whenever a new trace file is parsed")
95 );
96
97 $form['visualize_backtrace_source_code_link_location'] = array(
98 '#type' => 'select',
99 '#title' => t('Display Source Code Located at'),
100 '#default_value' => variable_get('visualize_backtrace_source_code_link_location', 'local'),
101 '#options' => array(
102 'local' => t('Local copy'),
103 'drupal60_cvs' => t('Drupal 6.0 CVS'),
104 ),
105 '#description' => t('Chose whether to dynamically link to locally-stored copies of source code on your computer or to the static versions from cvs.drupal.org'),
106 );
107
108 $form['visualize_backtrace_source_code_font_size'] = array(
109 '#type' => 'textfield',
110 '#title' => t('View Source Code Font Size'),
111 '#description' => t('The default font size for displaying the source code is 12px.'),
112 '#default_value' => variable_get('visualize_backtrace_source_code_font_size', 12),
113 '#size' => 4,
114 '#maxlength' => 4,
115 );
116
117 $form['visualize_backtrace_zgrviewer_location'] = array(
118 '#type' => 'textfield',
119 '#title' => t('Path Containing ZGRViewer'),
120 '#default_value' => variable_get('visualize_backtrace_zgrviewer_location', '/usr/local/bin/zgrviewer'),
121 '#description' => t('This is the unix path where the ZGRViewer is installed. The ZGRViewer is a java program that can be run from the command line to plot the *.dot flowchart visualizations. The INSTALL.txt contains more instructions for setting it up.'),
122 '#size' => 40,
123 );
124
125 $form['visualize_backtrace_hide_repeating_functions'] = array(
126 '#type' => 'checkbox',
127 '#title' => t('Hide Repeating Functions'),
128 '#default_value' => variable_get('visualize_backtrace_hide_repeating_functions', 1),
129 '#description' => t("Detect and consolidate repeating looping pairs of 1, 2 or 3 functions in order to make the flowcharts easier to read and quicker to load.")
130 );
131
132 $form['visualize_backtrace_debug_duplication'] = array(
133 '#type' => 'checkbox',
134 '#title' => t('Display debugging information for the repeating function detection algorithm'),
135 '#default_value' => variable_get('visualize_backtrace_debug_duplication', 0),
136 '#description' => t("Display some additional information in the Full Function Callstack table to help debug the visualize_backtrace_detect_repeating_functions() code. Only relevent when the Repeating Functions are being hidden.")
137 );
138
139
140 return system_settings_form($form);
141 }
142
143
144 /**
145 * Implementation of hook_block().
146 *
147 */
148 function visualize_backtrace_block($op = 'list', $delta = 0) {
149 if ($op == 'list') {
150 $blocks[0]['info'] = t('View Backtrace Graphs');
151 return $blocks;
152 }
153 else if ($op == 'view') {
154 switch ($delta) {
155 case 0:
156 if (user_access('administer visualize backtrace')) {
157 $block['subject'] = t('Visualize Backtrace');
158 // Get the trace log number by striping out number from /tmp/trace.####.xt file.
159 $current_trace_number = explode('.', xdebug_get_tracefile_name());
160 $current_trace_number = $current_trace_number[1];
161 $links[] = l('Generate Backtrace Data for this Page Load', 'visualize_backtrace/'. $current_trace_number .'/hide_callstack/all_sections');
162 $links[] .= l('View All Past Trace Files', 'view_traces');
163 $links[] .= l('Color Code Key', 'visualize_backtrace_color_codes');
164 $links[] .= l('Admin Settings', 'admin/settings/visualize_backtrace');
165 }
166 if ($links) {
167 $block['content'] = theme('item_list', $links);
168 }
169 break;
170 }
171 return $block;
172 }
173 }
174
175 /**
176 * Implementation of hook_perm
177 */
178 function visualize_backtrace_perm() {
179 return array('administer visualize backtrace');
180 }
181
182 /**
183 * Displays the Drupal source code stored on your localhost in the browswer so
184 * that you can toggle between the function call graph and the specific source
185 * code line number. It provides permalink anchors to each line of your Drupal
186 * source code along with proper PHP syntax highlighting
187 */
188 function visualize_backtrace_view_source_code() {
189 // For security considerations, prevent the settings.php file being displayed
190 // There should be a better way to do this -- as I think it's throwing a lot of error handlers
191 //return "test";
192 if (arg(3) != "settings.php") $handle = @fopen(arg(1) ."/". arg(2) ."/". arg(3) ."/". arg(4) ."/". arg(5)
193 ."/". arg(6) ."/". arg(7) ."/". arg(8) ."/". arg(9) ."/". arg(10) ."/", "r");
194 if ($handle) {
195 $i = 1;
196
197 $total_buffer = "";
198
199 while (!feof($handle)) {
200 $buffer = fgets($handle, 4096);
201 // Add in extra spaces before the colon so that the code will be aligned
202 // independent of extra spaces due to counter digit increases (i.e. 9 to 10)
203 $right_align = "";
204 if ($i < 1000) $right_align = " ";
205 if ($i < 100) $right_align = " ";
206 if ($i < 10) $right_align = " ";
207 $total_buffer .= "XXX_BEGIN_XXX". $i ."XXX_END_XXX". $i . $right_align .": ". $buffer;
208 $i++;
209 }
210
211 // Add a few more line breaks to separate the devel query info
212 $total_buffer .= "XXX_REPLACE_WITH_LINE_BREAKS_XXX";
213
214 // Do the PHP Syntax highlighting
215 $total_buffer = highlight_string($total_buffer, 1);
216
217 // Get rid of extra line breaks -- via codefilter.module @codefilter_process_php()
218 $total_buffer = str_replace("\n", '', $total_buffer);
219
220 // fix spaces -- via codefilter.module @codefilter_fix_spaces()
221 $total_buffer = preg_replace('@&nbsp;(?!&nbsp;)@', ' ', $total_buffer);
222
223 // Replace the placeholder XXX_BEGIN_XXX and XXX_END_XXX with anchor info (e.g. <a name ="45"></a> for line #45)
224 $total_buffer = str_replace("XXX_BEGIN_XXX", '<a name="', $total_buffer);
225 $total_buffer = str_replace("XXX_END_XXX", '"></a>', $total_buffer);
226 $total_buffer = str_replace("XXX_REPLACE_WITH_LINE_BREAKS_XXX", '<br /><br />', $total_buffer);
227 $total_buffer = '<div style="font-size: '. variable_get('visualize_backtrace_source_code_font_size', 12) .'px">'. $total_buffer .'</div>';
228
229 echo $total_buffer;
230 fclose($handle);
231 }
232 }
233
234 /**
235 * This is the main function that parses the trace file data into graphable
236 * flowcharts. The full function call backtrace is split into three sections:
237 * Bootstrap, Main, and Theme -- along with three optional subsections for the
238 * Main section if it is too large to graph at once. It is also possible to view
239 * the backtrace table and flowchart for a specific function
240 *
241 * Here are the URL Arguments:
242 * arg(0) = 'visualize_backtrace' initializes this fuction via menu_callback
243 * arg(1) = XDebug trace number to be parsed
244 * arg(2) = Flag to display a text table of all of the fuction calls that
245 * includes links to graph the backtraces of specific function calls
246 * arg(3) = $subsection_argument: Automatically graphs the flowchart of a
247 * specified section and displays just that portion of the callstack
248 * table. (i.e. "section_2")
249 * It also could be a flag to display either the
250 * backtrace or forwardtrace for a specific function
251 * (i.e. "show_function_backtrace" or "show_function_forwardtrace")
252 * It can also be a flag to display all of the functions referenced
253 * from a specific module filename
254 * (i.e "show_filename_backtrace" or "show_filename_forwardtrace")
255 * arg(4) = Show either the backtrace or forwardtrace of this specified function
256 * or specified filename.
257 * It could also be a flag to to display all of the functions that
258 * appear in filenames in the contributed module sites-all directory
259 * (i.e. "capture_all_contrib_functions")
260 *
261 * Quickly create the graphviz *.dot files with this splash page default view:
262 * /visualize_backtrace/1191366294/hide_callstack/all_sections/
263 *
264 * Create the *.dot files and display all of the function calls in table form:
265 * /visualize_backtrace/1191366294/view_callstack/all_sections/
266 *
267 * Create the *.dot files, display a subsection of the function calls in table
268 * form and automatically plot the backtrace flowchart for this section:
269 * /visualize_backtrace/1191366294/view_callstack/section_1
270 *
271 * Create the *.dot files, display the backtraces for a specific function call,
272 * and automatically plots them
273 * /visualize_backtrace/1191366294/view_callstack/show_function_backtrace/db_query/
274 *
275 * Create the *.dot files, display the backtraces & the forwardtraces for a
276 * specific function call, and automatically plots them.
277 * /visualize_backtrace/1191366294/view_callstack/show_function_forwardtrace/db_query/
278 *
279 * Create the *.dot files, display the backtrace for all of the functions from
280 * a specific module filename and automatically plots them.
281 * visualize_backtrace/1191366294/view_callstack/show_filename_backtrace/includes-menu.inc
282 *
283 * Create the *.dot files, display all the backtraces for all of the functions
284 * that are called from a contributed module filename & automatically plots them
285 * visualize_backtrace/1191366294/view_callstack/show_filename_backtrace/capture_all_contrib_functions
286 *
287 * NOTE: XDebug must be installed & properly configured for this to work.
288 * See the INSTALL.txt or http://xdebug.org for more information
289 */
290 function visualize_backtrace_generate() {
291 menu_rebuild();
292 global $base_url;
293
294 // The argument that is passed in is the trace number
295 $trace_number = arg(1);
296
297 // Determine the XDebug trace file path by parsing the result of the
298 // xdebug_get_tracefile_name() function
299 $trace_file = visualize_backtrace_get_xdebug_tracefile_path();
300
301 // Add the trace number to duplicate the full path (e.g. $trace_file = "/tmp/trace.1189698885.xt")
302 $trace_file .= "trace.". $trace_number .".xt";
303
304 // After determining the XDebug tracefile from the arguments, then parse out the relevent columns with an
305 // awk shell command. The following awk variables read in the tab-separated data created by XDebug w/ the
306 // following values: $1 = level, $2 = function_number, $3 = always_zero, $4 = time_index, $5 = memory,
307 // $6 = function_name, $7 = defined_function, $8 = via_filename,
308 // if ($10 exists) then {$9 = included_file; $10 = line_number} else {$9 = line_number}
309 // See this page to verify all of the information that XDebug should be outputting:
310 // http://xdebug.org/docs/all_settings#trace_format
311 // We're interested in the Drupal function data (i.e. $3 = 0 & $7 = 1), but the awk command
312 // also appends the previous PHP function data if the Drupal function's line number = 0.
313 // This command creates a new trace file w/ "parsed" appended to the name (i.e. "/tmp/trace.1189698885.xtparsed")
314 // Here is the full command $ cat -v /tmp/trace.1191544445.xt | awk {'if($3==0 && $7==0) {level = $1; function_number = $2; function_name = $6; line_number = $9; via_filename = $8; time_index = previous_time_index; memory = previous_memory} if($3 == 0 && $7 == 1 && $10) {if($10==0) {print $1 "\t" $2 "\t" $6 "\t" $9 "\t" $10 "\t" previous_time_index "\t" previous_memory "\t" function_name "\t" level "\t" function_number "\t" via_filename "\t" line_number "\t" time_index "\t" memory} else {print $1 "\t" $2 "\t" $6 "\t" $9 "\t" $10 "\t" previous_time_index "\t" previous_memory}} if($3 == 0 && $7 == 1 && !$10) {if($9==0) {print $1 "\t" $2 "\t" $6 "\t" $8 "\t" $9 "\t" previous_time_index "\t" previous_memory "\t" function_name "\t" level "\t" function_number "\t" via_filename "\t" line_number "\t" time_index "\t" memory} else {print $1 "\t" $2 "\t" $6 "\t" $8 "\t" $9 "\t" previous_time_index "\t" previous_memory}} if($5) {previous_time_index = $4; previous_memory = $5;} if($1 > 0 && $2 > 0 && !$4) {print $1 "\t" $2}'} > /tmp/trace.1191544445.xtparsed
315 $parse_trace_command = 'cat -v '. $trace_file .' | ';
316 $parse_trace_command .= 'awk {\'';
317 // Save the data for each non-Drupal PHP function to be able to reference it if a procedural hook is called
318 // The time index and memory values are after the function has run,
319 // and so shift them to the next row where they will represent the beginning
320 // & so that the time & memory delta will be ($function_count+1)-$function_count
321 $parse_trace_command .= 'if($3==0 && $7==0) {';
322 $parse_trace_command .= 'level = $1; function_number = $2; function_name = $6; line_number = $9; via_filename = $8; time_index = previous_time_index; memory = previous_memory';
323 $parse_trace_command .= '} ';
324 // Check to see if this is a Drupal function (i.e. $3 = 0 & $7 = 1)
325 // If there is a value for column $10, then that means that an include file is being added,
326 // which means that $9 = via_filename & $10 = line_number
327 $parse_trace_command .= 'if($3 == 0 && $7 == 1 && $10) {';
328 // If the line number is 0, then add in the previous PHP function data
329 $parse_trace_command .= 'if($10==0) {';
330 $parse_trace_command .= 'print $1 "\t" $2 "\t" $6 "\t" $9 "\t" $10 "\t" previous_time_index "\t" previous_memory "\t" function_name "\t" level "\t" function_number "\t" via_filename "\t" line_number "\t" time_index "\t" memory';
331 $parse_trace_command .= '} ';
332 $parse_trace_command .= 'else {';
333 $parse_trace_command .= 'print $1 "\t" $2 "\t" $6 "\t" $9 "\t" $10 "\t" previous_time_index "\t" previous_memory';
334 $parse_trace_command .= '}';
335 $parse_trace_command .= '} ';
336 // Most of the Drupal function won't have a column $10, which means that $8 = via_filename & $9 = line_number
337 $parse_trace_command .= 'if($3 == 0 && $7 == 1 && !$10) {';
338 // If the line number is 0, then add in the previous PHP function data
339 $parse_trace_command .= 'if($9==0) {';
340 $parse_trace_command .= 'print $1 "\t" $2 "\t" $6 "\t" $8 "\t" $9 "\t" previous_time_index "\t" previous_memory "\t" function_name "\t" level "\t" function_number "\t" via_filename "\t" line_number "\t" time_index "\t" memory';
341 $parse_trace_command .= '} ';
342 $parse_trace_command .= 'else {';
343 $parse_trace_command .= 'print $1 "\t" $2 "\t" $6 "\t" $8 "\t" $9 "\t" previous_time_index "\t" previous_memory';
344 $parse_trace_command .= '}';
345 $parse_trace_command .= '} ';
346 // Store this row's end time and end memory
347 // It will be used as the next row's beginning time and beginning memory
348 $parse_trace_command .= 'if($5) {';
349 $parse_trace_command .= 'previous_time_index = $4; previous_memory = $5;';
350 $parse_trace_command .= '} ';
351 // This will print out the final Time and Memory values on the 2nd to last line
352 // It will also inadvertantly output the XDebug version info from the first line,
353 // but it will be filtered out when it's read back in
354 $parse_trace_command .= 'if($1 > 0 && $2 > 0 && !$4) {';
355 $parse_trace_command .= 'print $1 "\t" $2';
356 $parse_trace_command .= '}';
357
358 $parse_trace_command .= '\'} > '. $trace_file .'parsed';
359 shell_exec($parse_trace_command);
360
361 // Flowchart nodes can be hypertext linked to the specific source code line where the function calls was executed
362 // If the link location variable is set to local, then it will use the visualize_backtrace_view_source_code() callback for viewing local copies of code
363 // The "local" option is primarily for viewing local copies of code for that is being written for custom modules or core development
364 // before a CVS commit has been made.
365 // The "Drupal52_CVS" option is for creating hyperlinked SVG files that have accessible permalinks to the CVS.drupal.org source code lines
366 // Primarily for documentation purposes, and the specific version numbers for Drupal 5.2 are included in the $lookup_table()
367 $source_code_link_location = variable_get('visualize_backtrace_source_code_link_location', 'local');
368 $lookup_table = visualize_backtrace_get_lookup_table_values($source_code_link_location);
369
370 // intitialize variables
371 $row = 0;
372 $function_call_data = array();
373 $function_call_data[1]['time_index'] = 0;
374 $function_call_data[1]['memory'] = 0;
375
376 // Extraneous filename path data will be stripped out (i.e. "/Library/WebServer/Documents/d52/") by subtracting out the length of UNIX base_path
377 // TODO: Possibly replace some of these things with The Drupal core functions...
378 $unix_base_path = shell_exec("pwd");
379
380 // Open up the parsed trace file
381 $handle = fopen($trace_file ."parsed", "r");
382 while (($data = fgetcsv($handle, 1000, "\t")) !== FALSE) {
383 // If there is a fifth column then that means that the Line = 0 for this function because it was called by
384 // a previously-called PHP function -- usually either preg_replace_callback or call_user_func_array
385 $data_length = count($data);
386 // If a row has 14 columns, then it has line = 8 and need to save the PHP function first.
387 // The first row of data also has 14 columns, but the last 9 columns are blank
388 if ($row > 0 && $data_length > 8) {
389 // Add the PHP function data to the function call list only if it is a previous level than the Drupal Function
390 // Also don't add the previous PHP function if the function is an error_handler
391 if ($data[8] < $data[0] && $data[2] != "error_handler") {
392 // First add in an additional row for the PHP function so that the levels will be plotted out correctly
393 $row++;
394 $function_call_data[$row]['PHP_function'] = TRUE;
395 $function_call_data[$row]['level'] = $data[8];
396 $function_call_data[$row]['function_number'] = $data[9];
397 // This is the PHP function name
398 $function_call_data[$row]['function_name'] = $data[7];
399 // Strip out the base path info from the via_filename in order to normalize the file path for the lookup arrays
400 $function_call_data[$row]['via_filename'] = substr($data[10], (strlen($data[10])-strlen($unix_base_path))*-1);
401 $function_call_data[$row]['line_number'] = $data[11];
402 $function_call_data[$row]['time_index'] = $data[12];
403 $function_call_data[$row]['memory'] = $data[13];
404 $function_call_data[$row]['via_PHP_function'] = "";
405 }
406
407 // Then add in the Drupal function
408 $row++;
409 $function_call_data[$row]['PHP_function'] = FALSE;
410 $function_call_data[$row]['level'] = $data[0];
411 $function_call_data[$row]['function_number'] = $data[1];
412 $function_call_data[$row]['function_name'] = $data[2];
413 $function_call_data[$row]['via_filename'] = substr($data[3], (strlen($data[3])-strlen($unix_base_path))*-1);
414 $function_call_data[$row]['line_number'] = $data[4];
415 $function_call_data[$row]['time_index'] = $data[5];
416 $function_call_data[$row]['memory'] = $data[6];
417 // Add data that will later be used as a label to indicate that this function was called from a PHP function
418 $function_call_data[$row]['via_PHP_function']='\n* via: ';
419
420 }
421 else {
422 // Add in the metadata for the Drupal functions called by other Drupal functions
423 // Filter out eval function names since the drupal_eval code yields irregular trace file data
424 // The final time and memory values only have two columns, and so make sure the fourth column has a number
425 // Also filter out the the erroneous "Version:" info on the first line of the parsed file
426 //if ($data[2] != "eval" && $data[0] != "Version:" && is_numeric($data[4])) {
427 if ($data_length > 2) {
428 if ($data[2] != "eval") {
429 $row++;
430 $function_call_data[$row]['PHP_function'] = FALSE;
431 $function_call_data[$row]['level'] = $data[0];
432 $function_call_data[$row]['function_number'] = $data[1];
433 $function_call_data[$row]['function_name'] = $data[2];
434 $function_call_data[$row]['via_filename'] = substr($data[3], (strlen($data[3])-strlen($unix_base_path))*-1);
435 $function_call_data[$row]['line_number'] = $data[4];
436 $function_call_data[$row]['time_index'] = $data[5];
437 $function_call_data[$row]['memory'] = $data[6];
438 $function_call_data[$row]['via_PHP_function']="";
439 }
440 }
441 elseif ($data[0] != "Version:") {
442 $final_time = $data[0];
443 $final_memory = $data[1];
444 }
445 }
446 }
447
448 // Save the total number of $rows to loop through after the duplicate functions have been removed.
449 $total_rows = $row;
450
451 // Close the parsed trace file.
452 fclose($handle);
453
454 // Detect whenever 1, 2 or 3 functions iteratively repeat
455 // The function all data is returned with a 'function_repeat' flag set to 1 if
456 // the function is part of a single, double or triple iterating loop
457 // Added an admin option to determine whether repeating functions should be filtered out
458 if (variable_get('visualize_backtrace_hide_repeating_functions', 1)) {
459 $function_call_data = visualize_backtrace_detect_repeating_functions($function_call_data);
460 }
461
462 // Initialize Variables
463 // Check arg(3) to see if the backtrace for a specific function should be graphed and output into a table
464 $subsection_argument = arg(3);
465 if ($subsection_argument == "show_function_backtrace" || $subsection_argument == "show_function_forwardtrace") {
466 $capture_function = arg(4);
467 $capture_function_backtrace_flag = TRUE;
468 $subgraph_cluster_count = 0;
469 $capture_function_count = 0;
470 if ($subsection_argument == "show_function_forwardtrace") {
471 $capture_function_forwardtrace_flag = TRUE;
472 }
473 $capture_contrib_functions = FALSE;
474 // Make sure that functions with null filenames don't get marked as a capture function
475 $capture_filename = "xxx";
476 }
477 // Use the same capture_function codebase to capture all instances of a specific module filename
478 elseif ($subsection_argument == "show_filename_backtrace" || $subsection_argument == "show_filename_forwardtrace") {
479 // Restore the '/' filepath argument delimiters by replacing any inserted '-' delimiters
480 $capture_filename = preg_replace('/-/', '/', arg(4));
481 // Check to see if a special flag has been set in arg(4) to capture all contributed functions
482 $capture_contrib_functions = FALSE;
483 if ($capture_filename == 'capture_all_contrib_functions') {
484 // This variable can be defined in the admin section
485 $contrib_module_filepath = variable_get('visualize_backtrace_contrib_module_filepath', 'sites/');
486 $capture_contrib_functions = TRUE;
487 }
488 $capture_function_backtrace_flag = TRUE;
489 $subgraph_cluster_count = 0;
490 $capture_function_count = 0;
491 if ($subsection_argument == "show_filename_forwardtrace") {
492 $capture_function_forwardtrace_flag = TRUE;
493 }
494 }
495 else {
496 $capture_function_backtrace_flag = FALSE;
497 $capture_contrib_functions = FALSE;
498 }
499
500 $function_call_stack = array();
501 // Create a 0-level index value for the function call stack to connect level=1 functions at the end
502 $function_call_stack[0] = "index.php";
503
504 // Keep track of whether or not the theme() function was executed from index.php
505 $index_theme_executed = FALSE;
506
507 // Intialize the *.dot output arrays
508 $output_to_dot_all = array();
509 $output_to_dot_function = array();
510
511 $row=1;
512 $function_count = 0;
513
514 // Loop through the results to store the time and memory values in an array with a $key of the $function_count
515 // This makes it possible to calculate the deltas with ($function_count + 1) without knowing
516 // which future $row number is the next non-repeating function
517 // (i.e. $time_delta = $time_index_array[($function_count+1)] - $time_index_array[$function_count] )
518 while ($row <= $total_rows) {
519 if (!$function_call_data[$row]['function_repeat']) {
520 $function_count++;
521 $time_index_array[$function_count] = $function_call_data[$row]['time_index'];
522 $memory_array[$function_count] = $function_call_data[$row]['memory'];
523 }
524 $row++;
525 }
526 // Add the final time and memory values at the end of the array in order to properly calculate the last function's values
527 $time_index_array[($function_count+1)] = $final_time;
528 $memory_array[($function_count+1)] = $final_memory;
529
530 // Re-Initialize the $row & $function_count
531 $row=1;
532 $function_count = 0;
533 $capture_function_depth = 1;
534 $drupal_version = floor(VERSION);
535 $time_threshold = variable_get('visualize_backtrace_execution_threshold', 5);
536
537 /**
538 * Generate the *.dot syntax for each function call by looping through all of
539 * the nodes & skipping over the repeating functions
540 * Optionally capture the backtrace or forwardtrace data for specific functions
541 */
542 while ($row <= $total_rows) {
543 // Verify that this row has not been flagged as a repeating function
544 if (!$function_call_data[$row]['function_repeat']) {
545 $level = $function_call_data[$row]['level'];
546 $function_number = $function_call_data[$row]['function_number'];
547 $function_name = $function_call_data[$row]['function_name'];
548 $via_filename = $function_call_data[$row]['via_filename'];
549 $line_number = $function_call_data[$row]['line_number'];
550 $via_PHP_function = $function_call_data[$row]['via_PHP_function'];
551 $repeating_function_label = $function_call_data[$row]['repeating_function_label'];
552
553 // This function count counts the number of non-repeating functions so that it can be shown on the flowchart as
554 // "f" plus the function count number.
555 // The function count also serves as the array index for the output data to be written to the *.dot file.
556 $function_count++;
557 $function_call_data[$row]['function_count'] = $function_count;
558
559 // If this was a procedural Drupal hook function, then use the previous level's function_id to determine
560 // the correct via_filename & line_number -- as well as correct referential function_name for the via_PHP_function label
561 if ($via_PHP_function) {
562 // The function id is delimited by a double underscore so explode it to determine the correct PHP reference info
563 $previous_level_function_id = $output = explode('__', $function_call_stack[$level-1]);
564 $via_filename = $previous_level_function_id[1];
565 $line_number = $previous_level_function_id[2];
566 // Tack on the correct previous function to the via_PHP_function label
567 $via_PHP_function .= $previous_level_function_id[0];
568 }
569
570 // The function_id uniquely identifies this function by combining the Function name + filename reference +
571 // line number + function call count. The function call count prevents commonly run functions from making
572 // the output both clustered and more complicated to follow
573 // Functions with a line number of 0 use the line number of the previous PHP function to make it easier
574 // to link to the source code location where procedural hooks are called
575 $function_id = $function_name ."__". $via_filename ."__". $line_number ."__". $function_count;
576
577 // This array keeps track of the call stack tree for any given function call.
578 // It is used for creating the nodal connections
579 $function_call_stack[$level] = $function_id;
580
581 // The node color & the source code Links Permalinks for each filename
582 // If the CVS permalink w/ version number isn't defined, then either manually add it to the
583 // $lookup_table array in visualize_backtrace_get_lookup_table_values() section of code.
584 // Otherwise, it will default to the local copy, but the lines won't be accessible for
585 // documentation purposes (i.e. in creating a hypertext linked SVG)
586 if ($source_code_link_location == "local") {
587 $lookup_table[$via_filename]["source_code_line_url"] = $base_url ."/view_source/". $via_filename ."#";
588 }
589 else {
590 if ($lookup_table[$via_filename]["source_code_line_url"] == "") {
591 $lookup_table[$via_filename]["source_code_line_url"] = $base_url ."/view_source/". $via_filename ."#";
592 }
593 }
594
595 // The node connection colors are sequentially alternated in order to help match the correct
596 // label to each line, which helps in cluttered graphs.
597 // Get the last digit of the function_count to determine the color for the node connection
598 $node_connection_color = $lookup_table["node_connection_color"][$function_count % 10];
599
600 // Create the node connection data for each row.
601 // The *.dot syntax will end up looking like the following:
602 // "{main}_1_1" -> "require_once__index.php_12_2" [label="f2" href="http://api.drupal.org/api/function/require_once/5" color="#FF0000" fontcolor="#FF0000"];
603 $node_connection_data = '"'. $function_call_stack[$level-1] .'" -> "'. $function_id .'" [label="f'. $function_count .'" href="http://api.drupal.org/api/function/'.
604 $function_name .'/'. $drupal_version .'" color="'. $node_connection_color .'" fontcolor="'. $node_connection_color .'"]; ';
605
606 $time_delta[$function_count] = round(($time_index_array[($function_count+1)] - $time_index_array[$function_count])*1000, 3);
607 $memory_delta[$function_count] = round(($memory_array[($function_count+1)] - $memory_array[$function_count])/1000, 3);
608
609 // Create the node label with the function name, filename, line number & function number,
610 // as well as the node color and a hypertext link to the specific line number code.
611 // Procedural Drupal Hooks are labeled with a $via_PHP_function label
612 // Looping sets of 1, 2 or 3 functions are also labeled with how many times they repeat.
613 // The label will end up looking something like:
614 // "require_once__index.php_12_2" [label="require_once\nindex.php\nline: 12\nf2" color="#C7E9B4" href="http://cvs.drupal.org/viewvc.py/drupal/drupal/index.php?annotate=1.91#l12"];
615 $node_label = '"'. $function_id .'" [label="'. $function_name .'\n'. $via_filename . $via_PHP_function . $repeating_function_label .'\nline: '. $line_number .'\nf'. $function_count .' -- '. $time_delta[$function_count] .'ms" "'. $level .' "color="'.
616 $lookup_table[$via_filename]["node_color_hex"] .'" href="'. $lookup_table[$via_filename]["source_code_line_url"] . $line_number .'"];';
617 $output_to_dot_all[$function_count] = $node_connection_data . $node_label;
618
619 /**
620 * If a capture function is passed in via arg(4), then capture the backtrace & forwardtrace data
621 * (i.e. backtrace = parent functions & forwardtrace = children functions)
622 */
623 if ($capture_function_backtrace_flag) {
624 if ($capture_contrib_functions) {
625 // Check to see if this function is called from a contributed module filename
626 // (i.e. if via_filename begins with 'sites/' -- or whatever the $contrib_module_filepath is)
627 if (substr($via_filename, 0, strlen($contrib_module_filepath)) == $contrib_module_filepath) {
628 $capture_contrib_functions_flag = TRUE;
629 }
630 }
631 // Initialize the variable that will flag the capture functions
632 $capture_function_flag[$function_count] = FALSE;
633
634 // Each time the $capture_function is called, then capture the parent backtrace functions
635 if ($function_name == $capture_function || $via_filename == $capture_filename || $capture_contrib_functions_flag) {
636 $function_level_loop = $level;
637
638 // Loop backwards down through the previous function call stack levels
639 // to extract the function count numbers of all of the functions in the backtrace
640 while ($function_level_loop > 0) {
641 // explode the function_id to get the function count
642 $previous_function_count = explode('__', $function_call_stack[$function_level_loop]);
643 // Save the *.dot syntax code for that particular function count
644 // If it is the captured function, then highlight it with a pink box (i.e. a GraphViz subgraph cluster)
645 // if $capture_function_flag is already TRUE, then the captured function is a child of itself &
646 // appears in its own backtrace so don't overwrite the label & inserted box
647 if (!$capture_function_flag[$previous_function_count[3]]) {
648 // Flag the captured function so that it can be marked with a box later on
649 // The captured function happens when the function_level_loop = the current $level
650 // Also keep track of the total number of times that the function occurs
651 if ($function_level_loop == $level) {
652
653 $subgraph_cluster_count++;
654 $output_to_dot_function[$previous_function_count[3]] = $output_to_dot_all[$previous_function_count[3]];
655
656 // Keep track of the captured function's function count to highlight in the callstack table
657 $capture_function_flag[$previous_function_count[3]] = TRUE;
658
659 // Keep track of the total number of times this function has been called
660 // including any number of repeats calculated in the visualize_backtrace_detect_repeating_functions() function
661 if ($function_call_data[$row]['repeating_function_count']) {
662 $capture_function_count = $capture_function_count + $function_call_data[$row]['repeating_function_count'];
663 }
664 // This instance of the capture function is not in a repeating loop
665 else {
666 $capture_function_count++;
667 }
668
669 // Check to see if this is a nested instance of the captured function by first
670 // checking to see if the $capture_function_children_flag is still set to TRUE
671 if ($capture_function_children_flag) {
672 // If the current level is greater than the level stored in the captured function level array,
673 // then add another level to the depth index.
674 if ($level > $capture_function_level_array[$capture_function_depth]) {
675 $capture_function_depth++;
676 // Add this nested child captured function to the parent capture function's count & time sum
677 // Start at one less the current depth and go down to the bottom level (i.e. top parent)
678 $capture_function_depth_key = $capture_function_depth - 1;
679 while ($capture_function_depth_key >= 1) {
680 $subgraph_cluster_count_index = $capture_function_cluster_number[$capture_function_depth_key];
681 $capture_function_children_time_sum[$subgraph_cluster_count_index] = $capture_function_children_time_sum[$subgraph_cluster_count_index] + $time_delta[$function_count];
682 $capture_function_children_count[$subgraph_cluster_count_index]++;
683 $capture_function_depth_key--;
684 }
685 }
686 // If the depth is greater than one, then first check to see if the depth needs to be reduced,
687 // and then determine whether or not to add in this function's time to the previous depths (i.e. parent functions)
688 elseif ($capture_function_depth > 1) {
689 // Check to see if the depth needs to be iteratively reduced
690 // (i.e. whenever a nested function's forwardtrace tree ends)
691 $reduce_depth_flag = TRUE;
692 while ($reduce_depth_flag) {
693 // Check to see if the current level is less than or equal to the stored level at the previous depth --
694 // while making sure not to decrement the depth down to 0
695 if ($level <= $capture_function_level_array[$capture_function_depth-1] && $capture_function_depth >= 1) {
696 // This marks the end of a previous nested capture function forward trace,
697 // and so reduce the capture function depth & zero out the stored level array at that depth
698 $capture_function_level_array[$capture_function_depth] = 0;
699 $capture_function_depth--;
700 }
701 else {
702 // Stop reducing the depth since this function's $level is now equal to or
703 // greater than $capture_function_level_array[$capture_function_depth]
704 $reduce_depth_flag = FALSE;
705 }
706 }
707 // Add this function's time data & count to its parent captured function(s) by looping through the previous depths
708 $capture_function_depth_key = $capture_function_depth - 1;
709 while ($capture_function_depth_key >= 1) {
710 $subgraph_cluster_count_index = $capture_function_cluster_number[$capture_function_depth_key];
711 $capture_function_children_time_sum[$subgraph_cluster_count_index] = $capture_function_children_time_sum[$subgraph_cluster_count_index] + $time_delta[$function_count];
712 $capture_function_children_count[$subgraph_cluster_count_index]++;
713 $capture_function_depth_key--;
714 }
715 }
716 }
717
718 // Set the flag to start capturing data on the children functions of this capture function
719 $capture_function_children_flag = TRUE;
720
721 // Save both the level & count of this captured function in case there are any captured function children
722 $capture_function_level_array[$capture_function_depth] = $level;
723 $capture_function_cluster_number[$capture_function_depth] = $subgraph_cluster_count;
724
725 // Determine the number of and elapsed time of all of the functions that come after the captured function -- if any
726 $capture_function_children_time_sum[$subgraph_cluster_count] = 0;
727 $capture_function_children_count[$subgraph_cluster_count] = 0;
728
729 // Keep track of where this captured function's function count for inserting the children time sum and count later on
730 $capture_function_index[$subgraph_cluster_count] = $function_count;
731
732 }
733 // Save the backtrace nodal connections for this instance of the captured function as it loops backwards through the backtrace levels
734 else {
735 $output_to_dot_function[$previous_function_count[3]] = $output_to_dot_all[$previous_function_count[3]];
736 }
737 }
738 $function_level_loop--;
739 }
740 }
741
742 /**
743 * Now gather the forwardtrace data for the captured function
744 * Capture the total count and length of time it takes of the children functions of the captured function
745 * Stop capturing children function data when the current row's $level is equal to or less than the $capture_function_level
746 * Graph the children functions to the $output_to_dot_function array if arg(3) = 'show_function_forwardtrace' --
747 * otherwise the children function count and length of run time is plotted on the backtrace graph
748 */
749 else {
750 if ($capture_function_children_flag) {
751 // Check to see if the current level is lower than the last captured function depth
752 // If so, then either reduce the capture function level depth if it is a nested function
753 // or stop capturing the child functions of the forwardtraced capture function
754 if ($level <= $capture_function_level_array[$capture_function_depth]) {
755 // Check to see if it is at the initial depth, or if the depth needs to be reduced.
756 if ($capture_function_depth == 1) {
757 // The current $level is equal to or less than the capture function & is not a nested capture function
758 // and so stop marking functions as children of the capture function
759 $capture_function_children_flag = FALSE;
760 }
761 else {
762 // The previous nested capture function tree has ended, and so reduce the depth to the previous level
763 $reduce_depth_flag = TRUE;
764 while ($reduce_depth_flag) {
765 // Check to see if the current level is less than or equal to the stored level at the previous depth --
766 // while making sure not to decrement the depth down to 0
767 if ($level <= $capture_function_level_array[$capture_function_depth-1] && $capture_function_depth >= 1) {
768 // This marks the end of a previous nested capture function forward trace,
769 // and so reduce the capture function depth & zero out the stored level array at that depth
770 $capture_function_level_array[$capture_function_depth] = 0;
771 $capture_function_depth--;
772 }
773 else {
774 // Stop reducing the depth because this function's $level is now equal to or
775 // greater than $capture_function_level_array[$capture_function_depth]
776 $reduce_depth_flag = FALSE;
777 }
778 }
779 // Do a final check after reducing the depth to see if the $level is equal to or less than
780 // the stored level @ capture_function_depth=1
781 if ($level <= $capture_function_level_array[$capture_function_depth]) {
782 // Stop marking functions as children of the capture function
783 $capture_function_children_flag = FALSE;
784 }
785 }
786 }
787
788 // Store the child data for this $capture_function
789 if ($capture_function_children_flag) {
790 // Save the children functions to the *.dot file if the Forwardtrace flag is set
791 if ($capture_function_forwardtrace_flag) {
792 $output_to_dot_function[$function_count] = $output_to_dot_all[$function_count];
793 }
794 // Sum the total length of all of the children functions of the capture function
795 // Start at the lowest depth and loop through up to the current depth
796 $capture_function_depth_key = 1;
797 while ($capture_function_depth_key <= $capture_function_depth) {
798 $subgraph_cluster_count_index = $capture_function_cluster_number[$capture_function_depth_key];
799 $capture_function_children_time_sum[$subgraph_cluster_count_index] = $capture_function_children_time_sum[$subgraph_cluster_count_index] + $time_delta[$function_count];
800 $capture_function_children_count[$subgraph_cluster_count_index]++;
801 $capture_function_depth_key++;
802 }
803 }
804 }
805 }
806 $capture_contrib_functions_flag = FALSE;
807 } // END of capturing the backtrace parent functions & forwardtrace children functions
808
809 // Capture all of the functions in between {main} and menu_execute_active_handler @ level=2
810 // This contains the Drupal bootstrap section which doesn't have a lot of variations
811 if ($function_name == "{main}") {
812 $section_endpoint[1]['begin'] = $function_count;
813 }
814 // Capture all of the functions in between menu_execute_active_handler @ level=3 and theme @ level=2
815 // This section is usually the bulk of the relevent functions and may need to be broken up in order to be plotted all at once
816 // Section two is broken up into three equal sections in section numbers 4, 5 & 6
817 if ($function_name == "menu_execute_active_handler" && $level == 2) {
818 $section_endpoint[1]['end'] = $function_count-1;
819 $section_endpoint[2]['begin'] = $function_count;
820 }
821 // Capture all of the functions in between theme @ level=2 and the end
822 // This contains a lot of the theming-specific code.
823 if ($function_name == "theme" && $level == 2) {
824 $index_theme_executed = TRUE;
825 $section_endpoint[2]['end'] = $function_count-1;
826 $section_endpoint[3]['begin'] = $function_count;
827 }
828 }
829 $row++;
830 }
831 // Determine the final $row number for the last subsection
832 if ($index_theme_executed) {
833 $section_endpoint[3]['end'] = $function_count;
834 }
835 else {
836 $section_endpoint[2]['end'] = $function_count;
837 }
838
839 // Sometimes the Main Section is too big to be plotted all at once, and so split it up into three equal sections
840 // Creating three main subsecions is an option that can be turned off in the admin section
841 $create_main_subsections = variable_get('visualize_backtrace_create_main_subsections', 1);
842 if ($create_main_subsections) {
843 $section_endpoint[4]['begin'] = $section_endpoint[2]['begin'];
844 $section_endpoint[4]['end'] = round(($section_endpoint[2]['end']-$section_endpoint[2]['begin'])/3) + $section_endpoint[4]['begin'];
845 $section_endpoint[5]['begin'] = $section_endpoint[4]['end'];
846 $section_endpoint[5]['end'] = round(($section_endpoint[2]['end']-$section_endpoint[2]['begin'])/3) + $section_endpoint[5]['begin'];
847 $section_endpoint[6]['begin'] = $section_endpoint[5]['end'];
848 $section_endpoint[6]['end'] = $section_endpoint[2]['end'];
849 $section = 6;
850 $main_sections = 6;
851 }
852 else {
853 $section = 3;
854 $main_sections = 3;
855 }
856
857 //// DEBUGGING: Print the start and stop row numbers for each of the 6 sections
858 //$i=1;
859 //while ($i <= $section) {
860 // print $i ." begin = ". $section_endpoint[$i]['begin']. " end = ". $section_endpoint[$i]['end'] ."<br>";
861 // $i++;
862 //}
863
864 // Loop through each of the section numbers
865 $section_number = 0;
866 $output_to_dot = array();
867
868 // Section 0 contains all of the all three subsections
869 $output_to_dot[0] = 'digraph G {graph [ordering="out"]; node [style=filled];'. implode("", $output_to_dot_all) .'}';
870 $section_length[0] = count($output_to_dot_all);
871
872
873 // Add another section for the captured backtrace for a specific function if the
874 // arg(3) is either 'show_function_backtrace' or 'show_function_forwardtrace'
875 if ($capture_function_backtrace_flag) {
876 // Insert a box around the captured functions that includes the total number and length of child functions of each captured function
877 // The box is a "subgraph cluster" that is inserted after the nodal connection & before the node label
878 // This is done by replacing '"]; "' with the subgraph code of '"]; subgraph cluster1{color="#000000"; label="Function #01 w/ 8 children = 1.068ms" "'
879 foreach ($capture_function_index as $subcluster_count_key => $function_count_value) {
880 if ($capture_function_children_count[$subcluster_count_key] > 0) {
881 if ($capture_function_children_time_sum[$subcluster_count_key] > $time_threshold) {
882 // Place a red box around the functions that exceed the time threshold (default threshold is 5 ms)
883 $box_color = "#FF9BD1";
884 $add_mark = TRUE;
885 }
886 else {
887 // Place a green box around all of the other functions that are below the time threshold
888 $box_color = "#7FCDBB";
889 $add_mark = FALSE;
890 }
891 // Place a leading 0 before the fuction numbers less than 10
892 if ($subcluster_count_key < 10) {
893 // Re-save this capture function child data w/ the function count as the array index to display in the callstack table
894 $capture_function_child_data[$function_count_value] = 'Function #0'. $subcluster_count_key .' w/ '. $capture_function_children_count[$subcluster_count_key] .' children = '. $capture_function_children_time_sum[$subcluster_count_key] .'ms';
895 }
896 else {
897 // Re-save this capture function child data w/ the function count as the array index to display in the callstack table
898 $capture_function_child_data[$function_count_value] = 'Function #'. $subcluster_count_key .' w/ '. $capture_function_children_count[$subcluster_count_key] .' children = '. $capture_function_children_time_sum[$subcluster_count_key] .'ms';
899 }
900 }
901 // Add a green box around the functions that have no child functions
902 else {
903 $box_color = "#7FCDBB";
904 $add_mark = FALSE;
905 if ($subcluster_count_key < 10) {
906 $capture_function_child_data[$function_count_value] = 'Function #0'. $subcluster_count_key .' w/ 0 children';
907 }
908 else {
909 $capture_function_child_data[$function_count_value] = 'Function #'. $subcluster_count_key .' w/ 0 children';
910 }
911 }
912 $insert_string = '"]; subgraph cluster'. $subcluster_count_key .'{color="'. $box_color .'"; label="'. $capture_function_child_data[$function_count_value] .'" "';
913 $output_to_dot_function[$function_count_value] = preg_replace('/"]; "/', $insert_string, $output_to_dot_function[$function_count_value]) .'}';
914 // Highlight this data red when displayed in the table
915 if ($add_mark) {
916 $capture_function_child_data[$function_count_value] = '<span class="marker">'. $capture_function_child_data[$function_count_value] .'</span>';
917 }
918 // Calculate the average time of each captured function + it's children and divide by the total number of captured function clusters at the end
919 $capture_function_children_time_average = $capture_function_children_time_sum[$subcluster_count_key] + $time_delta[$function_count_value] + $capture_function_children_time_average;
920 }
921 $capture_function_children_time_total = $capture_function_children_time_average;
922 $capture_function_children_time_average = round($capture_function_children_time_average / $subgraph_cluster_count, 3);
923
924 // If there are three main subsections, then create a total of 7 *.dot sections.
925 if ($create_main_subsections) {
926 $section = 7;
927 }
928 else {
929 $section = 4;
930 }
931 $output_to_dot[$section] = 'digraph G {graph [ordering="out"]; node [style=filled];'. implode("", $output_to_dot_function) .'}';
932 }
933
934 $zgrviewer_location = variable_get('visualize_backtrace_zgrviewer_location', '/usr/local/bin/zgrviewer');
935
936 // Create the *.dot file for each subsection
937 while ($section_number <= $section) {
938 // The *.dot files for the six main sections are created by taking an array_slice of output_to_dot_all
939 // And the *.dot file for the captured function is created from the output_to_dot_function
940 if ($section_number > 0 && $section_number <= $main_sections) {
941 // Calculate how many function calls were made in each section
942 $section_length[$section_number] = $section_endpoint[$section_number]['end']-$section_endpoint[$section_number]['begin'];
943
944 // Generate the *.dot file for each of three sub-sections
945 $output_to_dot[$section_number] = 'digraph G {graph [ordering="out"]; node [style=filled];'.
946 implode("", array_slice($output_to_dot_all, $section_endpoint[$section_number]['begin']-1, $section_length[$section_number]+1))
947 .'}';
948 }
949
950 // There is an extra space at the end of $unix_base_path that needs to be deleted.
951 $default_file_path = file_directory_path();
952 $dot_file[$section_number] = trim($unix_base_path) ."/". $default_file_path ."/". $trace_number ."_section". $section_number .".dot";
953 $output_file = fopen($dot_file[$section_number], 'w');
954 fwrite($output_file, $output_to_dot[$section_number]);
955 fclose($output_file);
956 $command[$section_number] = $zgrviewer_location ."/run.sh --Pdot ". $dot_file[$section_number] ."; ";
957 $section_number++;
958 }
959
960 // ** OUTPUT THE LINKS TO THE GRAPHS & OUTPUT THE DATA **
961 // Clear out some of the large arrays
962 $output_to_dot = array();
963 $output_to_dot_all = array();
964
965 $add_footnote = FALSE;
966
967 // Get the file sizes by piping in "ls -l" into awk and extracting the fifth column
968 // ls -l /Library/WebServer/Documents/d52/files/1189710511* | awk '{print $5}'
969 $get_file_sizes_command = "ls -l ". trim($unix_base_path) ."/files/". $trace_number ."* | awk '{print $5}'";
970 exec($get_file_sizes_command, $files_output);
971
972 // Add a note to any file sizes that are greater than 1000000 bytes
973 foreach ($files_output as $section_key => $filesize_value) {
974 if ($filesize_value > 1000000) {
975 $files_output[$section_key] = $files_output[$section_key] .' bytes [*]';
976 $add_footnote = TRUE;
977 }
978 else {
979 $files_output[$section_key] = $files_output[$section_key] .' bytes';
980 }
981 }
982
983 // Indicate where the trace file originated if it was called directly from the page load
984 // Don't display the HTTP reference if the previous a page had visualize_backtrace as arg(0)
985 // Determine which array position arg(0) is located in the exploded HTTP_REFERER array
986 // by counting the length of the exploded $base_url
987 $http_reference = explode('/', $_SERVER['HTTP_REFERER']);
988 if ($http_reference[count(explode('/', $base_url))] != "visualize_backtrace") {
989 $output = '<br />Graph of '. l($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_REFERER']) .'<br />';
990 }
991 else {
992 $output = '';
993 }
994
995 $output .= t('The %trace_file page load executed in %final_time milleseconds<br />', array('%trace_file' => $trace_file, '%final_time' => round($final_time * 1000, 3))); // DELETED FOR NOW: with a memory footprint of '. round($final_memory / 1000, 3) .' kB.
996 // $output .= 'The page load for this trace executed in <em>'. round($final_time * 1000, 3) .'</em> milleseconds<br />'; // DELETED FOR NOW: with a memory footprint of '. round($final_memory / 1000, 3) .' kB.
997 $output .= t('%total_rows Total Drupal Functions appearing in %section_length clusters after consolidating the repeating function loops.', array('%total_rows' => $total_rows, '%section_length' => $section_length[0])) .'<br />';
998
999 $output .= '<br /><h3>Backtrace Graphs</h3>';
1000 // TODO: Possibly replace this hardcoded table with the Drupal table theming as in devel_query_table($queries, $counts);
1001 $output .= '<table>';
1002 $output .= '<tr><td>0.) Full Backtrace</td><td>'. $section_length[0] .' functions</td><td>'. $files_output[0] .' </td><td>'. l('Graph Full Backtrace', 'visualize_backtrace/'. $trace_number .'/view_callstack/section_0') .'</td></tr>';
1003 // Indent each table to make the parent/child relationships clear
1004 // The section lengths calculated above were for the correct partitioning of the $output_to_dot_all data
1005 // through the array_slice & implosion when creating the *.dot files, and so add 1 to get the correct section lengths
1006 $output .= '<tr><td> &nbsp;&nbsp;&nbsp;&nbsp; 1.) Drupal Bootstrap</td><td> &nbsp;&nbsp;&nbsp;&nbsp; '. ($section_length[1] + 1) .' functions</td><td> &nbsp;&nbsp;&nbsp;&nbsp; '. $files_output[1] .' </td><td> &nbsp;&nbsp;&nbsp;&nbsp; '. l('Graph Bootstrap Section Backtrace', 'visualize_backtrace/'. $trace_number .'/view_callstack/section_1') .'</td></tr>';
1007 $output .= '<tr><td> &nbsp;&nbsp;&nbsp;&nbsp; 2.) Main Section</td><td> &nbsp;&nbsp;&nbsp;&nbsp; '. ($section_length[2] + 1) .' functions</td><td> &nbsp;&nbsp;&nbsp;&nbsp;'. $files_output[2] .' </td><td> &nbsp;&nbsp;&nbsp;&nbsp; '. l('Graph Main Section Backtrace', 'visualize_backtrace/'. $trace_number .'/view_callstack/section_2') .'</td></tr>';
1008 if ($create_main_subsections) {
1009 $output .= '<tr><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4.) Main (Part 1/3)</td><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '. $section_length[4] .' functions</td><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '. $files_output[4] .' </td><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '. l('Graph Main Section 1', 'visualize_backtrace/'. $trace_number .'/view_callstack/section_4') .'</td></tr>';
1010 $output .= '<tr><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5.) Main (Part 2/3)</td><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '. $section_length[5] .' functions</td><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '. $files_output[5] .' </td><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '. l('Graph Main Section 2', 'visualize_backtrace/'. $trace_number .'/view_callstack/section_5') .'</td></tr>';
1011 $output .= '<tr><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6.) Main (Part 3/3)</td><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '. ($section_length[6] + 1) .' functions</td><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '. $files_output[6] .' </td><td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '. l('Graph Main Section 3', 'visualize_backtrace/'. $trace_number .'/view_callstack/section_6') .'</td></tr>';
1012 }
1013 // Check to see if the theme() function was even called from index.php. If the section length is 0, then don't output a link to it.
1014 if ($section_length[3] > 0) {
1015 $output .= '<tr><td> &nbsp;&nbsp;&nbsp;&nbsp; 3.) Theming</td><td> &nbsp;&nbsp;&nbsp;&nbsp;'. ($section_length[3] + 1) .' functions</td><td> &nbsp;&nbsp;&nbsp;&nbsp; '. $files_output[3] .' </td><td> &nbsp;&nbsp;&nbsp;&nbsp; '. l('Graph Theme Section Backtrace', 'visualize_backtrace/'. $trace_number .'/view_callstack/section_3') .'</td></tr>';
1016 }
1017 $output .= '</table>';
1018
1019 // Add a footnote if if there are any files larger than 1000000 bytes
1020 if ($add_footnote) $output .= '<em>[*] - Indicates a large file size, which either takes longer to plot or doesn\'t plot at all.<br /><br /></em>';
1021
1022 // If any function or filename backtraces were captured, then publish another header with more details and links
1023 if ($capture_function_backtrace_flag) {
1024 if ($capture_function_forwardtrace_flag) {
1025 $add_text = ' and Forwardtraces';
1026 }
1027 // Create the header information for when a specific function is passed in via arg(4)
1028 if ($subsection_argument == "show_function_backtrace" || $subsection_argument == "show_function_forwardtrace") {
1029 $output .= '<h3>Backtraces'. $add_text .' for the Function "'. $capture_function .'"</h3>';
1030 // TODO: Possibly replace this hardcoded table with the Drupal table theming as in devel_query_table($queries, $counts);
1031 $output .= '<table><tr><td>'. $section .'.) "'. $capture_function .'"</td><td>'. count($output_to_dot_function) .' total functions</td><td>'. $files_output[7] .' </td><td>'.
1032 l('Graph Backtrace', 'visualize_backtrace/'. $trace_number .'/view_callstack/show_function_backtrace/'. $capture_function) .'<br />'.
1033 l('Graph Forwardtrace', 'visualize_backtrace/'. $trace_number .'/view_callstack/show_function_forwardtrace/'. $capture_function) .'</td></tr></table>';
1034 }
1035 else {
1036 // Create the header information for when all of the contributed module functions are being captured
1037 // (i.e. arg(4) = "capture_all_contrib_functions")
1038 if ($capture_filename == 'capture_all_contrib_functions') {
1039 if ($contrib_module_filepath == 'sites/') {
1040 $argument_label = "All Contributed Module Functions";
1041 }
1042 else {
1043 $argument_label = 'Functions from '. $contrib_module_filepath;
1044 }
1045
1046 $output .= '<h3>Backtraces'. $add_text .' of '. $argument_label .'</h3>';
1047 // TODO: Possibly replace this hardcoded table with the Drupal table theming as in devel_query_table($queries, $counts);
1048 $output .= '<table><tr><td>'. $section .'.) '. $argument_label .'</td><td>'. count($output_to_dot_function) .' total functions</td><td>'. $files_output[7] .' </td><td>'.
1049 l('Graph Backtrace', 'visualize_backtrace/'. $trace_number .'/view_callstack/show_filename_backtrace/capture_all_contrib_functions') .'<br />'.
1050 l('Graph Forwardtrace', 'visualize_backtrace/'. $trace_number .'/view_callstack/show_filename_forwardtrace/capture_all_contrib_functions') .'</td></tr></table>';
1051 }
1052 // Otherwise print out the header information for when a specific filename is being captured
1053 else {
1054 // Replace the filepath '/' with '-' to create one URL argument
1055 $graph_filename = preg_replace('/\//', '-', $capture_filename);
1056 $output .= '<h3>Backtraces'. $add_text .' of All Functions from the Filename "'. $capture_filename .'"</h3>';
1057 // TODO: Possibly replace this hardcoded table with the Drupal table theming as in devel_query_table($queries, $counts);
1058 $output .= '<table><tr><td>'. $section .'.) "'. $capture_filename .'"</td><td>'. count($output_to_dot_function) .' total functions</td><td>'. $files_output[7] .' </td><td>'.
1059 l('Graph Backtrace', 'visualize_backtrace/'. $trace_number .'/view_callstack/show_filename_backtrace/'. $graph_filename) .'<br />'.
1060 l('Graph Forwardtrace', 'visualize_backtrace/'. $trace_number .'/view_callstack/show_filename_forwardtrace/'. $graph_filename) .'</td></tr></table>';
1061 }
1062 }
1063 }
1064
1065 // Print out the all of the function call stack data
1066 $output .= '<h3>Function Call Stack</h3>';
1067
1068 // Determine if arg(3) passes in a specific function number (e.g. 's