Skip to content

Commit

Permalink
Initial Import
Browse files Browse the repository at this point in the history
  • Loading branch information
qasimakhan committed Jul 1, 2011
0 parents commit 420ffd0
Show file tree
Hide file tree
Showing 11 changed files with 298 additions and 0 deletions.
14 changes: 14 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
* -> Pending Tasks
# -> Completed Tasks

monasterisk-0.2a
# Create TODO
* Create README (Help + Installation)
* Make configuration file for variables
* Fix glitch in graph (Spike in graph when monasterisk is started/restarted)
* Make a nice HTML/PHP interface

monasterisk-0.1a
# Connectivity to askterisk through ami
# Create rrd database
# Create Graphs
Binary file added asterisk.rrd
Binary file not shown.
130 changes: 130 additions & 0 deletions daemon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env python

import sys, os, time, atexit
from signal import SIGTERM, SIGKILL

class Daemon:
"""
A generic daemon class.
Usage: subclass the Daemon class and override the run() method
"""
def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile

def daemonize(self):
"""
do the UNIX double-fork magic, see Stevens' "Advanced
Programming in the UNIX Environment" for details (ISBN 0201563177)
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
"""
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)

# decouple from parent environment
#os.chdir("/")
os.setsid()
os.umask(0)

# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError, e:
sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)

# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = file(self.stdin, 'r')
so = file(self.stdout, 'a+')
se = file(self.stderr, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())

# write pidfile
atexit.register(self.delpid)
pid = str(os.getpid())
file(self.pidfile,'w+').write("%s\n" % pid)

def delpid(self):
os.remove(self.pidfile)

