-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 420ffd0
Showing
11 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(). | ||
""" |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |