Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
//  Copyright (c) 2009 Facebook
3
//
4
//  Licensed under the Apache License, Version 2.0 (the "License");
5
//  you may not use this file except in compliance with the License.
6
//  You may obtain a copy of the License at
7
//
8
//      http://www.apache.org/licenses/LICENSE-2.0
9
//
10
//  Unless required by applicable law or agreed to in writing, software
11
//  distributed under the License is distributed on an "AS IS" BASIS,
12
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
//  See the License for the specific language governing permissions and
14
//  limitations under the License.
15
//
16
 
17
/*
18
 * This file contains callgraph image generation related XHProf utility
19
 * functions
20
 *
21
 */
22
 
23
// Supported ouput format
24
$xhprof_legal_image_types = array(
25
    "jpg" => 1,
26
    "gif" => 1,
27
    "png" => 1,
28
    "svg" => 1, // support scalable vector graphic
29
    "ps"  => 1,
30
    );
31
 
32
/**
33
 * Send an HTTP header with the response. You MUST use this function instead
34
 * of header() so that we can debug header issues because they're virtually
35
 * impossible to debug otherwise. If you try to commit header(), SVN will
36
 * reject your commit.
37
 *
38
 * @param string  HTTP header name, like 'Location'
39
 * @param string  HTTP header value, like 'http://www.example.com/'
40
 *
41
 */
42
function xhprof_http_header($name, $value) {
43
 
44
  if (!$name) {
45
    xhprof_error('http_header usage');
46
    return null;
47
  }
48
 
49
  if (!is_string($value)) {
50
    xhprof_error('http_header value not a string');
51
  }
52
 
53
  header($name.': '.$value, true);
54
}
55
 
56
/**
57
 * Genearte and send MIME header for the output image to client browser.
58
 *
59
 * @author cjiang
60
 */
61
function xhprof_generate_mime_header($type, $length) {
62
  switch ($type) {
63
    case 'jpg':
64
      $mime = 'image/jpeg';
65
      break;
66
    case 'gif':
67
      $mime = 'image/gif';
68
      break;
69
    case 'png':
70
      $mime = 'image/png';
71
      break;
72
    case 'svg':
73
      $mime = 'image/svg+xml'; // content type for scalable vector graphic
74
      break;
75
    case 'ps':
76
      $mime = 'application/postscript';
77
    default:
78
      $mime = false;
79
  }
80
 
81
  if ($mime) {
82
    xhprof_http_header('Content-type', $mime);
83
    xhprof_http_header('Content-length', (string)$length);
84
  }
85
}
86
 
87
/**
88
 * Generate image according to DOT script. This function will spawn a process
89
 * with "dot" command and pipe the "dot_script" to it and pipe out the
90
 * generated image content.
91
 *
92
 * @param dot_script, string, the script for DOT to generate the image.
93
 * @param type, one of the supported image types, see
94
 * $xhprof_legal_image_types.
95
 * @returns, binary content of the generated image on success. empty string on
96
 *           failure.
97
 *
98
 * @author cjiang
99
 */
100
function xhprof_generate_image_by_dot($dot_script, $type) {
101
  $descriptorspec = array(
102
       // stdin is a pipe that the child will read from
103
 
104
       // stdout is a pipe that the child will write to
105
       1 => array("pipe", "w"),
106
       // stderr is a pipe that the child will write to
107
       2 => array("pipe", "w")
108
       );
109
 
110
  // Start moodle modification: use $CFG->pathtodot for executing this.
111
  // $cmd = " dot -T".$type;
112
  global $CFG;
113
  $cmd = (!empty($CFG->pathtodot) ? $CFG->pathtodot : 'dot') . ' -T' . $type;
114
  // End moodle modification.
115
 
116
  $process = proc_open( $cmd, $descriptorspec, $pipes, sys_get_temp_dir(), array( 'PATH' => getenv( 'PATH' ) ) );
117
  if (is_resource($process)) {
118
    fwrite($pipes[0], $dot_script);
119
    fclose($pipes[0]);
120
 
121
    $output = stream_get_contents($pipes[1]);
122
 
123
    $err = stream_get_contents($pipes[2]);
124
    if (!empty($err)) {
125
      print "failed to execute cmd: \"$cmd\". stderr: `$err'\n";
126
      exit;
127
    }
128
 
129
    fclose($pipes[2]);
130
    fclose($pipes[1]);
131
    proc_close($process);
132
    return $output;
133
  }
134
  print "failed to execute cmd \"$cmd\"";
135
  exit();
136
}
137
 
138
/*
139
 * Get the children list of all nodes.
140
 */
141
function xhprof_get_children_table($raw_data) {
142
  $children_table = array();
143
  foreach ($raw_data as $parent_child => $info) {
144
    list($parent, $child) = xhprof_parse_parent_child($parent_child);
145
    if (!isset($children_table[$parent])) {
146
      $children_table[$parent] = array($child);
147
    } else {
148
      $children_table[$parent][] = $child;
149
    }
150
  }
151
  return $children_table;
152
}
153
 
154
/**
155
 * Generate DOT script from the given raw phprof data.
156
 *
157
 * @param raw_data, phprof profile data.
158
 * @param threshold, float, the threshold value [0,1). The functions in the
159
 *                   raw_data whose exclusive wall times ratio are below the
160
 *                   threshold will be filtered out and won't apprear in the
161
 *                   generated image.
162
 * @param page, string(optional), the root node name. This can be used to
163
 *              replace the 'main()' as the root node.
164
 * @param func, string, the focus function.
165
 * @param critical_path, bool, whether or not to display critical path with
166
 *                             bold lines.
167
 * @returns, string, the DOT script to generate image.
168
 *
169
 * @author cjiang
170
 */
171
function xhprof_generate_dot_script($raw_data, $threshold, $source, $page,
172
                                    $func, $critical_path, $right=null,
173
                                    $left=null) {
174
 
175
  $max_width = 5;
176
  $max_height = 3.5;
177
  $max_fontsize = 35;
178
  $max_sizing_ratio = 20;
179
 
180
  $totals;
181
 
182
  if ($left === null) {
183
    // init_metrics($raw_data, null, null);
184
  }
185
  $sym_table = xhprof_compute_flat_info($raw_data, $totals);
186
 
187
  if ($critical_path) {
188
    $children_table = xhprof_get_children_table($raw_data);
189
    $node = "main()";
190
    $path = array();
191
    $path_edges = array();
192
    $visited = array();
193
    while ($node) {
194
      $visited[$node] = true;
195
      if (isset($children_table[$node])) {
196
        $max_child = null;
197
        foreach ($children_table[$node] as $child) {
198
 
199
          if (isset($visited[$child])) {
200
            continue;
201
          }
202
          if ($max_child === null ||
203
            abs($raw_data[xhprof_build_parent_child_key($node,
204
                                                        $child)]["wt"]) >
205
            abs($raw_data[xhprof_build_parent_child_key($node,
206
                                                        $max_child)]["wt"])) {
207
            $max_child = $child;
208
          }
209
        }
210
        if ($max_child !== null) {
211
          $path[$max_child] = true;
212
          $path_edges[xhprof_build_parent_child_key($node, $max_child)] = true;
213
        }
214
        $node = $max_child;
215
      } else {
216
        $node = null;
217
      }
218
    }
219
  }
220
 
221
  // if it is a benchmark callgraph, we make the benchmarked function the root.
222
 if ($source == "bm" && array_key_exists("main()", $sym_table)) {
223
    $total_times = $sym_table["main()"]["ct"];
224
    $remove_funcs = array("main()",
225
                          "hotprofiler_disable",
226
                          "call_user_func_array",
227
                          "xhprof_disable");
228
 
229
    foreach ($remove_funcs as $cur_del_func) {
230
      if (array_key_exists($cur_del_func, $sym_table) &&
231
          $sym_table[$cur_del_func]["ct"] == $total_times) {
232
        unset($sym_table[$cur_del_func]);
233
      }
234
    }
235
  }
236
 
237
  // use the function to filter out irrelevant functions.
238
  if (!empty($func)) {
239
    $interested_funcs = array();
240
    foreach ($raw_data as $parent_child => $info) {
241
      list($parent, $child) = xhprof_parse_parent_child($parent_child);
242
      if ($parent == $func || $child == $func) {
243
        $interested_funcs[$parent] = 1;
244
        $interested_funcs[$child] = 1;
245
      }
246
    }
247
    foreach ($sym_table as $symbol => $info) {
248
      if (!array_key_exists($symbol, $interested_funcs)) {
249
        unset($sym_table[$symbol]);
250
      }
251
    }
252
  }
253
 
254
  $result = "digraph call_graph {\n";
255
 
256
  // Filter out functions whose exclusive time ratio is below threshold, and
257
  // also assign a unique integer id for each function to be generated. In the
258
  // meantime, find the function with the most exclusive time (potentially the
259
  // performance bottleneck).
260
  $cur_id = 0; $max_wt = 0;
261
  foreach ($sym_table as $symbol => $info) {
262
    if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) {
263
      unset($sym_table[$symbol]);
264
      continue;
265
    }
266
    if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) {
267
      $max_wt = abs($info["excl_wt"]);
268
    }
269
    $sym_table[$symbol]["id"] = $cur_id;
270
    $cur_id ++;
271
  }
272
 
273
  // Generate all nodes' information.
274
  foreach ($sym_table as $symbol => $info) {
275
    if ($info["excl_wt"] == 0) {
276
      $sizing_factor = $max_sizing_ratio;
277
    } else {
278
      $sizing_factor = $max_wt / abs($info["excl_wt"]) ;
279
      if ($sizing_factor > $max_sizing_ratio) {
280
        $sizing_factor = $max_sizing_ratio;
281
      }
282
    }
283
    $fillcolor = (($sizing_factor < 1.5) ?
284
                  ", style=filled, fillcolor=red" : "");
285
 
286
    if ($critical_path) {
287
      // highlight nodes along critical path.
288
      if (!$fillcolor && array_key_exists($symbol, $path)) {
289
        $fillcolor = ", style=filled, fillcolor=yellow";
290
      }
291
    }
292
 
293
    $fontsize = ", fontsize="
294
               .(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1));
295
 
296
    $width = ", width=".sprintf("%.1f", $max_width / $sizing_factor);
297
    $height = ", height=".sprintf("%.1f", $max_height / $sizing_factor);
298
 
299
    if ($symbol == "main()") {
300
      $shape = "octagon";
301
      $name = "Total: ".($totals["wt"] / 1000.0)." ms\\n";
302
      $name .= addslashes(isset($page) ? $page : $symbol);
303
    } else {
304
      $shape = "box";
305
      $name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"] / 1000) .
306
              " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]).")";
307
    }
308
    if ($left === null) {
309
      $label = ", label=\"".$name."\\nExcl: "
310
               .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms ("
311
               .sprintf("%.1f%%", 100 * $info["excl_wt"] / $totals["wt"])
312
               . ")\\n".$info["ct"]." total calls\"";
313
    } else {
314
      if (isset($left[$symbol]) && isset($right[$symbol])) {
315
         $label = ", label=\"".addslashes($symbol).
316
                  "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0))
317
                  ." ms - "
318
                  .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))." ms = "
319
                  .(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
320
                  "\\nExcl: "
321
                  .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0))
322
                  ." ms - ".(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0))
323
                   ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
324
                  "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - "
325
                   .(sprintf("%.3f",$right[$symbol]["ct"]))." = "
326
                   .(sprintf("%.3f",$info["ct"]))."\"";
327
      } else if (isset($left[$symbol])) {
328
        $label = ", label=\"".addslashes($symbol).
329
                  "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0))
330
                   ." ms - 0 ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))
331
                   ." ms"."\\nExcl: "
332
                   .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0))
333
                   ." ms - 0 ms = "
334
                   .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
335
                  "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = "
336
                  .(sprintf("%.3f",$info["ct"]))."\"";
337
      } else {
338
        $label = ", label=\"".addslashes($symbol).
339
                  "\\nInc: 0 ms - "
340
                  .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))
341
                  ." ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
342
                  "\\nExcl: 0 ms - "
343
                  .(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0))
344
                  ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
345
                  "\\nCalls: 0 - ".(sprintf("%.3f",$right[$symbol]["ct"]))
346
                  ." = ".(sprintf("%.3f",$info["ct"]))."\"";
347
      }
348
    }
349
    $result .= "N" . $sym_table[$symbol]["id"];
350
    $result .= "[shape=$shape ".$label.$width
351
               .$height.$fontsize.$fillcolor."];\n";
352
  }
353
 
354
  // Generate all the edges' information.
