<?php
/**
 * Summary of threading
 */

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

$javascript = <<<EOF
  $("#thread-tabs").tabs().find(".ui-tabs-nav").sortable({axis:'x'});
EOF;

if (! admin_init()) {
  return;
}

$jvm_thread = $g_mbean_server->lookup("java.lang:type=Threading");
$server = $g_mbean_server->lookup("resin:type=Server");

?>
<!--
<h2>Server: <?= $g_server->Id ? $g_server->Id : "default" ?></h2>
-->
<?php

$stat_service = $g_mbean_server->lookup("resin:type=StatService");
$si = sprintf("%02d", $server->SelfServer->ClusterIndex);

$thread_pool = $g_server->ThreadPool;
?>

<!--
"Restart" - "Exit this instance cleanly and allow the wrapper script to start a new JVM."
-->

<!--
<h2>Thread pool</h2>

<table class="data">
  <tr>
    <th colspan='3'>Resin Threads</th>

    <th colspan='2'>JVM Threads</th>

    <th colspan='2'>Config</th>
  </tr>
  <tr>
    <th title="The number of active threads. These threads are busy servicing requests or performing other tasks.">Active</th>
    <th title="The number of idle threads. These threads are allocated but inactive, available for new requests or tasks.">Idle</th>
    <th title="The current total number of threads managed by the pool.">Total</th>

    <th title="The number of threads currently running in the JVM.">Total</th>
    <th title="The maximum number of threads running in the JVM.">Peak</th>

    <th title="The maximum number of threads that Resin can allocate.">thread-max</th>
    <th title="The minimum number of threads Resin should have available for new requests or other tasks.  This value causes a minimum number of idle threads, useful for situations where there is a sudden increase in the number of threads required.">thread-idle-min</th>
  </tr>
  <tr align='right'>
    <td><?= $thread_pool->ThreadActiveCount ?></td>
    <td><?= $thread_pool->ThreadIdleCount ?></td>
    <td><?= $thread_pool->ThreadCount ?></td>

    <td><?= $jvm_thread->ThreadCount ?></td>
    <td><?= $jvm_thread->PeakThreadCount ?></td>

    <td><?= $thread_pool->ThreadMax ?></td>
    <td><?= $thread_pool->ThreadIdleMin ?></td>
  </tr>
</table>
-->
<?php

$threads = array();
$thread_ids = $jvm_thread->AllThreadIds;
sort($thread_ids);

$info_list = $jvm_thread->getThreadInfo($thread_ids, true, true);

foreach ($info_list as $info) {
  $conn = $server->findConnectionByThreadId($id);

  $id = $info->ThreadId;

  $thread = array("id" => $id,
                  "info" => $info);

  if ($conn) {
    $thread["url"] = $conn->getUrl();
  }
    
  $threads[] = $thread;
}

echo "<h1>Thread Dump"; print_help("Thread Dump"); echo "</h1>\n"

$thread_group = partition_threads($threads);
$groups = array("active", "blocked", "jni", "locking",
                "misc", "wait", "idle", "all");

display_tabs($thread_group, $groups);

function display_tabs($thread_group, $groups)
{
  echo "<div id='thread-tabs'>";
  echo "<ul>";
  $count = 0;
  foreach ($groups as $name) {
    $entries = $thread_group[$name];

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

    $count++;

    $tab_name = $name . "-tab";

    $thread_count = thread_count_rec($entries);

    echo "<li><a href='#${tab_name}'>$name"
         . " (" . $thread_count . ")</a></li>\n";
  }
  echo "</ul>\n";

  thread_display_content($thread_group, $groups);

  echo "</div>\n";
}

function thread_display_content($partition, $groups)
{
  foreach ($groups as $name) {
    $thread_group = $partition[$name];

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

    echo "<div id='${name}-tab'>\n";

    foreach ($thread_group as $threads) {
      thread_display_group($name, $threads);
    }
    
    echo "</div>\n";
  }
}

