Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
]]
local ssversion = "0.11" local redact_ips = true local warning_banner = [[
<div style="float: left; color: #222; margin-bottom: 8px; margin-top: 24px; text-align: center; width: 200px; font-size: 0.7rem; border: 1px dashed #333; background: #F8C940;">
<h3 style="margin: 4px; font-size: 1rem;">Don't be alarmed - this page is here for a reason!</h3>
<p style="font-weight: bolder; font-size: 0.8rem;">This is an example server status page for the Apache HTTP Server. Nothing on this server is secret, no URL tokens, no sensitive passwords. Everything served from here is static data.</p>
</div>
]]
local show_warning = true local show_modules = false local show_threads = true
local status_js, status_css, quokka_js
local function quickJSON(input)
if type(input) == "table" then
local t = 'array'
for k, v in pairs(input) do
if type(k) ~= "number" then
t = 'hash'
break
end
end
if t == 'hash' then
local out = ""
local tbl = {}
for k, v in pairs(input) do
local kv = ([["%s": %s]]):format(k, quickJSON(v))
table.insert(tbl, kv)
end
return "{" .. table.concat(tbl, ", ") .. "}"
else
local tbl = {}
for k, v in pairs(input) do
table.insert(tbl, quickJSON(v))
end
return "[" .. table.concat(tbl, ", ") .. "]"
end
elseif type(input) == "string" then
return ([["%s"]]):format(input:gsub('"', '\\"'):gsub("[\r\n\t]", " "))
elseif type(input) == "number" then
return tostring(input)
elseif type(input) == "boolean" then
return (input and "true" or "false")
else
return "null"
end
end
-- Module information callback
local function modInfo(r, modname)
if modname then
r:puts [[
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
]]
r:puts (status_css)
r:puts [[
</style>
<title>Module information</title>
</head>
<body>
]]
r:puts( ("<h3>Details for module %s</h3>\n"):format(r:escape_html(modname)) )
-- Queries the server for information about a module
local mod = r.module_info(modname)
if mod then
for k, v in pairs(mod.commands) do
-- print out all directives accepted by this module
r:puts( ("<b>%s:</b> %s<br>\n"):format(r:escape_html(k), v))
end
end
-- HTML tail
r:puts[[
</body>
</html>
]]
end
end
-- Function for generating server stats
function getServerState(r, verbose)
local state = {}
state.mpm = {
type = "prefork", -- default to prefork until told otherwise
threadsPerChild = 1,
threaded = false,
maxServers = r.mpm_query(12),
activeServers = 0
}
if r.mpm_query(14) == 1 then
state.mpm.type = "event" -- this is event mpm
elseif r.mpm_query(3) >= 1 then
state.mpm.type = "worker" -- it's not event, but it's threaded, we'll assume worker mpm (could be motorz??)
elseif r.mpm_query(2) == 1 then
state.mpm.type = "winnt" -- it's threaded, but not worker nor event, so it's probably winnt
end
if state.mpm.type ~= "prefork" then
state.mpm.threaded = true -- it's threaded
state.mpm.threadsPerChild = r.mpm_query(6) -- get threads per child proc
end
state.processes = {} -- list of child procs
state.connections = { -- overall connection info
idle = 0,
active = 0
}
-- overall server stats
state.server = {
connections = 0,
bytes = 0,
built = r.server_built,
localtime = os.time(),
uptime = os.time() - r.started,
version = r.banner,
host = r.server_name,
modules = nil,
extended = show_threads, -- whether extended status is available or not
}
-- if show_modules is true, add list of modules to the JSON
if show_modules then
state.server.modules = {}
for k, module in pairs(r:loaded_modules()) do
table.insert(state.server.modules, module)
end
end
-- Fetch process/thread data
for i=0,state.mpm.maxServers-1,1 do
local server = r.scoreboard_process(r, i);
if server then
local s = {
active = false,
pid = nil,
bytes = 0,
stime = 0,
utime = 0,
connections = 0,
}
local tstates = {}
if server.pid then
state.connections.idle = state.connections.idle + (server.keepalive or 0)
s.connections = 0
if server.pid > 0 then
state.mpm.activeServers = state.mpm.activeServers + 1
s.active = true
s.pid = server.pid
end
for j = 0, state.mpm.threadsPerChild-1, 1 do
local worker = r.scoreboard_worker(r, i, j)
if worker then
s.stime = s.stime + (worker.stimes or 0);
s.utime = s.utime + (worker.utimes or 0);
if verbose and show_threads then
s.threads = s.threads or {}
table.insert(s.threads, {
bytes = worker.bytes_served,
thread = ("0x%x"):format(worker.tid),
client = redact_ips and (worker.client or "???"):gsub("[a-f0-9]+[.:]+[a-f0-9]+$", "x.x") or worker.client or "???",
cost = ((worker.utimes or 0) + (worker.stimes or 0)),
count = worker.access_count,
vhost = worker.vhost:gsub(":%d+", ""),
request = worker.request,
last_used = math.floor(worker.last_used/1000000)
})
end
state.server.connections = state.server.connections + worker.access_count
s.bytes = s.bytes + worker.bytes_served
s.connections = s.connections + worker.access_count
if server.pid > 0 then
tstates[worker.status] = (tstates[worker.status] or 0) + 1
end
end
end
end
s.workerStates = {
keepalive = (server.keepalive > 0) and server.keepalive or tstates[5] or 0,
closing = tstates[8] or 0,
idle = tstates[2] or 0,
writing = tstates[4] or 0,
reading = tstates[3] or 0,
graceful = tstates[9] or 0
}
table.insert(state.processes, s)
state.server.bytes = state.server.bytes + s.bytes
state.connections.active = state.connections.active + (tstates[8] or 0) + (tstates[4] or 0) + (tstates[3] or 0)
end
end
return state
end
-- Handler function
function handle(r)
-- Parse GET data, if any, and set content type
local GET = r:parseargs()
if GET['module'] then
modInfo(r, GET['module'])
return apache2.OK
end
-- If we only need the stats feed, compact it and hand it over
if GET['view'] and GET['view'] == "json" then
local state = getServerState(r, GET['extended'] == 'true')
r.content_type = "application/json"
r:puts(quickJSON(state))
return apache2.OK
end
if not GET['resource'] then
local state = getServerState(r, show_threads)
-- Print out the HTML for the front page
r.content_type = "text/html"
r:puts ( ([=[
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- Stylesheet -->
<link href="?resource=css" rel="stylesheet">
<!-- JavaScript-->
<script type="text/javascript" src="?resource=js"></script>
<title>Server status for %s</title>
</head>
<body onload="refreshCharts(false);">
<div class="wrapper" id="wrapper">
<div class="navbarLeft">
<img align='absmiddle' src='?resource=feather' width="15" height="30"/>
Apache HTTPd
</div>
<div class="navbarRight">Status for %s on %s</div>
<div style="clear: both;"></div>
<div class="serverinfo" id="leftpane">
<ul id="menubar">
<li>
<a class="btn active" id="dashboard_button" href="javascript:void(showPanel('dashboard'));">Dashboard</a>
</li>
<li>
<a class="btn" id="misc_button" href="javascript:void(showPanel('misc'));">Server Info</a>
</li>
<li>
<a class="btn" id="threads_button" style="display: none;" href="javascript:void(showPanel('threads'));">Show thread information</a>
</li>
<li>
<a class="btn" id="modules_button" style="display: none;" href="javascript:void(showPanel('modules'));">Show loaded modules</a>
</li>
</ul>
<!-- warning --> %s <!-- /warning -->
</div>
<!-- dashboard -->
<div class="charts" id="dashboard_panel">
<div class="infobox_wrapper" style="clear: both; width: 100%%;">
<div class="infobox_title">Quick Stats</div>
<div class="infobox" id="general_stats">
</div>
</div>
<div class="infobox_wrapper" style="width: 100%%;">
<div class="infobox_title">Charts</div>
<div class="infobox">
<!--Div that will hold the pie chart-->
<canvas id="actions_div" width="1400" height="400" class="canvas_wide"></canvas>
<canvas id="status_div" width=580" height="400" class="canvas_narrow"></canvas>
<canvas id="traffic_div" width="1400" height="400" class="canvas_wide"></canvas>
<canvas id="idle_div" width="580" height="400" class="canvas_narrow"></canvas>
<canvas id="connection_div" width="1400" height="400" class="canvas_wide"></canvas>
<canvas id="cpu_div" width="580" height="400" class="canvas_narrow"></canvas>
<div style="clear: both"></div>
</div>
</div>
</div>
<! <div class="charts" id="misc_panel" style="display: none;">
<div class="infobox_wrapper" style="clear: both; width: 100%%;">
<div class="infobox_title">General server information</div>
<div class="infobox" style='padding: 16px; width: calc(100%% - 32px);' id="server_breakdown">
</div>
</div>
</div>
<! <div class="charts" id="threads_panel" style="display: none;">
<div class="infobox_wrapper" style="clear: both; width: 100%%;">
<div class="infobox_title">Thread breakdown</div>
<div class="infobox" style='padding: 16px; width: calc(100%% - 32px);' id="threads_breakdown">
</div>
</div>
</div>
<! <div class="charts" id="modules_panel" style="display: none;">
<div class="infobox_wrapper" style="clear: both; width: 100%%;">
<div class="infobox_title">Modules loaded</div>
<div class="infobox" style='padding: 16px; width: calc(100%% - 32px);' id="modules_breakdown">
blabla
</div>
</div>
</div>
</div>
]=]):format(
r.server_name,
r.banner,
r.server_name,
show_warning and warning_banner or ""
) );
r:puts[[
</body>
</html>
]]
else
if GET['resource'] == 'js' then
r.content_type = "application/javascript"
r:puts(quokka_js)
r:puts(status_js)
elseif GET['resource'] == 'css' then
r.content_type = "text/css"
r:puts(status_css)
elseif GET['resource'] == 'feather' then
r.content_type = "image/png"
r:write(r:base64_decode('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'))
end
end
return apache2.OK;
end
status_js = [==[
Number.prototype.pad = function(size) {
var str = String(this);
while (str.length < size) {
str = "0" + str;
}
return str;
}
function getAsync(theUrl, xstate, callback) {
var xmlHttp = null;
if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest();
} else {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlHttp.open("GET", theUrl, true);
xmlHttp.send(null);
xmlHttp.onreadystatechange = function(state) {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
if (callback) {
callback(JSON.parse(xmlHttp.responseText));
}
}
}
}
var actionCache = [];
var connectionCache = [];
var trafficCache = [];
var processes = {};
var lastBytes = 0;
var lastConnections = 0;
var negativeBytes = 0; // cache for proc reloads, which skews traffic
var updateSpeed = 5; // How fast do charts update?
var maxRecords = 24; // How many records to show per chart
var cpumax = 1000000; // random cpu max(?)
function refreshCharts(json, state) {
if (json && json.processes) {
// general server info box
var gs = document.getElementById('server_breakdown');
gs.innerHTML = "";
gs.innerHTML += "<b>Server version: </b>" + json.server.version + "<br/>";
gs.innerHTML += "<b>Server built: </b>" + json.server.built + "<br/>";
gs.innerHTML += "<b>Server MPM: </b>" + json.mpm.type + " <span id='mpminfo'></span><br/>";
// Get a timestamp
var now = new Date();
var ts = now.getHours().pad(2) + ":" + now.getMinutes().pad(2) + ":" + now.getSeconds().pad(2);
var utime = 0;
var stime = 0;
// Construct state based on proc details
var state = {
timestamp: ts,
closing: 0,
idle: 0,
writing: 0,
reading: 0,
keepalive: 0,
graceful: 0
}
for (var i in json.processes) {
var proc = json.processes[i];
if (proc.pid) {
state.closing += proc.workerStates.closing||0;
state.idle += proc.workerStates.idle||0;
state.writing += proc.workerStates.writing||0;
state.reading += proc.workerStates.reading||0;
state.keepalive += proc.workerStates.keepalive||0;
state.graceful += proc.workerStates.graceful||0;
utime += proc.utime;
stime += proc.stime;
}
}
// Push action state entry into action cache with timestamp
// Shift if more than 10 entries in cache
actionCache.push(state);
if (actionCache.length > maxRecords) {
actionCache.shift();
}
// construct array for QuokkaLines
var arr = [];
for (var i in actionCache) {
var el = actionCache[i];
if (json.mpm.type == 'event') {
arr.push([el.timestamp, el.closing, el.idle, el.writing, el.reading, el.graceful]);
} else {
arr.push([el.timestamp, el.keepalive, el.closing, el.idle, el.writing, el.reading, el.graceful]);
}
}
var states = ['Keepalive', 'Closing', 'Idle', 'Writing', 'Reading', 'Graceful']
if (json.mpm.type == 'event') {
states.shift();
if (document.getElementById('mpminfo')) {
document.getElementById('mpminfo').innerHTML = "(" + fn(parseInt(json.connections.idle)) + " connections in idle keepalive)";
}
}
// Draw action chart
quokkaLines("actions_div", states, arr, { lastsum: true, hires: true, nosum: true, stack: true, curve: true, title: "Thread states" } );
// Get traffic, figure out how much it was this time (0 if just started!)
var bytesThisTurn = 0;
var connectionsThisTurn = 0;
for (var i in json.processes) {
var proc = json.processes[i];
var pid = proc.pid
// if we haven't seen this proc before, ignore its bytes first time
if (!processes[pid]) {
processes[pid] = {
bytes: proc.bytes,
connections: proc.connections,
}
} else {
bytesThisTurn += proc.bytes - processes[pid].bytes;
if (pid) {
x = proc.connections - processes[pid].connections;
connectionsThisTurn += (x > 0) ? x : 0;
}
processes[pid].bytes = proc.bytes;
processes[pid].connections = proc.connections;
}
}
if (lastBytes == 0 ) {
bytesThisTurn = 0;
}
lastBytes = 1;
// Push a new element into cache, prune cache
var el = {
timestamp: ts,
bytes: bytesThisTurn/updateSpeed
};
trafficCache.push(el);
if (trafficCache.length > maxRecords) {
trafficCache.shift();
}
// construct array for QuokkaLines
arr = [];
for (var i in trafficCache) {
var el = trafficCache[i];
arr.push([el.timestamp, el.bytes]);
}
// Draw action chart
quokkaLines("traffic_div", ['Traffic'], arr, { traffic: true, hires: true, nosum: true, stack: true, curve: true, title: "Traffic per second" } );
// Get connections per second
// Push a new element into cache, prune cache
var el = {
timestamp: ts,
connections: (connectionsThisTurn+1)/updateSpeed
};
connectionCache.push(el);
if (connectionCache.length > maxRecords) {
connectionCache.shift();
}
// construct array for QuokkaLines
arr = [];
for (var i in connectionCache) {
var el = connectionCache[i];
arr.push([el.timestamp, el.connections]);
}
// Draw connection chart
quokkaLines("connection_div", ['Connections/sec'], arr, { traffic: false, hires: true, nosum: true, stack: true, curve: true, title: "Connections per second" } );
// Thread info
quokkaCircle("status_div", [
{ title: 'Active', value: (json.mpm.threadsPerChild*json.mpm.activeServers)},
{ title: 'Reserve', value: (json.mpm.threadsPerChild*(json.mpm.activeServers-json.mpm.maxServers))}
],
{ title: "Worker pool", hires: true});
// Idle vs active connections
var idlecons = json.connections.idle;
var activecons = json.connections.active;
quokkaCircle("idle_div", [
{ title: 'Idle', value: idlecons},
{ title: 'Active', value: activecons},
],
{ hires: true, title: "Idle vs active connections"});
// CPU info
while ( (stime+utime) > cpumax ) {
cpumax = cpumax * 2;
}
quokkaCircle("cpu_div", [
{ title: 'Idle', value: (cpumax - stime - utime) / (cpumax/100)},
{ title: 'System', value: stime/(cpumax/100)},
{ title: 'User', value: utime/(cpumax/100)}
],
{ hires: true, title: "CPU usage", pct: true});
// General stats infobox
var gstats = document.getElementById('general_stats');
gstats.innerHTML = ''; // wipe the box
// Days since restart
var u_f = Math.floor(json.server.uptime/8640.0) / 10;
var u_d = Math.floor(json.server.uptime/86400);
var u_h = Math.floor((json.server.uptime%86400)/3600);
var u_m = Math.floor((json.server.uptime%3600)/60);
var u_s = Math.floor(json.server.uptime %60);
var str = u_d + " day" + (u_d != 1 ? "s, " : ", ") + u_h + " hour" + (u_h != 1 ? "s, " : ", ") + u_m + " minute" + (u_m != 1 ? "s" : "");
var ubox = document.createElement('div');
ubox.setAttribute("class", "statsbox");
ubox.innerHTML = "<span style='font-size: 2rem;'>" + u_f + " days</span><br/><i>since last (re)start.</i><br/><small>" + str;
gstats.appendChild(ubox);
// Bytes transferred in total
var MB = fnmb(json.server.bytes);
var KB = (json.server.bytes > 0) ? fnmb(json.server.bytes/json.server.connections) : 0;
var KBs = fnmb(json.server.bytes/json.server.uptime);
var mbbox = document.createElement('div');
mbbox.setAttribute("class", "statsbox");
mbbox.innerHTML = "<span style='font-size: 2rem;'>" + MB + "</span><br/><i>transferred in total.</i><br/><small>" + KBs + "/sec, " + KB + "/request";
gstats.appendChild(mbbox);
// connections in total
var cons = fn(json.server.connections);
var cps = Math.floor(json.server.connections/json.server.uptime*100)/100;
var conbox = document.createElement('div');
conbox.setAttribute("class", "statsbox");
conbox.innerHTML = "<span style='font-size: 2rem;'>" + cons + " conns</span><br/><i>since server started.</i><br/><small>" + cps + " requests per second";
gstats.appendChild(conbox);
// threads working
var tpc = json.mpm.threadsPerChild;
var activeThreads = fn(json.mpm.activeServers * json.mpm.threadsPerChild);
var maxThreads = json.mpm.maxServers * json.mpm.threadsPerChild;
var tbox = document.createElement('div');
tbox.setAttribute("class", "statsbox");
tbox.innerHTML = "<span style='font-size: 2rem;'>" + activeThreads + " threads</span><br/><i>currently at work (" + json.mpm.activeServers + "x" + tpc+" threads).</i><br/><small>" + maxThreads + " (" + json.mpm.maxServers + "x"+tpc+") threads allowed.";
gstats.appendChild(tbox);
window.setTimeout(waitTwo, updateSpeed*1000);
// resize pane
document.getElementById('leftpane').style.height = document.getElementById('wrapper').getBoundingClientRect().height + "px";
// Do we have extended info and module lists??
if (json.server.extended) document.getElementById('threads_button').style.display = 'block';
if (json.server.modules && json.server.modules.length > 0) {
var panel = document.getElementById('modules_breakdown');
var list = "<ul>";
for (var i in json.server.modules) {
var mod = json.server.modules[i];
list += "<li>" + mod + "</li>";
}
list += "</ul>";
panel.innerHTML = list;
document.getElementById('modules_button').style.display = 'block';
}
} else if (json === false) {
waitTwo();
}
}
function refreshThreads(json, state) {
var box = document.getElementById('threads_breakdown');
box.innerHTML = "";
for (var i in json.processes) {
var proc = json.processes[i];
var phtml = '<div style="color: #DDF">';
if (!proc.active) phtml = '<div title="this process is inactive" style="color: #999;">';
phtml += "<h3>Process " + i + ":</h3>";
phtml += "<b>PID:</b> " + (proc.pid||"None (not active)") + "<br/>";
if (proc.threads && proc.active) {
phtml += "<table style='width: 800px; color: #000;'><tr><th>Thread ID</th><th>Access count</th><th>Bytes served</th><th>Last Used</th><th>Last client</th><th>Last request</th></tr>";
for (var j in proc.threads) {
var thread = proc.threads[j];
thread.request = (thread.request||"(Unknown)").replace(/[<>]+/g, "");
phtml += "<tr><td>"+thread.thread+"</td><td>"+thread.count+"</td><td>"+thread.bytes+"</td><td>"+thread.last_used+"</td><td>"+thread.client+"</td><td>"+thread.request+"</td></tr>";
}
phtml += "</table>";
} else {
phtml += "<p>No thread information avaialable</p>";
}
phtml += "</div>";
box.innerHTML += phtml;
}
}
function waitTwo() {
getAsync(location.href + "?view=json&rnd=" + Math.random(), null, refreshCharts)
}
function showPanel(what) {
var items = ['dashboard','misc','threads','modules'];
for (var i in items) {
var item = items[i];
var btn = document.getElementById(item+'_button');
var panel = document.getElementById(item+'_panel');
if (item == what) {
btn.setAttribute("class", "btn active");
panel.style.display = 'block';
} else {
btn.setAttribute("class", "btn");
panel.style.display = 'none';
}
}
// special constructors
if (what == 'threads') {
getAsync(location.href + "?view=json&extended=true&rnd=" + Math.random(), null, refreshThreads)
}
}
function fn(num) {
num = num + "";
num = num.replace(/(\d)(\d{9})$/, '$1,$2');
num = num.replace(/(\d)(\d{6})$/, '$1,$2');
num = num.replace(/(\d)(\d{3})$/, '$1,$2');
return num;
}
function fnmb(num) {
var add = "bytes";
var dec = "";
var mul = 1;
if (num > 1024) { add = "KB"; mul= 1024; }
if (num > (1024*1024)) { add = "MB"; mul= 1024*1024; }
if (num > (1024*1024*1024)) { add = "GB"; mul= 1024*1024*1024; }
if (num > (1024*1024*1024*1024)) { add = "TB"; mul= 1024*1024*1024*1024; }
num = num / mul;
if (add != "bytes") {
dec = "." + Math.floor( (num - Math.floor(num)) * 100 );
}
return ( fn(Math.floor(num)) + dec + " " + add );
}
function sort(a,b){
last_col = -1;
var sort_reverse = false;
var sortWay = a.getAttribute("sort_" + b);
if (sortWay && sortWay == "forward") {
a.setAttribute("sort_" + b, "reverse");
sort_reverse = true;
}
else {
a.setAttribute("sort_" + b, "forward");
}
var c,d,e,f,g,h,i;
c=a.rows.length;
if(c<1){ return; }
d=a.rows[1].cells.length;
e=1;
var j=new Array(c);
f=0;
for(h=e;h<c;h++){
var k=new Array(d);
for(i=0;i<d;i++){
cell_text="";
cell_text=a.rows[h].cells[i].textContent;
if(cell_text===undefined){cell_text=a.rows[h].cells[i].innerText;}
k[i]=cell_text;
}
j[f++]=k;
}
var l=false;
var m,n;
if(b!=lastcol) lastseq="A";
else{
if(lastseq=="A") lastseq="D";
lastseq="A";
}
g=c-1;
for(h=0;h<g;h++){
l=false;
for(i=0;i<g-1;i++){
m=j[i];
n=j[i+1];
if(lastseq=="A"){
var gt = (m[b]>n[b]) ? true : false;
var lt = (m[b]<n[b]) ? true : false;
if (n[b].match(/^(\d+)$/)) { gt = parseInt(m[b], 10) > parseInt(n[b], 10) ? true : false; lt = parseInt(m[b], 10) < parseInt(n[b], 10) ? true : false; }
if (sort_reverse) {gt = (!gt); lt = (!lt);}
if(gt){
j[i+1]=m;
j[i]=n;
l=true;
}
}
else{
if(lt){
j[i+1]=m;
j[i]=n;
l=true;
}
}
}
if(l===false){
break;
}
}
f=e;
for(h=0;h<g;h++){
m=j[h];
for(i=0;i<d;i++){
if(a.rows[f].cells[i].innerText!==undefined){
a.rows[f].cells[i].innerText=m[i];
}
else{
a.rows[f].cells[i].textContent=m[i];
}
}
f++;
}
lastcol=b;
}
var CPUmax = 1000000;
var showing = false;
function showDetails() {
for (i=1; i < 1000; i++) {
var obj = document.getElementById("srv_" + i);
if (obj) {
if (showing) { obj.style.display = "none"; }
else { obj.style.display = "block"; }
}
}
var link = document.getElementById("show_link");
showing = (!showing);
if (showing) { link.innerHTML = "Hide thread information"; }
else { link.innerHTML = "Show thread information"; }
}
var showing_modules = false;
function show_modules() {
var obj = document.getElementById("modules");
if (obj) {
if (showing_modules) { obj.style.display = "none"; }
else { obj.style.display = "block"; }
}
var link = document.getElementById("show_modules_link");
showing_modules = (!showing_modules);
if (showing_modules) { link.innerHTML = "Hide loaded modules"; }
else { link.innerHTML = "Show loaded modules"; }
}
]==]
quokka_js = [==[
/*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Traffic shaper
function quokka_fnmb(num) {
var add = "b";
var dec = "";
var mul = 1;
if (num > 1024) { add = "KB"; mul= 1024; }
if (num > (1024*1024)) { add = "MB"; mul= 1024*1024; }
if (num > (1024*1024*1024)) { add = "GB"; mul= 1024*1024*1024; }
if (num > (1024*1024*1024*1024)) { add = "TB"; mul= 1024*1024*1024*1024; }
num = num / mul;
if (add != "b" && num < 10) {
dec = "." + Math.floor( (num - Math.floor(num)) * 100 );
}
return ( Math.floor(num) + dec + " " + add );
}
// Hue, Saturation and Lightness to Red, Green and Blue:
function