File: //opt/apache-top.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# apache-top
# Copyright (C) 2006 Carles Amigó
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
from HTMLParser import HTMLParser
import operator
import sys
import urllib
import curses
import traceback
import getopt
import time
class ApacheStatusParser(HTMLParser):
"""
Clase que parseja la sortida del handler server-status de apache
"""
performance_info = 2
scoreboard = 3
proceses = 4
status = 0
store = False # defineix si el contingut s'ha de guardar o no
append = False # defineix si els seguents caracters s'han d'afegir o posar en un altre camp
performance_info_data = []
scoreboard_data = []
proceses_data = []
def __init__(self):
HTMLParser.__init__(self)
self.performance_info_data = []
self.scoreboard_data = []
self.proceses_data = []
self.store = False
self.append = False
self.status = 1
def handle_starttag(self, tag, attrs):
if tag == "b":
return
self.store = False
if self.status <= self.performance_info:
if tag == "dt":
self.store = True
elif self.status <= self.scoreboard:
if tag == "pre":
self.store = True
elif self.status <= self.proceses:
if tag == "tr":
#if len(self.proceses_data[-1]) != 0:
if len (self.proceses_data) == 0:
self.proceses_data.append([])
else:
if len(self.proceses_data[-1]) > 0:
self.proceses_data.append([])
elif tag == "td":
self.store = True
def handle_endtag(self, tag):
if tag == "b":
return
self.store = False
self.append = False
if self.status <= self.performance_info and tag == "dl":
self.status += 1
elif self.status <= self.scoreboard and tag == "pre":
self.status += 1
elif self.status <= self.proceses and tag == "table":
self.status += 1
def handle_data(self,data):
if self.store and data != "\n":
if self.status <= self.performance_info:
self.performance_info_data.append(data.replace("\n",""))
elif self.status <= self.scoreboard:
self.scoreboard_data.append(data.replace("\n",""))
elif self.status <= self.proceses:
if not self.append:
self.proceses_data[-1].append(data.replace("\n",""))
else:
self.proceses_data[-1][-1] += data.replace("\n","")
def handle_charref(self, ref):
self.append = True
self.handle_data("&#%s;" % ref)
def handle_entityref(self, ref):
self.append = True
self.handle_data("&%s;" % ref)
def eval_data(self):
for process in self.proceses_data:
# PID
try:
process[1] = eval(process[1])
except:
process[1] = 0
# Acc Number of accesses this connection / this child / this slot
process[2] = process[2].split("/")
process[2][0] = eval(process[2][0])
process[2][1] = eval(process[2][1])
process[2][2] = eval(process[2][2])
# M Mode of operation
#pass
# CPU CPU usage, number of seconds
process[4] = eval(process[4])
# SS Seconds since beginning of most recent request
process[5] = eval(process[5])
# Req Milliseconds required to process most recent request
process[6] = eval(process[6])
# Conn Kilobytes transferred this connection
process[7] = eval(process[7])
# Child Megabytes transferred this child
process[8] = eval(process[8])
# Slot Total megabytes transferred this slot
process[9] = eval(process[9])
def usage(exit = 1):
print main.__doc__
sys.exit(exit)
def print_screen(screen, url, show_scoreboard):
screen = stdscr.subwin(0, 0)
screen.nodelay(1)
end = False
sort = 5
message = ""
reverse = True
show_only_active = True
y = 0
c = ""
while not end:
try:
data = ApacheStatusParser()
statusdata = urllib.urlopen(url).read()
data.feed(statusdata)
data.eval_data()
#width = curses.tigetnum('cols') or 80
#height = curses.tigetnum('lines') or 24
(height, width) = screen.getmaxyx()
screen.clear()
# imprimim el header
screen.addstr(0,0,data.performance_info_data[5].replace("Server uptime: ","Uptime:").replace(" days","d").replace(" day","d").replace(" hours","h").replace(" hour","h").replace(" minutes","m").replace(" minute","m").replace(" seconds","s").replace("second","s") + ", " + data.performance_info_data[3])
screen.addstr(1,0,data.performance_info_data[7])
screen.addstr(2,0,data.performance_info_data[8].replace("request","req").replace("second","sec") + ", Active/Idle: " + data.performance_info_data[9].split()[0] + "/" + data.performance_info_data[9].split()[5])
# evaluar scoreboard
if show_scoreboard:
y = 6
screen.addstr(3,0," +-------------------------Scoreboard Key-------------------------+")
screen.addstr(4,0," | _ Waiting for Connection, S Starting up, R Reading Request |")
screen.addstr(5,0," | W Sending Reply, K Keepalive (read), D DNS Lookup |")
screen.addstr(6,0," | C Closing connection, L Logging, G Gracefully finishing |")
screen.addstr(7,0," | I Idle cleanup of worker, . Open slot with no current process |")
screen.addstr(8,0," +----------------------------------------------------------------+")
# imprimim el scoreboard
for num in range(0,len(data.scoreboard_data[0]),width):
screen.addstr(y+4+num/width,0, data.scoreboard_data[0][num:num+width])
if len(message) > 0:
screen.addstr(y+5+num/width,0,message, curses.A_BOLD | curses.A_REVERSE)
message = ""
print_proceses(y+6+num/width,0,screen, data.proceses_data, columns=[ 1, 3, 5, 4, 11, 10, 12 ], sort=sort, reverse=reverse, width=width, show_only_active=show_only_active )
#screen.hline(2, 1, curses.ACS_HLINE, 77)
screen.refresh()
time.sleep(2)
try:
c = screen.getkey()
except:
pass
if c == "q":
# sortir
end = True
elif c == "P":
# ordenar per PID
sort = 1
message = "Sort by PID"
elif c == "C":
# ordenar per cpu
sort = 4
message = "Sort by CPU usage"
elif c == "S":
# ordenar per SS"
sort = 5
message = "Sort by Seconds since beginning of most recent request"
elif c == "V":
# ordenar per vhost
sort = 11
message = "Sort by VirtualHost"
elif c == "M":
# ordenar per Mode of operation
sort = 3
message = "Sort by Mode of operation"
elif c == "R":
# ordenar per request
sort = 12
message = "Sort by Request"
elif c == "I":
# ordenar per ip
sort = 10
message = "Sort by IP"
elif c == "s":
# mostrar scoreboard"
if show_scoreboard == 0:
show_scoreboard = 1
message = "Showing mod_status scoreboard"
else:
show_scoreboard = 0
message = "Hiding mod_status scoreboard"
y = 0
elif c == "a":
# mostra els actius
if show_only_active:
show_only_active = False
message = "Show all processes"
else:
show_only_active = True
message = "Show only active processes"
elif c == "r":
# cambiar l'ordre
if reverse:
reverse = False
message = "Reversed sorting"
else:
reverse = True
message = "Normal sorting"
c = ""
except IndexError:
raise
except:
pass
def print_proceses(y,x,screen, proceses, columns, sort, reverse, width, show_only_active = True):
header = "PID M SS CPU VHost IP Request"
screen.addstr(y,x,header + " "*(width-len(header)), curses.A_REVERSE)
n = 1
if sort != None:
for process in sorted(proceses, key=operator.itemgetter(sort), reverse=reverse):
n += print_process(y+n,x,screen,process,columns,show_only_active,width)
else:
for process in proceses:
n += print_process(y+n,x,screen,process,columns,show_only_active,width)
try:
screen.addstr(y+n,x, " "*width)
except:
pass
def print_process(y,x,screen,process,columns,show_only_active,width):
if not show_only_active or (process[3] != "." and process[3] != "_"):
try:
screen.addstr(y,x, " "*width)
n = x;
screen.addstr(y,n, str(process[columns[0]])) # SS
n = n+ 6
screen.addstr(y,n, process[columns[1]]) # M
n = n+ 2
screen.addstr(y,n, str(process[columns[2]])) # PID
n = n+ 6
cpu = str(process[columns[3]])
if len(cpu.split('.')[1]) < 2:
cpu = cpu + "0"*(2-len(cpu.split('.')[1]))
screen.addstr(y,n+(4-len(cpu)), cpu) # CPU
n = n+ 6
screen.addstr(y,n, str(process[columns[4]])) # VHOST
n = n+ 16
screen.addstr(y,n, str(process[columns[5]])) # IP
n = n+ 15
screen.addstr(y,n, " " + str(process[columns[6]])) # REQUEST
return 1
except:
return 1
else:
return 0
def main(url, stdscr, show_scoreboard):
"""Shows the actual status of the Apache web server using the server-status
url. It needs the ExtendedStatus flag
Usage: apache-top [-s] -u url
-u url Url where apache-status is located
Example: apache-top.py -u http://www.domain.com/server-status
-s Show scoreboard
Interactive keys:
q Exit
P Sort by PID
C Sort by CPU usage
S Sort by Seconds since beginning of most recent request
V Sort by VirtualHost
M Sort by Mopde of operation
R Sort by Request
I Sort by Ip
s Show/Hide mod_status scoreboard
a Switch between show all processes and show only active processes (default)
r Reverse sort
"""
cols = {
"srv": 0,
"pid": 1,
"acc": 2,
"m": 3,
"cpu": 4,
"ss": 5,
"req": 6,
"conn": 7,
"child": 8,
"slot": 9,
"client": 10,
"vhost": 11,
"request": 12
}
try:
print_screen(stdscr,url,show_scoreboard)
except:
raise
if __name__ == "__main__":
url = None
show_scoreboard = False
try:
opt_list = getopt.getopt(sys.argv[1:], "u:h:s")
except:
usage()
for opt in opt_list[0]:
if opt[0]=="-h":
usage(0)
elif opt[0]=="-s":
show_scoreboard = True
elif opt[0]=="-u":
url = opt[1]
else:
usage
if url == None:
print "*** ERROR: Url missing\n"
usage()
try:
# Initialize curses
stdscr=curses.initscr()
# Turn off echoing of keys, and enter cbreak mode,
# where no buffering is performed on keyboard input
curses.noecho()
curses.cbreak()
curses.curs_set(0)
# In keypad mode, escape sequences for special keys
# (like the cursor keys) will be interpreted and
# a special value like curses.KEY_LEFT will be returned
stdscr.keypad(1)
try:
main(url,stdscr,show_scoreboard) # Enter the main loop
except:
raise
# Set everything back to normal
curses.curs_set(1)
stdscr.keypad(0)
curses.echo()
curses.nocbreak()
curses.endwin() # Terminate curses
except:
# In event of error, restore terminal to sane state.
stdscr.keypad(0)
curses.echo()
curses.nocbreak()
curses.endwin()
#traceback.print_exc() # Print the exception
print "ERROR parsing the data. Please, make sure you are alowed to read the server-status page and you have ExtendedStatus flag activated"