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.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   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 various XHProf library (utility) functions.
  19  // Do not add any display specific code here.
  20  //
  21  
  22  function xhprof_error($message) {
  23    error_log($message);
  24  }
  25  
  26  /*
  27   * The list of possible metrics collected as part of XHProf that
  28   * require inclusive/exclusive handling while reporting.
  29   *
  30   * @author Kannan
  31   */
  32  function xhprof_get_possible_metrics() {
  33   static $possible_metrics =
  34     array("wt" => array("Wall", "microsecs", "walltime"),
  35           "ut" => array("User", "microsecs", "user cpu time"),
  36           "st" => array("Sys", "microsecs", "system cpu time"),
  37           "cpu" => array("Cpu", "microsecs", "cpu time"),
  38           "mu" => array("MUse", "bytes", "memory usage"),
  39           "pmu" => array("PMUse", "bytes", "peak memory usage"),
  40           "samples" => array("Samples", "samples", "cpu time"));
  41   return $possible_metrics;
  42  }
  43  
  44  /**
  45   * Initialize the metrics we'll display based on the information
  46   * in the raw data.
  47   *
  48   * @author Kannan
  49   */
  50  function init_metrics($xhprof_data, $rep_symbol, $sort, $diff_report = false) {
  51    global $stats;
  52    global $pc_stats;
  53    global $metrics;
  54    global $diff_mode;
  55    global $sortable_columns;
  56    global $sort_col;
  57    global $display_calls;
  58  
  59    $diff_mode = $diff_report;
  60  
  61    if (!empty($sort)) {
  62      if (array_key_exists($sort, $sortable_columns)) {
  63        $sort_col = $sort;
  64      } else {
  65        print("Invalid Sort Key $sort specified in URL");
  66      }
  67    }
  68  
  69    // For C++ profiler runs, walltime attribute isn't present.
  70    // In that case, use "samples" as the default sort column.
  71    if (!isset($xhprof_data["main()"]["wt"])) {
  72  
  73      if ($sort_col == "wt") {
  74        $sort_col = "samples";
  75      }
  76  
  77      // C++ profiler data doesn't have call counts.
  78      // ideally we should check to see if "ct" metric
  79      // is present for "main()". But currently "ct"
  80      // metric is artificially set to 1. So, relying
  81      // on absence of "wt" metric instead.
  82      $display_calls = false;
  83    } else {
  84      $display_calls = true;
  85    }
  86  
  87    // parent/child report doesn't support exclusive times yet.
  88    // So, change sort hyperlinks to closest fit.
  89    if (!empty($rep_symbol)) {
  90      $sort_col = str_replace("excl_", "", $sort_col);
  91    }
  92  
  93    if ($display_calls) {
  94      $stats = array("fn", "ct", "Calls%");
  95    } else {
  96      $stats = array("fn");
  97    }
  98  
  99    $pc_stats = $stats;
 100  
 101    $possible_metrics = xhprof_get_possible_metrics();
 102    foreach ($possible_metrics as $metric => $desc) {
 103      if (isset($xhprof_data["main()"][$metric])) {
 104        $metrics[] = $metric;
 105        // flat (top-level reports): we can compute
 106        // exclusive metrics reports as well.
 107        $stats[] = $metric;
 108        $stats[] = "I" . $desc[0] . "%";
 109        $stats[] = "excl_" . $metric;
 110        $stats[] = "E" . $desc[0] . "%";
 111  
 112        // parent/child report for a function: we can
 113        // only breakdown inclusive times correctly.
 114        $pc_stats[] = $metric;
 115        $pc_stats[] = "I" . $desc[0] . "%";
 116      }
 117    }
 118  }
 119  
 120  /*
 121   * Get the list of metrics present in $xhprof_data as an array.
 122   *
 123   * @author Kannan
 124   */
 125  function xhprof_get_metrics($xhprof_data) {
 126  
 127    // get list of valid metrics
 128    $possible_metrics = xhprof_get_possible_metrics();
 129  
 130    // return those that are present in the raw data.
 131    // We'll just look at the root of the subtree for this.
 132    $metrics = array();
 133    foreach ($possible_metrics as $metric => $desc) {
 134      if (isset($xhprof_data["main()"][$metric])) {
 135        $metrics[] = $metric;
 136      }
 137    }
 138  
 139    return $metrics;
 140  }
 141  
 142  /**
 143   * Takes a parent/child function name encoded as
 144   * "a==>b" and returns array("a", "b").
 145   *
 146   * @author Kannan
 147   */
 148  function xhprof_parse_parent_child($parent_child) {
 149    $ret = explode("==>", $parent_child);
 150  
 151    // Return if both parent and child are set
 152    if (isset($ret[1])) {
 153      return $ret;
 154    }
 155  
 156    return array(null, $ret[0]);
 157  }
 158  
 159  /**
 160   * Given parent & child function name, composes the key
 161   * in the format present in the raw data.
 162   *
 163   * @author Kannan
 164   */
 165  function xhprof_build_parent_child_key($parent, $child) {
 166    if ($parent) {
 167      return $parent . "==>" . $child;
 168    } else {
 169      return $child;
 170    }
 171  }
 172  
 173  
 174  /**
 175   * Checks if XHProf raw data appears to be valid and not corrupted.
 176   *
 177   *  @param   int    $run_id        Run id of run to be pruned.
 178   *                                 [Used only for reporting errors.]
 179   *  @param   array  $raw_data      XHProf raw data to be pruned
 180   *                                 & validated.
 181   *
 182   *  @return  bool   true on success, false on failure
 183   *
 184   *  @author Kannan
 185   */
 186  function xhprof_valid_run($run_id, $raw_data) {
 187  
 188    $main_info = $raw_data["main()"];
 189    if (empty($main_info)) {
 190      xhprof_error("XHProf: main() missing in raw data for Run ID: $run_id");
 191      return false;
 192    }
 193  
 194    // raw data should contain either wall time or samples information...
 195    if (isset($main_info["wt"])) {
 196      $metric = "wt";
 197    } else if (isset($main_info["samples"])) {
 198      $metric = "samples";
 199    } else {
 200      xhprof_error("XHProf: Wall Time information missing from Run ID: $run_id");
 201      return false;
 202    }
 203  
 204    foreach ($raw_data as $info) {
 205      $val = $info[$metric];
 206  
 207      // basic sanity checks...
 208      if ($val < 0) {
 209        xhprof_error("XHProf: $metric should not be negative: Run ID $run_id"
 210                     . serialize($info));
 211        return false;
 212      }
 213      if ($val > (86400000000)) {
 214        xhprof_error("XHProf: $metric > 1 day found in Run ID: $run_id "
 215                     . serialize($info));
 216        return false;
 217      }
 218    }
 219    return true;
 220  }
 221  
 222  
 223  /**
 224   * Return a trimmed version of the XHProf raw data. Note that the raw
 225   * data contains one entry for each unique parent/child function
 226   * combination.The trimmed version of raw data will only contain
 227   * entries where either the parent or child function is in the list
 228   * of $functions_to_keep.
 229   *
 230   * Note: Function main() is also always kept so that overall totals
 231   * can still be obtained from the trimmed version.
 232   *
 233   * @param  array  XHProf raw data
 234   * @param  array  array of function names
 235   *
 236   * @return array  Trimmed XHProf Report
 237   *
 238   * @author Kannan
 239   */
 240  function xhprof_trim_run($raw_data, $functions_to_keep) {
 241  
 242    // convert list of functions to a hash with function as the key
 243    $function_map = array_fill_keys($functions_to_keep, 1);
 244  
 245    // always keep main() as well so that overall totals can still
 246    // be computed if need be.
 247    $function_map['main()'] = 1;
 248  
 249    $new_raw_data = array();
 250    foreach ($raw_data as $parent_child => $info) {
 251      list($parent, $child) = xhprof_parse_parent_child($parent_child);
 252  
 253      if (isset($function_map[$parent]) || isset($function_map[$child])) {
 254        $new_raw_data[$parent_child] = $info;
 255      }
 256    }
 257  
 258    return $new_raw_data;
 259  }
 260  
 261  /**
 262   * Takes raw XHProf data that was aggregated over "$num_runs" number
 263   * of runs averages/nomalizes the data. Essentially the various metrics
 264   * collected are divided by $num_runs.
 265   *
 266   * @author Kannan
 267   */
 268  function xhprof_normalize_metrics($raw_data, $num_runs) {
 269  
 270    if (empty($raw_data) || ($num_runs == 0)) {
 271      return $raw_data;
 272    }
 273  
 274    $raw_data_total = array();
 275  
 276    if (isset($raw_data["==>main()"]) && isset($raw_data["main()"])) {
 277      xhprof_error("XHProf Error: both ==>main() and main() set in raw data...");
 278    }
 279  
 280    foreach ($raw_data as $parent_child => $info) {
 281      foreach ($info as $metric => $value) {
 282        $raw_data_total[$parent_child][$metric] = ($value / $num_runs);
 283      }
 284    }
 285  
 286    return $raw_data_total;
 287  }
 288  
 289  
 290  /**
 291   * Get raw data corresponding to specified array of runs
 292   * aggregated by certain weightage.
 293   *
 294   * Suppose you have run:5 corresponding to page1.php,
 295   *                  run:6 corresponding to page2.php,
 296   *             and  run:7 corresponding to page3.php
 297   *
 298   * and you want to accumulate these runs in a 2:4:1 ratio. You
 299   * can do so by calling:
 300   *
 301   *     xhprof_aggregate_runs(array(5, 6, 7), array(2, 4, 1));
 302   *
 303   * The above will return raw data for the runs aggregated
 304   * in 2:4:1 ratio.
 305   *
 306   *  @param object  $xhprof_runs_impl  An object that implements
 307   *                                    the iXHProfRuns interface
 308   *  @param  array  $runs            run ids of the XHProf runs..
 309   *  @param  array  $wts             integral (ideally) weights for $runs
 310   *  @param  string $source          source to fetch raw data for run from
 311   *  @param  bool   $use_script_name If true, a fake edge from main() to
 312   *                                  to __script::<scriptname> is introduced
 313   *                                  in the raw data so that after aggregations
 314   *                                  the script name is still preserved.
 315   *
 316   *  @return array  Return aggregated raw data
 317   *
 318   *  @author Kannan
 319   */
 320  function xhprof_aggregate_runs($xhprof_runs_impl, $runs,
 321                                 $wts, $source="phprof",
 322                                 $use_script_name=false) {
 323  
 324    $raw_data_total = null;
 325    $raw_data       = null;
 326    $metrics        = array();
 327  
 328    $run_count = count($runs);
 329    $wts_count = count($wts);
 330  
 331    if (($run_count == 0) ||
 332        (($wts_count > 0) && ($run_count != $wts_count))) {
 333      return array('description' => 'Invalid input..',
 334                   'raw'  => null);
 335    }
 336  
 337    $bad_runs = array();
 338    foreach ($runs as $idx => $run_id) {
 339  
 340      $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description);
 341  
 342      // use the first run to derive what metrics to aggregate on.
 343      if ($idx == 0) {
 344        foreach ($raw_data["main()"] as $metric => $val) {
 345          if ($metric != "pmu") {
 346            // for now, just to keep data size small, skip "peak" memory usage
 347            // data while aggregating.
 348            // The "regular" memory usage data will still be tracked.
 349            if (isset($val)) {
 350              $metrics[] = $metric;
 351            }
 352          }
 353        }
 354      }
 355  
 356      if (!xhprof_valid_run($run_id, $raw_data)) {
 357        $bad_runs[] = $run_id;
 358        continue;
 359      }
 360  
 361      if ($use_script_name) {
 362        $page = $description;
 363  
 364        // create a fake function '__script::$page', and have and edge from
 365        // main() to '__script::$page'. We will also need edges to transfer
 366        // all edges originating from main() to now originate from
 367        // '__script::$page' to all function called from main().
 368        //
 369        // We also weight main() ever so slightly higher so that
 370        // it shows up above the new entry in reports sorted by
 371        // inclusive metrics or call counts.
 372        if ($page) {
 373          foreach ($raw_data["main()"] as $metric => $val) {
 374            $fake_edge[$metric] = $val;
 375            $new_main[$metric]  = $val + 0.00001;
 376          }
 377          $raw_data["main()"] = $new_main;
 378          $raw_data[xhprof_build_parent_child_key("main()",
 379                                                  "__script::$page")]
 380            = $fake_edge;
 381        } else {
 382          $use_script_name = false;
 383        }
 384      }
 385  
 386      // if no weights specified, use 1 as the default weightage..
 387      $wt = ($wts_count == 0) ? 1 : $wts[$idx];
 388  
 389      // aggregate $raw_data into $raw_data_total with appropriate weight ($wt)
 390      foreach ($raw_data as $parent_child => $info) {
 391        if ($use_script_name) {
 392          // if this is an old edge originating from main(), it now
 393          // needs to be from '__script::$page'
 394          if (substr($parent_child, 0, 9) == "main()==>") {
 395            $child = substr($parent_child, 9);
 396            // ignore the newly added edge from main()
 397            if (substr($child, 0, 10) != "__script::") {
 398              $parent_child = xhprof_build_parent_child_key("__script::$page",
 399                                                            $child);
 400            }
 401          }
 402        }
 403  
 404        if (!isset($raw_data_total[$parent_child])) {
 405          foreach ($metrics as $metric) {
 406            $raw_data_total[$parent_child][$metric] = ($wt * $info[$metric]);
 407          }
 408        } else {
 409          foreach ($metrics as $metric) {
 410            $raw_data_total[$parent_child][$metric] += ($wt * $info[$metric]);
 411          }
 412        }
 413      }
 414    }
 415  
 416    $runs_string = implode(",", $runs);
 417  
 418    if (isset($wts)) {
 419      $wts_string  = "in the ratio (" . implode(":", $wts) . ")";
 420      $normalization_count = array_sum($wts);
 421    } else {
 422      $wts_string = "";
 423      $normalization_count = $run_count;
 424    }
 425  
 426    $run_count = $run_count - count($bad_runs);
 427  
 428    $data['description'] = "Aggregated Report for $run_count runs: ".
 429                           "$runs_string $wts_string\n";
 430    $data['raw'] = xhprof_normalize_metrics($raw_data_total,
 431                                            $normalization_count);
 432    $data['bad_runs'] = $bad_runs;
 433  
 434    return $data;
 435  }
 436  
 437  
 438  /**
 439   * Analyze hierarchical raw data, and compute per-function (flat)
 440   * inclusive and exclusive metrics.
 441   *
 442   * Also, store overall totals in the 2nd argument.
 443   *
 444   * @param  array $raw_data          XHProf format raw profiler data.
 445   * @param  array &$overall_totals   OUT argument for returning
 446   *                                  overall totals for various
 447   *                                  metrics.
 448   * @return array Returns a map from function name to its
 449   *               call count and inclusive & exclusive metrics
 450   *               (such as wall time, etc.).
 451   *
 452   * @author Kannan Muthukkaruppan
 453   */
 454  function xhprof_compute_flat_info($raw_data, &$overall_totals) {
 455  
 456    global $display_calls;
 457  
 458    $metrics = xhprof_get_metrics($raw_data);
 459  
 460    $overall_totals = array("ct" => 0,
 461                             "wt" => 0,
 462                             "ut" => 0,
 463                             "st" => 0,
 464                             "cpu" => 0,
 465                             "mu" => 0,
 466                             "pmu" => 0,
 467                             "samples" => 0
 468                             );
 469  
 470    // compute inclusive times for each function
 471    $symbol_tab = xhprof_compute_inclusive_times($raw_data);
 472  
 473    /* total metric value is the metric value for "main()" */
 474    foreach ($metrics as $metric) {
 475      $overall_totals[$metric] = $symbol_tab["main()"][$metric];
 476    }
 477  
 478    /*
 479     * initialize exclusive (self) metric value to inclusive metric value
 480     * to start with.
 481     * In the same pass, also add up the total number of function calls.
 482     */
 483    foreach ($symbol_tab as $symbol => $info) {
 484      foreach ($metrics as $metric) {
 485        $symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric];
 486      }
 487      if ($display_calls) {
 488        /* keep track of total number of calls */
 489        $overall_totals["ct"] += $info["ct"];
 490      }
 491    }
 492  
 493    /* adjust exclusive times by deducting inclusive time of children */
 494    foreach ($raw_data as $parent_child => $info) {
 495      list($parent, $child) = xhprof_parse_parent_child($parent_child);
 496  
 497      if ($parent) {
 498        foreach ($metrics as $metric) {
 499          // make sure the parent exists hasn't been pruned.
 500          if (isset($symbol_tab[$parent])) {
 501            $symbol_tab[$parent]["excl_" . $metric] -= $info[$metric];
 502          }
 503        }
 504      }
 505    }
 506  
 507    return $symbol_tab;
 508  }
 509  
 510  /**
 511   * Hierarchical diff:
 512   * Compute and return difference of two call graphs: Run2 - Run1.
 513   *
 514   * @author Kannan
 515   */
 516  function xhprof_compute_diff($xhprof_data1, $xhprof_data2) {
 517    global $display_calls;
 518  
 519    // use the second run to decide what metrics we will do the diff on
 520    $metrics = xhprof_get_metrics($xhprof_data2);
 521  
 522    $xhprof_delta = $xhprof_data2;
 523  
 524    foreach ($xhprof_data1 as $parent_child => $info) {
 525  
 526      if (!isset($xhprof_delta[$parent_child])) {
 527  
 528        // this pc combination was not present in run1;
 529        // initialize all values to zero.
 530        if ($display_calls) {
 531          $xhprof_delta[$parent_child] = array("ct" => 0);
 532        } else {
 533          $xhprof_delta[$parent_child] = array();
 534        }
 535        foreach ($metrics as $metric) {
 536          $xhprof_delta[$parent_child][$metric] = 0;
 537        }
 538      }
 539  
 540      if ($display_calls) {
 541        $xhprof_delta[$parent_child]["ct"] -= $info["ct"];
 542      }
 543  
 544      foreach ($metrics as $metric) {
 545        $xhprof_delta[$parent_child][$metric] -= $info[$metric];
 546      }
 547    }
 548  
 549    return $xhprof_delta;
 550  }
 551  
 552  
 553  /**
 554   * Compute inclusive metrics for function. This code was factored out
 555   * of xhprof_compute_flat_info().
 556   *
 557   * The raw data contains inclusive metrics of a function for each
 558   * unique parent function it is called from. The total inclusive metrics
 559   * for a function is therefore the sum of inclusive metrics for the
 560   * function across all parents.
 561   *
 562   * @return array  Returns a map of function name to total (across all parents)
 563   *                inclusive metrics for the function.
 564   *
 565   * @author Kannan
 566   */
 567  function xhprof_compute_inclusive_times($raw_data) {
 568    global $display_calls;
 569  
 570    $metrics = xhprof_get_metrics($raw_data);
 571  
 572    $symbol_tab = array();
 573  
 574    /*
 575     * First compute inclusive time for each function and total
 576     * call count for each function across all parents the
 577     * function is called from.
 578     */
 579    foreach ($raw_data as $parent_child => $info) {
 580  
 581      list($parent, $child) = xhprof_parse_parent_child($parent_child);
 582  
 583      if ($parent == $child) {
 584        /*
 585         * XHProf PHP extension should never trigger this situation any more.
 586         * Recursion is handled in the XHProf PHP extension by giving nested
 587         * calls a unique recursion-depth appended name (for example, foo@1).
 588         */
 589        xhprof_error("Error in Raw Data: parent & child are both: $parent");
 590        return;
 591      }
 592  
 593      if (!isset($symbol_tab[$child])) {
 594  
 595        if ($display_calls) {
 596          $symbol_tab[$child] = array("ct" => $info["ct"]);
 597        } else {
 598          $symbol_tab[$child] = array();
 599        }
 600        foreach ($metrics as $metric) {
 601          $symbol_tab[$child][$metric] = $info[$metric];
 602        }
 603      } else {
 604        if ($display_calls) {
 605          /* increment call count for this child */
 606          $symbol_tab[$child]["ct"] += $info["ct"];
 607        }
 608  
 609        /* update inclusive times/metric for this child  */
 610        foreach ($metrics as $metric) {
 611          $symbol_tab[$child][$metric] += $info[$metric];
 612        }
 613      }
 614    }
 615  
 616    return $symbol_tab;
 617  }
 618  
 619  
 620  /*
 621   * Prunes XHProf raw data:
 622   *
 623   * Any node whose inclusive walltime accounts for less than $prune_percent
 624   * of total walltime is pruned. [It is possible that a child function isn't
 625   * pruned, but one or more of its parents get pruned. In such cases, when
 626   * viewing the child function's hierarchical information, the cost due to
 627   * the pruned parent(s) will be attributed to a special function/symbol
 628   * "__pruned__()".]
 629   *
 630   *  @param   array  $raw_data      XHProf raw data to be pruned & validated.
 631   *  @param   double $prune_percent Any edges that account for less than
 632   *                                 $prune_percent of time will be pruned
 633   *                                 from the raw data.
 634   *
 635   *  @return  array  Returns the pruned raw data.
 636   *
 637   *  @author Kannan
 638   */
 639  function xhprof_prune_run($raw_data, $prune_percent) {
 640  
 641    $main_info = $raw_data["main()"];
 642    if (empty($main_info)) {
 643      xhprof_error("XHProf: main() missing in raw data");
 644      return false;
 645    }
 646  
 647    // raw data should contain either wall time or samples information...
 648    if (isset($main_info["wt"])) {
 649      $prune_metric = "wt";
 650    } else if (isset($main_info["samples"])) {
 651      $prune_metric = "samples";
 652    } else {
 653      xhprof_error("XHProf: for main() we must have either wt "
 654                   ."or samples attribute set");
 655      return false;
 656    }
 657  
 658    // determine the metrics present in the raw data..
 659    $metrics = array();
 660    foreach ($main_info as $metric => $val) {
 661      if (isset($val)) {
 662        $metrics[] = $metric;
 663      }
 664    }
 665  
 666    $prune_threshold = (($main_info[$prune_metric] * $prune_percent) / 100.0);
 667  
 668    init_metrics($raw_data, null, null, false);
 669    $flat_info = xhprof_compute_inclusive_times($raw_data);
 670  
 671    foreach ($raw_data as $parent_child => $info) {
 672  
 673      list($parent, $child) = xhprof_parse_parent_child($parent_child);
 674  
 675      // is this child's overall total from all parents less than threshold?
 676      if ($flat_info[$child][$prune_metric] < $prune_threshold) {
 677        unset($raw_data[$parent_child]); // prune the edge
 678      } else if ($parent &&
 679                 ($parent != "__pruned__()") &&
 680                 ($flat_info[$parent][$prune_metric] < $prune_threshold)) {
 681  
 682        // Parent's overall inclusive metric is less than a threshold.
 683        // All edges to the parent node will get nuked, and this child will
 684        // be a dangling child.
 685        // So instead change its parent to be a special function __pruned__().
 686        $pruned_edge = xhprof_build_parent_child_key("__pruned__()", $child);
 687  
 688        if (isset($raw_data[$pruned_edge])) {
 689          foreach ($metrics as $metric) {
 690            $raw_data[$pruned_edge][$metric]+=$raw_data[$parent_child][$metric];
 691          }
 692        } else {
 693          $raw_data[$pruned_edge] = $raw_data[$parent_child];
 694        }
 695  
 696        unset($raw_data[$parent_child]); // prune the edge
 697      }
 698    }
 699  
 700    return $raw_data;
 701  }
 702  
 703  
 704  /**
 705   * Set one key in an array and return the array
 706   *
 707   * @author Kannan
 708   */
 709  function xhprof_array_set($arr, $k, $v) {
 710    $arr[$k] = $v;
 711    return $arr;
 712  }
 713  
 714  /**
 715   * Removes/unsets one key in an array and return the array
 716   *
 717   * @author Kannan
 718   */
 719  function xhprof_array_unset($arr, $k) {
 720    unset($arr[$k]);
 721    return $arr;
 722  }
 723  
 724  /**
 725   * Type definitions for URL params
 726   */
 727  define('XHPROF_STRING_PARAM', 1);
 728  define('XHPROF_UINT_PARAM',   2);
 729  define('XHPROF_FLOAT_PARAM',  3);
 730  define('XHPROF_BOOL_PARAM',   4);
 731  
 732  
 733  /**
 734   * Internal helper function used by various
 735   * xhprof_get_param* flavors for various
 736   * types of parameters.
 737   *
 738   * @param string   name of the URL query string param
 739   *
 740   * @author Kannan
 741   */
 742  function xhprof_get_param_helper($param) {
 743    $val = null;
 744    if (isset($_GET[$param]))
 745      $val = $_GET[$param];
 746    else if (isset($_POST[$param])) {
 747      $val = $_POST[$param];
 748    }
 749    return $val;
 750  }
 751  
 752  /**
 753   * Extracts value for string param $param from query
 754   * string. If param is not specified, return the
 755   * $default value.
 756   *
 757   * @author Kannan
 758   */
 759  function xhprof_get_string_param($param, $default = '') {
 760    $val = xhprof_get_param_helper($param);
 761  
 762    if ($val === null)
 763      return $default;
 764  
 765    return $val;
 766  }
 767  
 768  /**
 769   * Extracts value for unsigned integer param $param from
 770   * query string. If param is not specified, return the
 771   * $default value.
 772   *
 773   * If value is not a valid unsigned integer, logs error
 774   * and returns null.
 775   *
 776   * @author Kannan
 777   */
 778  function xhprof_get_uint_param($param, $default = 0) {
 779    $val = xhprof_get_param_helper($param);
 780  
 781    if ($val === null)
 782      $val = $default;
 783  
 784    // trim leading/trailing whitespace
 785    $val = trim($val);
 786  
 787    // if it only contains digits, then ok..
 788    if (ctype_digit($val)) {
 789      return $val;
 790    }
 791  
 792    xhprof_error("$param is $val. It must be an unsigned integer.");
 793    return null;
 794  }
 795  
 796  
 797  /**
 798   * Extracts value for a float param $param from
 799   * query string. If param is not specified, return
 800   * the $default value.
 801   *
 802   * If value is not a valid unsigned integer, logs error
 803   * and returns null.
 804   *
 805   * @author Kannan
 806   */
 807  function xhprof_get_float_param($param, $default = 0) {
 808    $val = xhprof_get_param_helper($param);
 809  
 810    if ($val === null)
 811      $val = $default;
 812  
 813    // trim leading/trailing whitespace
 814    $val = trim($val);
 815  
 816    // TBD: confirm the value is indeed a float.
 817    if (true) // for now..
 818      return (float)$val;
 819  
 820    xhprof_error("$param is $val. It must be a float.");
 821    return null;
 822  }
 823  
 824  /**
 825   * Extracts value for a boolean param $param from
 826   * query string. If param is not specified, return
 827   * the $default value.
 828   *
 829   * If value is not a valid unsigned integer, logs error
 830   * and returns null.
 831   *
 832   * @author Kannan
 833   */
 834  function xhprof_get_bool_param($param, $default = false) {
 835    $val = xhprof_get_param_helper($param);
 836  
 837    if ($val === null)
 838      $val = $default;
 839  
 840    // trim leading/trailing whitespace
 841    $val = trim($val);
 842  
 843    switch (strtolower($val)) {
 844    case '0':
 845    case '1':
 846      $val = (bool)$val;
 847      break;
 848    case 'true':
 849    case 'on':
 850    case 'yes':
 851      $val = true;
 852      break;
 853    case 'false':
 854    case 'off':
 855    case 'no':
 856      $val = false;
 857      break;
 858    default:
 859      xhprof_error("$param is $val. It must be a valid boolean string.");
 860      return null;
 861    }
 862  
 863    return $val;
 864  
 865  }
 866  
 867  /**
 868   * Initialize params from URL query string. The function
 869   * creates globals variables for each of the params
 870   * and if the URL query string doesn't specify a particular
 871   * param initializes them with the corresponding default
 872   * value specified in the input.
 873   *
 874   * @params array $params An array whose keys are the names
 875   *                       of URL params who value needs to
 876   *                       be retrieved from the URL query
 877   *                       string. PHP globals are created
 878   *                       with these names. The value is
 879   *                       itself an array with 2-elems (the
 880   *                       param type, and its default value).
 881   *                       If a param is not specified in the
 882   *                       query string the default value is
 883   *                       used.
 884   * @author Kannan
 885   */
 886  function xhprof_param_init($params) {
 887    /* Create variables specified in $params keys, init defaults */
 888    foreach ($params as $k => $v) {
 889      switch ($v[0]) {
 890      case XHPROF_STRING_PARAM:
 891        $p = xhprof_get_string_param($k, $v[1]);
 892        break;
 893      case XHPROF_UINT_PARAM:
 894        $p = xhprof_get_uint_param($k, $v[1]);
 895        break;
 896      case XHPROF_FLOAT_PARAM:
 897        $p = xhprof_get_float_param($k, $v[1]);
 898        break;
 899      case XHPROF_BOOL_PARAM:
 900        $p = xhprof_get_bool_param($k, $v[1]);
 901        break;
 902      default:
 903        xhprof_error("Invalid param type passed to xhprof_param_init: "
 904                     . $v[0]);
 905        exit();
 906      }
 907  
 908      if ($k === 'run') {
 909        $p = implode(',', array_filter(explode(',', $p), 'ctype_xdigit'));
 910      }
 911  
 912      if ($k == 'symbol') {
 913          $p = strip_tags($p);
 914      }
 915  
 916      // create a global variable using the parameter name.
 917      $GLOBALS[$k] = $p;
 918    }
 919  }
 920  
 921  
 922  /**
 923   * Given a partial query string $q return matching function names in
 924   * specified XHProf run. This is used for the type ahead function
 925   * selector.
 926   *
 927   * @author Kannan
 928   */
 929  function xhprof_get_matching_functions($q, $xhprof_data) {
 930  
 931    $matches = array();
 932  
 933    foreach ($xhprof_data as $parent_child => $info) {
 934      list($parent, $child) = xhprof_parse_parent_child($parent_child);
 935      if (stripos($parent, $q) !== false) {
 936        $matches[$parent] = 1;
 937      }
 938      if (stripos($child, $q) !== false) {
 939        $matches[$child] = 1;
 940      }
 941    }
 942  
 943    $res = array_keys($matches);
 944  
 945    // sort it so the answers are in some reliable order...
 946    asort($res);
 947  
 948    return ($res);
 949  }