<?php
/**
 * Summary of profiling
 */

require_once "WEB-INF/php/inc.php";

if (! admin_init()) {
  return;
}

$profile = @new Java("com.caucho.profile.Profile");

if ($profile) {
  if ($_POST['action'] == 'stop') {
    $profile->stop();
  }
  else if ($_POST['action'] == 'start') {
    if ($_POST['period'] >= 10) {
      $profile->setPeriod($_POST['period']);
    }

    if ($_POST['depth'] >= 1) {
      $profile->setDepth($_POST['depth']);
    }
    
    $profile->start();
  }

  if ($profile->active) {
    echo "<h2>Profile";
    print_help("Profile");
    echo " Active for Server " . $g_server->Id;
    echo "</h2>";
    echo "Period: {$profile->period}ms Depth: {$profile->depth}";

    echo "<form action='?q=profile' method='post' style='display:inline'>";
    echo "<input type='hidden' name='period' ";
    echo        "value='" . $_POST['period'] . "'/>";
    echo "<input type='hidden' name='depth' ";
    echo        "value='" . $_POST['depth'] . "'/>";

    if ($profileQuercus) {
      echo "<input type='hidden' name='profile_quercus' ";
      echo        "value='profile_quercus'/>";
    }

    echo "<input type='submit' name='action' value='stop'/>";

    echo "</form>";

    echo "</h2>\n";
  }
  else {
    echo "<h2>";
    echo "Profile";
    print_help("Profile");
    echo " Stopped";
    echo "</h2>";

    echo "<form action='?q=profile' method='post' style='display:inline'>";
    echo "Period: <select type='select' name='period'>";

    $PERIODS = Array(10, 25, 100, 250, 1000, 2500, 10000);

    if (!isset($_POST['period']) || 
        !is_numeric($_POST['period']) ||
        !in_array($_POST['period'], $PERIODS))
      $postedPeriod = 100;
    else
      $postedPeriod = $_POST['period'];

    foreach ($PERIODS as $period) {
      if ($period == $postedPeriod) 
        echo "  <option selected value='{$period}'>{$period}ms</option>";
      else
        echo "  <option value='{$period}'>{$period}ms</option>";
    }

    echo "</select>";

    echo " Depth: <select type='select' name='depth'>";

    $DEPTHS = Array(1, 2, 4, 8, 12, 16, 20, 32, 250);

    if (!isset($_POST['depth']) || 
        !is_numeric($_POST['depth']) ||
        !in_array(intval($_POST['depth']), $DEPTHS))
      $postedDepth = 16;
    else
      $postedDepth = $_POST['depth'];

    foreach ($DEPTHS as $depth) {
      if ($depth == $postedDepth) 
        echo "  <option selected>{$depth}</option>";
      else
        echo "  <option>{$depth}</option>";
    }

    echo "</select>";

    echo "<input type='submit' name='action' value='start'/><br/>";

    echo "<span title='Highlight Quercus function names in stacks and ";
    echo              "decode Quercus-generated Java class names and ";
    echo              "functions to their original PHP names'>";

    echo "</form>";

    if ($profileQuercus) 
      echo "<br/>* indicates stack trace contains PHP function calls";
  }

  $period = $profile->getPeriod();
  $sample_ticks = $profile->getTicks();
  $results = $profile->getResults();

  echo "<h3>Run Time: " . ($sample_ticks * $period / 1000) . "s</h3>\n";

  $partition = do_partition_profile($results);
  $groups = array("active", "block", "jni", "resin", "keepalive", "wait",
                  "accept", "single", "all");

//  echo "<table class='threads'>\n";

  if ($results) {
    display_profile_results($partition, $groups, $profile);
  }
}
else {
  echo "<h2>Profiling is not available</h2>";

  echo "<p>Profiling requires Resin Professional and compiled JNI</p>";
}

function display_profile_results($partition, $groups, $profile)
{
  echo "<div id='profile-tabs' style='display: none'>";
  echo "<ul>";
  $count = 0;
  foreach ($groups as $name) {
    $entries = $partition[$name];

    if (sizeof($entries) <= 0)
      continue;

    $count++;

    echo "<li>\n";
    echo "<a class='profile-tab' href='#${name}-tab'>$name</a>";
    echo "</li>\n";
  }
  echo "</ul>\n";

  $count = 0;
  foreach ($groups as $name) {
    $entries = $partition[$name];

    if (sizeof($entries) <= 0)
      continue;

    $count++;

    echo "<div id='${name}-tab' class='profile-tab'>"
    print_profile_tab($entries, $name, $count, $profile);
    echo "</div>\n";
  }

  echo "</div>\n";
}  