function thread_display_group($group_name, $threads)
{
  $name = $threads["name"];
  if (! $name) {
    $name = $group_name;
  }

  if ($group_name != "all") {
    usort($threads, "thread_name_cmp");
  }
    
  echo "<table class='threads'>\n";
  
  echo "<tr class='head'><th colspan='5' align='left'>";
  
  echo "<span id='sw_" . $group_name . "' class='switch'></span>\n";
  echo "$name (" . thread_count_rec($threads) . ")";

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

  echo "<tr>";
//  echo "<td style='border-width:0'>&nbsp;&nbsp;&nbsp;</td>";
  echo "<th>id</th>";
  echo "<th>name</th>";
  echo "<th>method</th>";
  echo "<th>state</th>";
  echo "<th>url</th>";
  echo "</tr>\n";

  $count = 0;
  foreach ($threads as $thread) {
    $info = $thread["info"];

    if (is_object($info)) {
      thread_display_thread($info, $group_name, $count++);
    }
  }
  
  echo "</table>\n";
}

function thread_count_rec($value)
{
  $count = 0;

  if (is_array($value)) {
    $info = $value["info"];

    if (is_object($info)) {
      $count++;
    }

    foreach ($value as $item) {
      $count += thread_count_rec($item);
    }
  }

  return $count;
}

function thread_display_thread($info, $group_name, $count)
{
  echo "<tr class='" . row_style($count) . "'>";

  $id = $info->threadId;

//    echo "<td style='border-width:0'>&nbsp;&nbsp;&nbsp;</td>";
  echo "<td><a name='th_${id}'></a>";
  echo "<span id='sw_${group_name}_${id}' class='switch'></span>\n";
  echo $id . "</td>";
  echo "<td>" . $info->threadName . "</td>";

  $topStack = thread_get_description($info);

  if ($topStack) {
    $name = $topStack->className;
    $tail = strrchr($name, '.');
    if ($tail)
      $name = substr($tail, 1);
    echo "<td>" . $name . "."
	 . $topStack->methodName . "()</td>";
  }
  else
    echo "<td></td>";
    
  echo "<td>" . $info->threadState;
  if ($info->isInNative())
    echo " (JNI)";
  if ($info->getBlockedTime() > 0)
    echo sprintf(" (%dms)", $info->getBlockedTime())
  else if ($info->getWaitedTime() > 0)
    echo sprintf(" (%dms)", $info->getWaitedTime())
  echo "</td>";

  echo "<td>" . $thread["url"] . "</td>\n";

  echo "</tr>\n";

  thread_display_stack($id, $group_name, $info);

  echo "</tr>\n";
}  

function thread_display_stack($id, $group, $info)
{
  echo "<tr class='stack_trace toggle-sw_${group} toggle-sw_${group}_${id}'>";
  echo "<td colspan='5'>";
 // echo "<pre class='stack_trace toggle-sw_${group}'>";
  echo "<pre class='stack_trace'>";

  if ($info->getLockName()) {
    echo "<b> -- waiting on " . $info->getLockName() . " owned by ";
    echo "<a href='#th_${group}_{$info->getLockOwnerId()}'>"
         . $info->getLockOwnerId() . "</a></b>\n\n";
  }
    
  // echo $info;

  // XXX: need to look at the JDK 1.6 for locks
  $monitors = $info->getLockedMonitors();
  $synchronizers = $info->getLockedSynchronizers();

  $i = 0;
  foreach ($info->stackTrace as $elt) {
    thread_print_monitor($monitors, $i);
    echo " at {$elt->className}.{$elt->methodName} ({$elt->fileName}:{$elt->lineNumber})\n";
    
    $i++;
  }

  echo "</pre>\n";
  echo "</td>\n";
}

function thread_print_monitor($monitors, $i)
{
  foreach ($monitors as $monitor) {
    if ($monitor->getLockedStackDepth() == $i) {
      printf("<b> -- locked %s@0x%x</b>\n",
             $monitor->getClassName(),
             $monitor->getIdentityHashCode());
    }
  }
}

function thread_get_description($info)
{
  foreach ($info->stackTrace as $stackItem) {
    if ($stackItem->className == "sun.misc.Unsafe"
        || $stackItem->className == "java.util.concurrent.locks.LockSupport"
        || $stackItem->className == "java.lang.Object") {
      continue;
    }

    return $stackItem;
  }

  return $info->stackTrace[0];
}

// echo "</table>\n";

/*
foreach ($thread_ids as $id) {
  var_dump($jvm_thread->getThreadInfo($id));
}
*/
echo "</pre>";

/*

foreach ($slow_conn as $slow) {
  echo "<h3>" . $slow->ObjectName . " " . ($slow->ActiveTime / 1000) . "</h3>";

  $thread_id = $slow->ThreadId;

  resin_var_dump($thread_id);
  $info = $thread_mgr->getThreadInfo($thread_id, 16);

  if ($info) {
    $bean = Java("java.lang.management.ThreadInfo");
    $info = $bean->from($info);
  }

  resin_var_dump($info->getStackTrace());

}
*/

