Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
   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         0 => array("pipe", "r"),
 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  }