def start(self):
"""
Start the daemon
"""
# Check for a pidfile to see if the daemon already runs
try:
pf = file(self.pidfile,'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None

if pid:
message = "pidfile %s already exist. Daemon already running?\n"
sys.stderr.write(message % self.pidfile)
sys.exit(1)

# Start the daemon
self.daemonize()
self.run()

def stop(self):
"""
Stop the daemon
"""
# Get the pid from the pidfile
try:
pf = file(self.pidfile,'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None

if not pid:
message = "pidfile %s does not exist. Daemon not running?\n"
sys.stderr.write(message % self.pidfile)
return # not an error in a restart

# Try killing the daemon process
try:
while 1:
#os.kill(pid, SIGTERM)
os.kill(pid, SIGKILL)
time.sleep(0.1)
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
print str(err)
sys.exit(1)

def restart(self):
"""
Restart the daemon
"""
self.stop()
self.start()

def run(self):
"""
You should override this method when you subclass Daemon. It will be called after the process has been
daemonized by start() or restart().
"""
Binary file added html/callstats.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added html/callstats_month.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added html/callstats_week.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added html/callstats_year.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added html/index.html
Empty file.
5 changes: 5 additions & 0 deletions makedb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env python

import rrdtool

rrdtool.create("asterisk.rrd","--step","300","--start","0","DS:calls:GAUGE:400:U:U","RRA:LAST:0.5:1:525600")
144 changes: 144 additions & 0 deletions monasterisk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#! /usr/bin/env python
"""Sample application to watch for PRI exhaustion
This script watches for events on the AMI interface, tracking the identity of
open channels in order to track how many channels are being used. This would
be used to send messages to an administrator when network capacity is being
approached.
Similarly, you could watch for spare capacity on the network and use that
to decide whether to allow low-priority calls, such as peering framework or
free-world-dialup calls to go through.
"""
from twisted.application import service, internet
from twisted.internet import reactor, defer
from starpy import manager, fastagi, utilapplication, menu
import sys, os, logging, pprint, time, rrdtool, socket
from basicproperty import common, propertied, basic
from daemon import Daemon

log = logging.getLogger( 'priexhaustion' )
log.setLevel( logging.INFO )

class ChannelTracker( propertied.Propertied ):
"""Track open channels on the Asterisk server"""
channels = common.DictionaryProperty(
"channels", """Set of open channels on the system""",
)
thresholdCount = common.IntegerProperty(
"thresholdCount", """Storage of threshold below which we don't warn user""",
defaultValue = 20,
)
hitsCount = common.IntegerProperty(
"thresholdCount", """Storage of threshold below which we don't warn user""",
defaultValue = 0,
)
def main( self ):
"""Main operation for the channel-tracking demo"""
amiDF = APPLICATION.amiSpecifier.login(
).addCallback( self.onAMIConnect )
# XXX do something useful on failure to login...
def onAMIConnect( self, ami ):
"""Register for AMI events"""
# XXX should do an initial query to populate channels...
# XXX should handle asterisk reboots (at the moment the AMI
# interface will just stop generating events), not a practical
# problem at the moment, but should have a periodic check to be sure
# the interface is still up, and if not, should close and restart
log.debug( 'onAMIConnect' )
ami.status().addCallback( self.onStatus, ami=ami )
ami.registerEvent( 'Hangup', self.onChannelHangup )
ami.registerEvent( 'Newchannel', self.onChannelNew )
def interestingEvent( self, event, ami=None ):
"""Decide whether this channel event is interesting
Real-world application would want to take only Zap channels, or only
channels from a given context, or whatever other filter you want in
order to capture *just* the scarce resource (such as PRI lines).
Keep in mind that an "interesting" event must show up as interesting
for *both* Newchannel and Hangup events or you will leak
references/channels or have unknown channels hanging up.
"""
return True
def onStatus( self, events, ami=None ):
"""Integrate the current status into our set of channels"""
log.debug( """Initial channel status retrieved""" )
for event in events:
self.onChannelNew( ami, event )
def onChannelNew( self, ami, event ):
"""Handle creation of a new channel"""
log.debug( """Start on channel %s""", event )
if self.interestingEvent( event, ami ):
self.hitsCount = self.hitsCount + 1
opening = not self.channels.has_key( event['uniqueid'] )
self.channels[ event['uniqueid'] ] = event
if opening:
self.onChannelChange( ami, event, opening = opening )
def onChannelHangup( self, ami, event ):
"""Handle hangup of an existing channel"""
if self.interestingEvent( event, ami ):
try:
del self.channels[ event['uniqueid']]
except KeyError, err:
log.warn( """Hangup on unknown channel %s""", event )
else:
log.debug( """Hangup on channel %s""", event )
self.onChannelChange( ami, event, opening = False )
def onChannelChange( self, ami, event, opening=False ):
"""Channel count has changed, do something useful like enforcing limits"""
if opening and len(self.channels) > self.thresholdCount:
log.warn( """Current channel count: %s""", len(self.channels ) )
else:
log.info( """Current channel count: %s""", len(self.channels ) )
def rrdGraph(x):
while 1:
print x.hitsCount
ret = rrdtool.update('asterisk.rrd', 'N:' + str(x.hitsCount))
# if ret:
# if (os.path.exists("asterisk.rrd")):
# os.remove("asterisk.rrd")
# rrdtool.create("asterisk.rrd","--step","300","--start","0","DS:calls:GAUGE:400:U:U","RRA:LAST:0.5:1:1440")
# else:
# rrdtool.create("asterisk.rrd","--step","300","--start","0","DS:calls:GAUGE:400:U:U","RRA:LAST:0.5:1:1440")
x.hitsCount = 0
ret = rrdtool.graph( "html/callstats.png", "--start", "-1d", "--vertical-label=Call Hits","--title=Host:"+socket.gethostname()+"\tDate: "+time.strftime("%a, %d %b %Y %H:%M", time.localtime()), "DEF:acalls=asterisk.rrd:calls:LAST", "AREA:acalls#00FF00:Hits Average Over 5 Min")
ret = rrdtool.graph( "html/callstats_week.png", "--start", "-7d", "--vertical-label=Call Hits","--title=Host:"+socket.gethostname()+"\tDate: "+time.strftime("%a, %d %b %Y %H:%M", time.localtime()), "DEF:acalls=asterisk.rrd:calls:LAST", "AREA:acalls#00FF00:Hits Average Over 5 Min")
ret = rrdtool.graph( "html/callstats_month.png", "--start", "-30d", "--vertical-label=Call Hits","--title=Host:"+socket.gethostname()+"\tDate: "+time.strftime("%a, %d %b %Y %H:%M", time.localtime()), "DEF:acalls=asterisk.rrd:calls:LAST", "AREA:acalls#00FF00:Hits Average Over 5 Min")
ret = rrdtool.graph( "html/callstats_year.png", "--start", "-365d", "--vertical-label=Call Hits","--title=Host:"+socket.gethostname()+"\tDate: "+time.strftime("%a, %d %b %Y %H:%M", time.localtime()), "DEF:acalls=asterisk.rrd:calls:LAST", "AREA:acalls#00FF00:Hits Average Over 5 Min")
time.sleep(300)

class MyDaemon(Daemon):
def run(self):
while True:
tracker = ChannelTracker()
reactor.callInThread( tracker.main )
reactor.callInThread( rrdGraph, tracker )
reactor.run()

APPLICATION = utilapplication.UtilApplication()

if __name__ == "__main__":
logging.basicConfig()
#log.setLevel( logging.DEBUG )
#manager.log.setLevel( logging.DEBUG )
#fastagi.log.setLevel( logging.DEBUG )
#tracker = ChannelTracker()
#reactor.callInThread( tracker.main )
#reactor.callInThread( rrdGraph, tracker )
#reactor.run()
daemon = MyDaemon('/tmp/daemon-example.pid')
if len(sys.argv) == 2:
if 'start' == sys.argv[1]:
daemon.start()
elif 'stop' == sys.argv[1]:
daemon.stop()
elif 'restart' == sys.argv[1]:
daemon.restart()
else:
print "Unknown command"
sys.exit(2)
sys.exit(0)
else:
print "usage: %s start|stop|restart" % sys.argv[0]
sys.exit(2)
5 changes: 5 additions & 0 deletions starpy.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[AMI]
username=acv
secret=acv!@#e
server=127.0.0.1
port=5038

0 comments on commit 420ffd0

Please sign in to comment.