Browse Source

XCache Administration web frontend

git-svn-id: svn://svn.lighttpd.net/xcache/trunk@34 c26eb9a1-5813-0410-bd6c-c2e55f420ca7
1.1
Xuefer 15 years ago
parent
commit
127b9b1d72
  1. 47
      admin/config.php.example
  2. 3
      admin/index.php
  3. 58
      admin/tablesort.js
  4. 15
      admin/xcache.css
  5. 189
      admin/xcache.php
  6. 233
      admin/xcache.tpl.php
  7. 88
      xcache.c
  8. 5
      xcache.ini

47
admin/config.php.example

@ -0,0 +1,47 @@
<?php
// this is an example only
// write your own config and name it as config.php
$charset = "UTF-8";
// this function is detected by xcache.tpl.php, and enabled if function_exists
// this ob filter is applied for the cache list, not the whole page
function ob_filter_path_nicer($o)
{
$o = str_replace("/home/", "{H}/", $o);
return $o;
}
// you can simply let xcache to do the http auth
// but if you have your home made login/permission system, you can implement the following
// {{{ home made login example
// this is an example only, it's won't work for you without your implemention.
function check_admin_and_by_pass_xcache_http_auth()
{
require("/path/to/user-login-and-permission-lib.php");
session_start();
if (!user_logined()) {
if (!ask_the_user_to_login()) {
exit;
}
}
user_load_permissions();
if (!user_is_admin()) {
die("Permission denied");
}
// user is trusted after permission checks above.
// tell XCache about it (the only way to by pass XCache http auth)
$_SERVER["PHP_AUTH_USER"] = "moo";
$_SERVER["PHP_AUTH_PW"] = "your-xcache-password";
return true;
}
// uncomment:
// check_admin_and_by_pass_xcache_http_auth();
// }}}
?>

3
admin/index.php

@ -0,0 +1,3 @@
<?php
include("xcache.php");

58
admin/tablesort.js

@ -0,0 +1,58 @@
var sort_column;
var prev_span = null;
function get_inner_text(el) {
if((typeof el == 'string')||(typeof el == 'undefined'))
return el;
if(el.innerText)
return el.innerText;
else {
var str = "";
var cs = el.childNodes;
var l = cs.length;
for (i=0;i<l;i++) {
if (cs[i].nodeType==1) str += get_inner_text(cs[i]);
else if (cs[i].nodeType==3) str += cs[i].nodeValue;
}
}
return str;
}
function sortfn(a,b) {
var i = a.cells[sort_column].getAttribute('int');
if (i != null) {
return parseInt(i)-parseInt(b.cells[sort_column].getAttribute('int'));
} else {
var at = get_inner_text(a.cells[sort_column]);
var bt = get_inner_text(b.cells[sort_column]);
aa = at.toLowerCase();
bb = bt.toLowerCase();
if (aa==bb) return 0;
else if (aa<bb) return -1;
else return 1;
}
}
function resort(lnk) {
var span = lnk.childNodes[1];
if (!span) {
var span = document.createElement("span")
span.className = "sortarrow";
lnk.appendChild(span);
}
var table = lnk.parentNode.parentNode.parentNode.parentNode;
var rows = new Array();
for (j=1;j<table.rows.length;j++)
rows[j-1] = table.rows[j];
sort_column = lnk.parentNode.cellIndex;
rows.sort(sortfn);
if (prev_span != null) prev_span.innerHTML = '';
if (span.getAttribute('sortdir')=='down') {
span.innerHTML = '&uarr;';
span.setAttribute('sortdir','up');
rows.reverse();
} else {
span.innerHTML = '&darr;';
span.setAttribute('sortdir','down');
}
for (i=0;i<rows.length;i++)
table.tBodies[0].appendChild(rows[i]);
prev_span = span;
}

15
admin/xcache.css