function print_profile_tab($entries, $name, $count, $profile)
{
  echo "<div class='pie-chart'>";
  echo "<canvas id='${name}-table-chart'/ width='300' height='300' style='display: block'></canvas>\n";
  echo "<div id='${name}-table-label' class='chart-label'></div>\n";
  echo "</div>\n";

  $period = $profile->getPeriod();
  $sample_ticks = $profile->getTicks();
  
  echo "<table class='threads' id='${name}-table' style='display: inline-block; min-height: 400px;'>\n";

  //$topTicks = $entries[0]->getCount();
  $total_ticks = 0;
  for ($i = sizeof($entries) - 1; $i >= 0; $i--) {
    $total_ticks += $entries[$i]->getCount();
  }

  echo "<tr class='head'>"
  echo "<th colspan='5' align='left'>"
  echo "<span id='sw_$name' class='switch'></span>";
  echo "$name (" . sizeof($entries) . ")";

  echo "</th></tr>\n";

  echo "<tr>";
  echo "  <th></th>";
  echo "  <th>% time</th>";
  echo "  <th>time self";
  echo "  <th>% sum</th>";
  echo "  <th colspan='2'>Method Call</th>";
  echo "</tr>";

  $total_percent = 0;
  for ($i = 0; $i < sizeof($entries); $i++) {
    $entry = $entries[$i];

    $time_percent = 100 * $entry->getCount() / $sample_ticks;
    $self_percent = 100 * $entry->getCount() / $total_ticks;
    $total_percent += $self_percent;

    echo "<tr class='chart-data'>";
    echo "<td>";
    echo "<span id='sw_{$name}_{$i}' class='switch'></span>";
    echo "</td>";
    
    echo "<td align='right' class='time-percent'>";
    printf("%9.3f%%", $time_percent);
    echo "</td><td align='right' class='time-self'>";
    printf("%.3fs", $entry->getCount() * $period * 0.001);
    echo "</td><td align='right' class='sum-percent'>";
    printf("%9.3f%%", $total_percent);
    echo "</td>";

    echo "<td class='method-call'>";

    echo htmlspecialchars(short_method_call($entry->getDescription()));

    echo "</td>";

    echo "<td class='legend'></td>";
    echo "</tr>\n";

    echo "<tr class='toggle-sw_${name} toggle-sw_${name}_${i}'>";
    echo "<td></td>";
    
    echo "<td colspan='4'>";

    echo "<div class='stack-trace'>";
    echo "<hr>";

    foreach ($entry->getStackTrace() as $stack) {
      echo "  at " . print_stack_entry($stack);
    }
    
    echo "</div>";
    echo "</td>";
    echo "</tr>\n";
  }

  echo "</table>\n";

}

function do_partition_profile($entries)
{
  $partition = array();
  foreach ($entries as $info) {
    $stackTrace = $info->getStackTrace();

    if (! $stackTrace) {
      continue;
    }

    $partition["all"][] = $info;
    $className = $stackTrace[0]->className;
    $methodName = $stackTrace[0]->methodName;

    if ($info->getCount() < 2) {
      $partition["single"][] = $info;
    }
    else if ($info->isBlocked()) {
      $partition["block"][] = $info;
    }
    else if ($info->isJni()
             && ! preg_match("/com.caucho.server.admin/", $className)
             && $className != "com.caucho.vfs.JniRandomAccessFile"
             && $className != "com.caucho.vfs.JniFileStream") {
      $partition["jni"][] = $info;
    }
    else if ($info->isWaiting() || $info->isParked() || $info->isSleeping()) {
      $partition["wait"][] = $info;
    }
    else if ($stackTrace[0]->className == "java.lang.Object"
        && $stackTrace[0]->methodName == "wait") {
      $partition["wait"][] = $info;
    }
    else if ($stackTrace[0]->className == "sun.misc.Unsafe"
        && $stackTrace[0]->methodName == "park") {
      $partition["wait"][] = $info;
    }
    else if ($stackTrace[0]->className == "java.lang.Thread"
        && $stackTrace[0]->methodName == "sleep") {
      $partition["wait"][] = $info;
    }
    else if ($stackTrace[0]->className == "com.caucho.vfs.JniServerSocketImpl"
             && $stackTrace[0]->methodName == "nativeAccept") {
      $partition["accept"][] = $info;
    }
    else if ($stackTrace[0]->className == "java.net.PlainSocketImpl"
             && $stackTrace[0]->methodName == "socketAccept") {
      $partition["accept"][] = $info;
    }
    else if ($stackTrace[0]->className == "com.caucho.profile.Profile"
             && $stackTrace[0]->methodName == "nativeProfile") {
      $partition["resin"][] = $info;
    }
    else if ($stackTrace[0]->className == "com.caucho.server.admin.JniNetStat") {
      // netstat is a slow blocking
      $partition["resin"][] = $info;
    }
    else if ($stackTrace[0]->className == "com.caucho.server.port.JniSelectManager"
             && $stackTrace[0]->methodName == "selectNative") {
      $partition["resin"][] = $info;
    }
    else if (is_resin_main($stackTrace)) {
      $partition["resin"][] = $info;
    }
    else if (is_keepalive($stackTrace)) {
      $partition["keepalive"][] = $info;
    }
    else if (is_accept($stackTrace)) {
      $partition["accept"][] = $info;
    }
    else if ($stackTrace[0]->className == "java.lang.ref.ReferenceQueue") {
    }
    else {
      $partition["active"][] = $info;
    }
  }

  return $partition;
}