355
  foreach ($raw_data as $parent_child => $info) {
356
    list($parent, $child) = xhprof_parse_parent_child($parent_child);
357
 
358
    if (isset($sym_table[$parent]) && isset($sym_table[$child]) &&
359
        (empty($func) ||
360
         (!empty($func) && ($parent == $func || $child == $func)))) {
361
 
362
      $label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls";
363
 
364
      $headlabel = $sym_table[$child]["wt"] > 0 ?
365
                  sprintf("%.1f%%", 100 * $info["wt"]
366
                                    / $sym_table[$child]["wt"])
367
                  : "0.0%";
368
 
369
      $taillabel = ($sym_table[$parent]["wt"] > 0) ?
370
        sprintf("%.1f%%",
371
                100 * $info["wt"] /
372
                ($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"]))
373
        : "0.0%";
374
 
375
      $linewidth = 1;
376
      $arrow_size = 1;
377
 
378
      if ($critical_path &&
379
          isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) {
380
        $linewidth = 10; $arrow_size = 2;
381
      }
382
 
383
      $result .= "N" . $sym_table[$parent]["id"] . " -> N"
384
                 . $sym_table[$child]["id"];
385
      $result .= "[arrowsize=$arrow_size, color=grey, style=\"setlinewidth($linewidth)\","
386
                 ." label=\""
387
                 .$label."\", headlabel=\"".$headlabel
388
                 ."\", taillabel=\"".$taillabel."\" ]";
389
      $result .= ";\n";
390
 
391
    }
392
  }
393
  $result = $result . "\n}";
394
 
395
  return $result;
396
}
397
 
398
function  xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2,
399
                                   $type, $threshold, $source) {
400
  $total1;
401
  $total2;
402
 
403
  $raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused);
404
  $raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused);
405
 
406
  // init_metrics($raw_data1, null, null);
407
  $children_table1 = xhprof_get_children_table($raw_data1);
408
  $children_table2 = xhprof_get_children_table($raw_data2);
409
  $symbol_tab1 = xhprof_compute_flat_info($raw_data1, $total1);
410
  $symbol_tab2 = xhprof_compute_flat_info($raw_data2, $total2);
411
  $run_delta = xhprof_compute_diff($raw_data1, $raw_data2);
412
  $script = xhprof_generate_dot_script($run_delta, $threshold, $source,
413
                                       null, null, true,
414
                                       $symbol_tab1, $symbol_tab2);
415
  $content = xhprof_generate_image_by_dot($script, $type);
416
 
417
  xhprof_generate_mime_header($type, strlen($content));
418
  echo $content;
419
}
420
 
421
/**
422
 * Generate image content from phprof run id.
423
 *
424
 * @param object  $xhprof_runs_impl  An object that implements
425
 *                                   the iXHProfRuns interface
426
 * @param run_id, integer, the unique id for the phprof run, this is the
427
 *                primary key for phprof database table.
428
 * @param type, string, one of the supported image types. See also
429
 *              $xhprof_legal_image_types.
430
 * @param threshold, float, the threshold value [0,1). The functions in the
431
 *                   raw_data whose exclusive wall times ratio are below the
432
 *                   threshold will be filtered out and won't apprear in the
433
 *                   generated image.
434
 * @param func, string, the focus function.
435
 * @returns, string, the DOT script to generate image.
436
 *
437
 * @author cjiang
438
 */
439
function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
440
                                   $threshold, $func, $source,
441
                                   $critical_path) {
442
  if (!$run_id)
443
    return "";
444
 
445
  $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description);
446
  if (!$raw_data) {
447
    xhprof_error("Raw data is empty");
448
    return "";
449
  }
450
 
451
  $script = xhprof_generate_dot_script($raw_data, $threshold, $source,
452
                                       $description, $func, $critical_path);
453
 
454
  $content = xhprof_generate_image_by_dot($script, $type);
455
  return $content;
456
}
457
 
458
/**
459
 * Generate image from phprof run id and send it to client.
460
 *
461
 * @param object  $xhprof_runs_impl  An object that implements
462
 *                                   the iXHProfRuns interface
463
 * @param run_id, integer, the unique id for the phprof run, this is the
464
 *                primary key for phprof database table.
465
 * @param type, string, one of the supported image types. See also
466
 *              $xhprof_legal_image_types.
467
 * @param threshold, float, the threshold value [0,1). The functions in the
468
 *                   raw_data whose exclusive wall times ratio are below the
469
 *                   threshold will be filtered out and won't apprear in the
470
 *                   generated image.
471
 * @param func, string, the focus function.
472
 * @param bool, does this run correspond to a PHProfLive run or a dev run?
473
 * @author cjiang
474
 */
475
function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold,
476
                             $func, $source, $critical_path) {
477
 
478
  $content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
479
                                       $threshold,
480
                                       $func, $source, $critical_path);
481
  if (!$content) {
482
    print "Error: either we can not find profile data for run_id ".$run_id
483
          ." or the threshold ".$threshold." is too small or you do not"
484
          ." have 'dot' image generation utility installed.";
485
    exit();
486
  }
487
 
488
  xhprof_generate_mime_header($type, strlen($content));
489
  echo $content;
490
}