function thread_name_cmp($thread_a, $thread_b)
{
  $info_a = $thread_a["info"];
  $info_b = $thread_b["info"];
  
  if (! is_object($info_a))
    return 1;
  else if (! is_object($info_b))
    return -1;

  if ($info_a->threadId == $info_b->threadId)
    return 0;
  else if ($info_b->getLockName() && ! $info_a->getLockName())
    return -1;
  else if ($info_a->getLockName() && ! $info_b->getLockName())
    return 1;
  else if ($info_a->threadId < $info_b->threadId)
    return -1;
  else
    return 1;
}

function partition_threads($threads)
{
  $partition = array();

  foreach ($threads as $thread) {
    $info = $thread["info"];

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

    if (is_idle_pool_thread($info)) {
      $partition["idle"][0][] = $thread;
    }
    else if ($info->getLockName() != null && $info->getLockOwnerId() > 0) {
      $owner_id = $info->getLockOwnerId();
      $owner_thread = thread_find($threads, $info->getLockOwnerId());

      $lock_group =&  $partition["locking"][$owner_id];
      
      if ($owner_thread) {
        $lock_group["owner"] = $owner_thread;
        $lock_group["name"] = "lock = " . $info->getLockName();
      }
      else {
        $lock_group["name"] = "system";
      }

      $lock_group[] = $thread;
    }
    else if (is_jni_thread($info)) {
      $partition["jni"][0][] = $thread;
    }
    else if (is_wait_thread($info)) {
      $partition["wait"][0][] = $thread;
    }
/*
    else if (is_accept_thread($info)) {
      $partition["accept"][0][] = $thread;
    }
    else if (is_keepalive_thread($info)) {
      $partition["keepalive"][0][] = $thread;
    }
*/
    else if (preg_match("/^(http|server)/", $info->threadName)) {
      $partition["active"][0][] = $thread;
    }
    else {
      $partition["misc"][0][] = $thread;
    }
  }

  return $partition;
}

function thread_find($threads, $id)
{
  foreach ($threads as $thread) {
    if ($thread["info"]->threadId == $id)
      return $thread;
  }

  return null;
}

function is_accept_thread($info)
{
  foreach ($info->stackTrace as $item) {
    if ($item->className == "com.caucho.server.port.Port"
	&& $item->methodName == "accept")
      return true;
  }

  return false;
}

function is_keepalive_thread($info)
{
  $stackTrace = $info->stackTrace;
  
  for ($i = 0; $i < sizeof($stackTrace); $i++) {
    $item = $stackTrace[$i];
  
    if ($item->className == "com.caucho.server.port.TcpConnection"
	&& $item->methodName == "waitForKeepalive")
      return true;

    if ($item->className == "com.caucho.server.port.TcpConnection"
	&& $item->methodName == "run") {
      $prev = $stackTrace[$i - 1];
      
      if ($prev->className == "com.caucho.vfs.ReadStream"
	  && $prev->methodName == "waitForRead")
	return true;
      else if ($prev->className == "com.caucho.server.port.TcpConnection"
  	       && $prev->methodName == "waitForKeepalive")
	return true;
      else
	return false;
    }
  }

  return false;
}

function is_blocked_thread($info)
{
  if ($info == null)
    return false;

  return ($info->getThreadState() == "BLOCKED")
}

function is_wait_thread($info)
{
  if ($info == null)
    return false;

  if ($info->getThreadState() == "WAITING"
      || $info->getThreadState() == "TIMED_WAITING") {
    return true;
  }
  else
    return false;
}

function is_jni_thread($info)
{
  return $info && $info->isInNative();
}

function is_idle_pool_thread($info)
{
  $stackTrace = $info->stackTrace;
  
  for ($i = 0; $i < sizeof($stackTrace); $i++) {
    $item = $stackTrace[$i];
  
    if ($item->className == "com.caucho.util.ThreadPool\$PoolThread"
        && $item->methodName == "nextTask") {
      $prev = $stackTrace[$i - 1];
      
      if ($prev->className == "java.util.concurrent.locks.LockSupport"
  	  && $prev->methodName == "park")
	return true;
      else
	return false;
    }
  }

  return false;
}

display_footer($g_page, $javascript);

?>