function is_resin_main($stackTrace)
{
  for ($i = 0; $i < sizeof($stackTrace); $i++) {
    if ($stackTrace[$i]->className == "com.caucho.server.resin.Resin"
        && $stackTrace[$i]->methodName == "waitForExit") {
      return true;
    }
  }

  return false;
}

function is_accept($stackTrace)
{
  if ($stackTrace[0]->className == "com.caucho.vfs.JniServerSocketImpl"
      && $stackTrace[0]->methodName == "nativeAccept") {
    return true;
  }
  else if ($stackTrace[0]->className == "java.net.PlainSocketImpl"
           && $stackTrace[0]->methodName == "socketAccept") {
    return true;
  }
  else if ($stackTrace[0]->className == "java.net.PlainSocketImpl"
           && $stackTrace[0]->methodName == "accept") {
    return true;
  }

  return false;
}

function is_keepalive($stackTrace)
{
  for ($i = 0; $i < sizeof($stackTrace); $i++) {
    if ($stackTrace[$i - 1]->className == "com.caucho.server.port.TcpConnection"
             && $stackTrace[$i - 1]->methodName == "keepaliveRead") {
      return true;
    }
    else if ($stackTrace[$i - 1]->className == "com.caucho.server.port.TcpConnection"
             && $stackTrace[$i - 1]->methodName == "waitForKeepalive") {
      return true;
    }
    else if ($stackTrace[$i - 1]->className == "com.caucho.vfs.ReadStream"
             && $stackTrace[$i - 1]->methodName == "waitForRead") {
      return true;
    }
  }

  return false;
}

function is_quercus($className)
{
  return strpos($className, "_quercus._") === 0;
}

function contains_quercus_calls($entry)
{
  global $profileQuercus;

  if ($profileQuercus) {
    foreach ($entry->getStackTrace() as $stack) {
      if (is_quercus($stack->className))
        return true;
    }
  }

  return false;
}

function decode_quercus_class_name($className)
{
  $parts = explode("$", $className);
  $script = $parts[0];
  $function = $parts[1];

  $script = substr($script, strlen("_quercus._"));
  $script = str_replace("._", "/", $script);
  $script = str_replace("__", ".", $script);
  $script = str_replace("_22d", "-", $script);

  ereg("fun_(.*)_[0-9]*", $function, $matches);

  return Array($script, $matches[1]);
}

function print_stack_entry($stack)
{
  global $profileQuercus;

  if ($profileQuercus && is_quercus($stack->className)) {
    $result = decode_quercus_class_name($stack->className);
    $script = $result[0];
    $function = $result[1];
    $arg = $stack->getArg();

    if ($function == "")
      return "<b>{$script}: &lt;top level&gt;</b>\n";
    else 
      return "<b>{$script}: {$function}({$arg})</b>\n";
  }
  else {
    $methodName = str_replace("<init>", "&lt;init&gt;", $stack->methodName);
    return "{$stack->className}.{$methodName}({$stack->getArg()})\n";
  }
}

function short_method_call($long_method)
{
  $parts = explode('.', $long_method);
  $part_count = count($parts);
  
  if ($part_count >= 2) {
    return $parts[$part_count - 2] . '.' . $parts[$part_count - 1];
  }
  else {
    return $long_method;
  }
}

$javascript = <<<EOF
$("#profile-tabs").show();

$("a.profile-tab").each(function(i) {
  var tabContents = $($(this).attr("href"));
  var table = tabContents.find("table.threads");

  var builder = null;

  var buildChart = function() { 
    if (builder == null) { 
      builder = new PieChartBuilder();
      builder.setTable(table);
      builder.setLabelId(table.attr("id") + "-label");
      builder.setChartId(table.attr("id") + "-chart");
      builder.setLabelClass("method-call");
      builder.setDataClass("time-percent");
      builder.setLegendClass("legend");
      builder.setNumTopSlices(10);
      builder.create();
    }
  };

  if ($(this).parent().hasClass("ui-state-active"))
    buildChart();
  else 
    $(this).click(buildChart);
});
EOF;

display_footer($g_page, javascript_create_tab("profile-tabs") . $javascript);
?>