@ -0,0 +1,15 @@
input, table { font-family: monospace; font-size: 11px; }
table.cycles { border: 1px solid black; background: white; margin-top: 5px; margin-bottom: 5px; }
table.cycles .col1 { background-color: #f5f5f5; }
table.cycles .col2 { background-color: #e0e0e0; }
table.cycles th { background-color: #707090; color: white; font-weight: bold; height: 20px; line-height: 16px; font-family: serif; }
th a { color: white; font-weight: bold; display: block; width: 100%; height: 100%; }
.button { }
span.sortarrow { color: white; text-decoration: none; }
.freeblocks { float: left; margin-right: 4px;}
form {margin: 0; padding: 0}
.percent { border: 1px solid gray; width: 100%; }
.percent .v { background: black; font-size: 1px; line-height: 11px;}
.switcher, h1 { text-align: center; display: block; }
.switcher * { color: blue; }
.switcher a.active { font-weight: bold; font-size: 130%; color: black; }

189
admin/xcache.php

@ -0,0 +1,189 @@
<?php
error_reporting(E_ALL);
define('REQUEST_TIME', time());
class Cycle
{
var $values;
var $i;
var $count;
function Cycle($v)
{
$this->values = func_get_args();
$this->i = -1;
$this->count = count($this->values);
}
function next()
{
$this->i = ($this->i + 1) % $this->count;
return $this->values[$this->i];
}
function cur()
{
return $this->values[$this->i];
}
function reset()
{
$this->i = -1;
}
}
function number_formats($a, $keys)
{
foreach ($keys as $k) {
$a[$k] = number_format($a[$k]);
}
return $a;
}
function size($size, $suffix = '', $precision = 2)
{
$size = (int) $size;
if ($size < 1024)
return number_format($size, $precision) . ' ' . $suffix;
if ($size < 1048576)
return number_format($size / 1024, $precision) . ' K' . $suffix;
return number_format($size / 1048576, $precision) . ' M' . $suffix;
}
function age($time)
{
if (!$time) return '';
$delta = REQUEST_TIME - $time;
if ($delta < 0) {
$delta = -$delta;
}
static $seconds = array(1, 60, 3600, 86400, 604800, 2678400, 31536000);
static $name = array('sec', 'min', 'hr', 'day', 'week', 'month', 'year');
for ($i = 6; $i >= 0; $i --) {
if ($delta >= $seconds[$i]) {
$ret = (int) ($delta / $seconds[$i]);
return $ret . ' ' . $name[$i] . ($ret > 1 ? 's' : '');
}
}
}
function switcher($name, $options)
{
$n = isset($_GET[$name]) ? $_GET[$name] : null;
$html = array();
foreach ($options as $k => $v) {
$html[] = sprintf('<a href="?%s=%s"%s>%s</a>', $name, $k, $k == $n ? 'class="active"' : '', $v);
}
return implode(' ', $html);
}
$charset = "UTF-8";
if (file_exists("config.php")) {
include("config.php");
}
$pcnt = xcache_count(XC_TYPE_PHP);
$vcnt = xcache_count(XC_TYPE_VAR);
$type_none = -1;
if (!isset($_GET['type'])) {
$_GET['type'] = $type_none;
}
$_GET['type'] = $type = (int) $_GET['type'];
// {{{ process clear
function processClear()
{
$type = isset($_POST['type']) ? $_POST['type'] : null;
if ($type != XC_TYPE_PHP && $type != XC_TYPE_VAR) {
$type = null;
}
if (isset($type)) {
$cacheid = (int) (isset($_POST['cacheid']) ? $_POST['cacheid'] : 0);
if (isset($_POST['clearcache'])) {
xcache_clear_cache($type, $cacheid);
}
}
}
processClear();
// }}}
// {{{ load info/list
$cacheinfos = array();
for ($i = 0; $i < $pcnt; $i ++) {
$data = xcache_info(XC_TYPE_PHP, $i);
if ($type === XC_TYPE_PHP) {
$data += xcache_list(XC_TYPE_PHP, $i);
}
$data['type'] = XC_TYPE_PHP;
$data['cache_name'] = "php#$i";
$data['cacheid'] = $i;
$cacheinfos[] = $data;
}
for ($i = 0; $i < $vcnt; $i ++) {
$data = xcache_info(XC_TYPE_VAR, $i);
if ($type === XC_TYPE_VAR) {
$data += xcache_list(XC_TYPE_VAR, $i);
}
$data['type'] = XC_TYPE_VAR;
$data['cache_name'] = "var#$i";
$data['cacheid'] = $i;
$cacheinfos[] = $data;
}
// }}}
// {{{ merge the list
switch ($type) {
case XC_TYPE_PHP:
case XC_TYPE_VAR:
$cachelist = array('type' => $type, 'cache_list' => array(), 'deleted_list' => array());
if ($type == XC_TYPE_VAR) {
$cachelist['type_name'] = 'var';
}
else {
$cachelist['type_name'] = 'php';
}
foreach ($cacheinfos as $i => $c) {
if ($c['type'] == $type && isset($c['cache_list'])) {
foreach ($c['cache_list'] as $e) {
$e['cache_name'] = $c['cache_name'];
$cachelist['cache_list'][] = $e;
}
foreach ($c['deleted_list'] as $e) {
$e['cache_name'] = $c['cache_name'];
$cachelist['deleted_list'][] = $e;
}
}
}
if ($type == XC_TYPE_PHP) {
$inodes = array();
foreach ($cachelist['cache_list'] as $e) {
$i = &$inodes[$e['inode']];
if (isset($i) && $i == 1) {
set_error("duplicate inode $e[inode]");
}
$i ++;
}
}
unset($data);
break;
default:
$_GET['type'] = $type_none;
$cachelist = array();
break;
}
// }}}
$type_php = XC_TYPE_PHP;
$type_var = XC_TYPE_VAR;
$types = array($type_none => 'Statistics', $type_php =>'List PHP', $type_var =>'List Var Data');
include("xcache.tpl.php");
?>

233
admin/xcache.tpl.php

@ -0,0 +1,233 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Language" content="en-us" />
<?php
echo <<<HEAD
<meta http-equiv="Content-Type" content="text/html; charset=$charset" />
<script type="text/javascript" src="tablesort.js" charset="$charset"></script>
HEAD;
?>
<link rel="stylesheet" type="text/css" href="xcache.css" />
<title>XCache Administration</title>
</head>
<body>
<h1>XCache Administration</h1>
<span class="switcher"><?php echo switcher("type", $types); ?></span>
<?php
$a = new Cycle('class="col1"', 'class="col2"');
$b = new Cycle('class="col1"', 'class="col2"');
?>
<table cellspacing="1" cellpadding="4" class="cycles">
<col />
<col align="right" />
<col align="right" />
<col align="right" />
<col />
<col />
<col align="right" />
<col align="right" />
<col align="right" />
<col align="right" />
<col align="right" />
<col align="right" />
<col align="right" />
<col align="right" />
<col />
<tr <?php echo $a->next(); ?>>
<th></th>
<th>Slots</th>
<th>Size</th>
<th>Avail</th>
<th>Used</th>
<th>Clear</th>
<th>Compiling</th>
<th>Hits</th>
<th>Misses</th>
<th>CLogs</th>
<th>OOMs</th>
<th>Protected</th>
<th>Cached</th>
<th>Deleted</th>
<th>Blocks</th>
</tr>
<?php
$numkeys = explode(',', 'slots,size,avail,hits,misses,clogs,ooms,cached,deleted');
foreach ($cacheinfos as $i => $ci) {
echo "
<tr ", $a->next(), ">";
$ci = number_formats($ci, $numkeys);
$ci['compiling'] = $ci['type'] == $type_php ? ($ci['compiling'] ? 'yes' : 'no') : '-';
$ci['can_readonly'] = $ci['can_readonly'] ? 'yes' : 'no';
$percent = (int) (($ci['size'] - $ci['avail']) / $ci['size'] * 100);
echo <<<EOS
<th>{$ci['cache_name']}</th>
<td>{$ci['slots']}</td>
<td>{$ci['size']}</td>
<td>{$ci['avail']}</td>
<td><div class="percent"><div style="width: $percent%" class="v">&nbsp;</div></div></td>
<td>
<form method="post">
<div>
<input type="hidden" name="type" value="{$ci['type']}">
<input type="hidden" name="cacheid" value="{$ci['cacheid']}">
<input type="submit" name="clearcache" value="Clear" class="submit" onclick="return confirm('Sure to clear?');" />
</div>
</form>
</td>
<td>{$ci['compiling']}</td>
<td>{$ci['hits']}</td>
<td>{$ci['misses']}</td>
<td>{$ci['clogs']}</td>
<td>{$ci['ooms']}</td>
<td>{$ci['can_readonly']}</td>
<td>{$ci['cached']}</td>
<td>{$ci['deleted']}</td>
<td>
EOS;
$b->reset();
?>
</td>
</tr>
<?php } ?>
</table>
<?php
foreach ($cacheinfos as $i => $ci) {
?>
<table cellspacing="1" cellpadding="4" class="cycles freeblocks">
<tr>
<th><?php echo $ci['cache_name']; ?> size<br>offset</th>
<?php
foreach ($ci['free_blocks'] as $block) {
$size = number_format($block['size']);
$offset = number_format($block['offset']);
$c = $b->next();
echo "
<td $c><nobr>$size<br>$offset</nobr></td>";
}
?>
</tr>
</table>
<?php
}
?>
<div style="clear: both">&nbsp;</div>
<?php
if ($cachelist) {
$isphp = $cachelist['type'] == $type_php;
if (function_exists("ob_filter_path_nicer")) {
ob_start("ob_filter_path_nicer");
}
foreach (array('Cached' => $cachelist['cache_list'], 'Deleted' => $cachelist['deleted_list']) as $listname => $entries) {
$a->reset();
echo "
<caption>{$cachelist['type_name']} $listname</caption>";
?>
<table cellspacing="1" cellpadding="4" class="cycles entrys" width="100%">
<col />
<col />
<col align="right" />
<col align="right" />
<col align="right" />
<col align="right" />
<col align="right" />
<col align="right" />
<?php
if ($listname == 'Deleted') {
echo '<col align="right" />';
}
if ($isphp) {
echo '<col align="right" />';
echo '<col align="right" />';
echo '<col align="right" />';
}
echo "
<tr ", $a->next(), ">";
?>
<th><a href="javascript:" onclick="resort(this); return false">Cache</a></th>
<th><a href="javascript:" onclick="resort(this); return false">entry</a></th>
<th><a href="javascript:" onclick="resort(this); return false">Hits</a></th>
<th><a href="javascript:" onclick="resort(this); return false">Ref count</a></th>
<th><a href="javascript:" onclick="resort(this); return false">Size</a></th>
<?php if ($isphp) { ?>
<th><a href="javascript:" onclick="resort(this); return false">SrcSize</a></th>
<th><a href="javascript:" onclick="resort(this); return false">Modify</a></th>
<th><a href="javascript:" onclick="resort(this); return false">device</a></th>
<th><a href="javascript:" onclick="resort(this); return false">inode</a></th>
<?php } ?>
<th><a href="javascript:" onclick="resort(this); return false">Access</a></th>
<th><a href="javascript:" onclick="resort(this); return false">Create</a></th>
<?php if ($listname == 'Deleted') { ?>
<th><a href="javascript:" onclick="resort(this); return false">Delete</a></th>
<?php } ?>
</tr>
<?php
foreach ($entries as $i => $entry) {
echo "
<tr ", $a->next(), ">";
$name = htmlspecialchars($entry['name']);
$hits = number_format($entry['hits']);
$refcount = number_format($entry['refcount']);
$size = size($entry['size']);
if ($isphp) {
$sourcesize = size($entry['sourcesize']);
}
if ($isphp) {
$mtime = age($entry['mtime']);
}
$ctime = age($entry['ctime']);
$atime = age($entry['atime']);
$dtime = age($entry['dtime']);
echo <<<ENTRY
<td>{$entry['cache_name']} {$i}</td>
<td>{$name}</td>
<td int="{$entry['hits']}">{$entry['hits']}</td>
<td int="{$entry['refcount']}">{$entry['refcount']}</td>
<td int="{$entry['size']}">{$size}</td>
ENTRY;
if ($isphp) {
echo <<<ENTRY
<td int="{$entry['sourcesize']}">{$sourcesize}</td>
<td int="{$entry['mtime']}">{$mtime}</td>
<td int="{$entry['device']}">{$entry['device']}</td>
<td int="{$entry['inode']}">{$entry['inode']}</td>
ENTRY;
}
echo <<<ENTRY
<td int="{$entry['atime']}">{$atime}</td>
<td int="{$entry['ctime']}">{$ctime}</td>
ENTRY;
if ($listname == 'Deleted') {
echo <<<ENTRY
<td int="{$entry['dtime']}">{$dtime}</td>
ENTRY;
}
echo "
</tr>
";
}
?>
</table>
<?php
}
if (function_exists("ob_filter_path_nicer")) {
ob_end_flush();
}
}
?>
</body>
</html>

88
xcache.c

@ -11,6 +11,7 @@
#include "php.h"
#include "ext/standard/info.h"
#include "ext/standard/md5.h"
#include "zend_extensions.h"
#include "SAPI.h"
@ -1047,9 +1048,82 @@ static void xc_shutdown_globals(zend_xcache_globals* xc_globals TSRMLS_DC) /* {{
/* }}} */
/* user functions */
/* {{{ xcache_op */
static int xcache_admin_auth_check(TSRMLS_C) /* {{{ */
{
zval **server = NULL;
zval **user = NULL;
zval **pass = NULL;
char *admin_user = NULL;
char *admin_pass = NULL;
HashTable *ht;
if (cfg_get_string("xcache.admin.user", &admin_user) == FAILURE || !admin_user[0]) {
admin_user = NULL;
}
if (cfg_get_string("xcache.admin.pass", &admin_pass) == FAILURE || !admin_pass[0]) {
admin_pass = NULL;
}
if (admin_user == NULL || admin_pass == NULL) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "xcache.admin.user and xcache.admin.pass is required");
zend_bailout();
}
if (strlen(admin_pass) != 32) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "unexpect %d bytes of xcache.admin.pass, expected 32 bytes, the password after md5()", strlen(admin_pass));
zend_bailout();
}
if (zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **) &server) != SUCCESS || Z_TYPE_PP(server) != IS_ARRAY) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "_SERVER is corrupted");
zend_bailout();
}
ht = HASH_OF((*server));
if (zend_hash_find(ht, "PHP_AUTH_USER", sizeof("PHP_AUTH_USER"), (void **) &user) == FAILURE) {
user = NULL;
}
else if (Z_TYPE_PP(user) != IS_STRING) {
user = NULL;
}
if (zend_hash_find(ht, "PHP_AUTH_PW", sizeof("PHP_AUTH_PW"), (void **) &pass) == FAILURE) {
pass = NULL;
}
else if (Z_TYPE_PP(pass) != IS_STRING) {
pass = NULL;
}
if (user != NULL && pass != NULL && strcmp(admin_user, Z_STRVAL_PP(user)) == 0) {
PHP_MD5_CTX context;
char md5str[33];
unsigned char digest[16];
PHP_MD5Init(&context);
PHP_MD5Update(&context, Z_STRVAL_PP(pass), Z_STRLEN_PP(pass));
PHP_MD5Final(digest, &context);
md5str[0] = '\0';
make_digest(md5str, digest);
if (strcmp(admin_pass, md5str) == 0) {
return 1;
}
}
#define STR "WWW-authenticate: basic realm='XCache Administration'"
sapi_add_header_ex(STR, sizeof(STR) - 1, 1, 1 TSRMLS_CC);
#undef STR
#define STR "HTTP/1.0 401 Unauthorized"
sapi_add_header_ex(STR, sizeof(STR) - 1, 1, 1 TSRMLS_CC);
#undef STR
ZEND_PUTS("XCache Auth Failed. User and Password is case sense\n");
zend_bailout();
return 0;
}
/* }}} */
/* {{{ xcache_admin_operate */
typedef enum { XC_OP_COUNT, XC_OP_INFO, XC_OP_LIST, XC_OP_CLEAR } xcache_op_type;
static void xcache_op(xcache_op_type optype, INTERNAL_FUNCTION_PARAMETERS)
static void xcache_admin_operate(xcache_op_type optype, INTERNAL_FUNCTION_PARAMETERS)
{
long type;
int size;
@ -1061,6 +1135,8 @@ static void xcache_op(xcache_op_type optype, INTERNAL_FUNCTION_PARAMETERS)
RETURN_FALSE;
}
xcache_admin_auth_check(TSRMLS_C);
if (optype == XC_OP_COUNT) {
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &type) == FAILURE) {
return;
@ -1142,28 +1218,28 @@ static void xcache_op(xcache_op_type optype, INTERNAL_FUNCTION_PARAMETERS)
Return count of cache on specified cache type */
PHP_FUNCTION(xcache_count)
{
xcache_op(XC_OP_COUNT, INTERNAL_FUNCTION_PARAM_PASSTHRU);
xcache_admin_operate(XC_OP_COUNT, INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
/* }}} */
/* {{{ proto array xcache_info(int type, int id)
Get cache info by id on specified cache type */
PHP_FUNCTION(xcache_info)
{
xcache_op(XC_OP_INFO, INTERNAL_FUNCTION_PARAM_PASSTHRU);
xcache_admin_operate(XC_OP_INFO, INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
/* }}} */
/* {{{ proto array xcache_list(int type, int id)
Get cache entries list by id on specified cache type */
PHP_FUNCTION(xcache_list)
{
xcache_op(XC_OP_LIST, INTERNAL_FUNCTION_PARAM_PASSTHRU);
xcache_admin_operate(XC_OP_LIST, INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
/* }}} */
/* {{{ proto array xcache_clear_cache(int type, int id)
Clear cache by id on specified cache type */
PHP_FUNCTION(xcache_clear_cache)
{
xcache_op(XC_OP_CLEAR, INTERNAL_FUNCTION_PARAM_PASSTHRU);
xcache_admin_operate(XC_OP_CLEAR, INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
/* }}} */

5
xcache.ini

@ -7,6 +7,11 @@ extension = xcache.so
; required for >=php5.1 if you turn xcache on
auto_globals_jit = Off
[xcache.admin]
xcache.admin.user = "mOo"
; xcache.admin.pass = md5($your_password)
xcache.admin.pass = ""
[xcache]
; ini only settings, all the values here is default
; to disable: xcache.size=0

Loading…
Cancel
Save