#!/usr/bin/env python2.5
"""
Plugin to monitor icecast2 streaming servers.

Author: Markus Lindenberg <markus.lindenberg@gmail.com>
COntributors: Julien 'Lta' BALLET <elthariel@gmail.com>
Version: 2009111101

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 3 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, see <http://www.gnu.org/licenses/>.

Usage:
	- Link or copy to /etc/munin/plugins
	- To enable listening duration, traffic and uptime stats,
	also link to icecast_duration, icecast_traffic and
	icecast_uptime respectively.

Configuration:
	- Enter your server, user and pw below
	  (The plugin does not need to run on
	  the same host as your icecast2 server)
	- Optionally provide hosts to exclude.
        - Optionally provide sources to exclude.

Possible TODOs:
	- use autoconf
	- use munin's configuration system
	- documentation

"""

# CONFIGURATION

server = "localhost:8000"
user = "admin"
pw = "yourpassword"

# Exclude these hosts when calculating listening duration / listeners count
# (we use this to prevent our aircheck recording system from appearing in the stats)
#exclude = ("123.123.123.123",)
exclude = ()

# Exclude these sources from calculation. This is useful to excluse special sources like
# fallback sources which doesn't expose the same informations and then break this script
# Ever add fallback sources to this list
#source_exclude = ["/fallback.mp3", "/fallback.ogg"]
source_exclude = ("/fallback.mp3", "/fallback.ogg")

# /CONFIGURATION

from sys import argv, exit, stderr
import urllib
from xml.etree.ElementTree import ElementTree
from os.path import basename

class IcecastURLopener(urllib.FancyURLopener):
	def prompt_user_passwd(self, host, realm):
		return (user, pw)

opener = IcecastURLopener()
f = opener.open("http://%s/admin/listmounts" % server)

tree = ElementTree()
tree.parse(f)
f.close()

sources = []
for s in tree.getiterator("source"):
	if s.attrib["mount"] not in source_exclude:
		sources.append({"mount": s.attrib["mount"],
				"listeners": s.find("listeners").text,
				"connected": s.find("Connected").text})

plugin_name = basename(argv[0])

try:
	if argv[1] == "config":
		if plugin_name == "icecast_duration":
			print "graph_title Icecast client listening duration"
			print "graph_args --base 1000 -l 0"
			print "graph_scale no"
			print "graph_category streaming"
			print "graph_vlabel minutes"
			print "avg.label average listening duration"
			print "mdn.label median listening duration"
		elif plugin_name == "icecast_uptime":
			print "graph_title Icecast source uptime"
			print "graph_args --base 1000 -l 0"
			print "graph_scale no"
			print "graph_category streaming"
			print "graph_vlabel hours"
			for s in sources:
				print "%s.label source %s" % (s["mount"].strip("/").replace(".","_").replace("-","_"), s["mount"])
		elif plugin_name == "icecast_traffic":
			print "graph_title Icecast outgoing traffic"
			print "graph_args --base 1024 -l 0"
			print "graph_category streaming"
			print "graph_vlabel bytes / second"
			is_first = True
			for s in sources:
				sname = s["mount"].strip("/").replace(".","_").replace("-","_")
				print "%s.label source %s" % (sname, s["mount"])
				print "%s.type DERIVE" % sname
				print "%s.min 0" % sname
				if is_first:
					print "%s.draw AREA" % sname
					is_first = False
				else:
					print "%s.draw STACK" % sname
		else:
			print "graph_title Icecast listeners count"
			print "graph_args --base 1000 -l 0"
			print "graph_scale no"
			print "graph_category streaming"
			print "graph_vlabel listeners"
			is_first = True
			for s in sources:
				sname = s["mount"].strip("/").replace(".","_").replace("-","_")
				print "%s.label source %s" % (sname, s["mount"])
				if is_first:
					print "%s.draw AREA" % sname
					is_first = False
				else:
					print "%s.draw STACK" % sname

		exit(0)

except IndexError:
	pass

if plugin_name == "icecast_uptime":
	for s in sources:
		print "%s.value %s" % (s["mount"].strip("/").replace(".","_").replace("-","_"), int(s["connected"]) / 3600.)

elif plugin_name == "icecast_traffic":
	f = opener.open("http://%s/admin/stats.xml" % server)
	tree = ElementTree()
	tree.parse(f)
	f.close()
	for s in tree.getiterator("source"):
		print "%s.value %s" % (s.attrib["mount"].strip("/").replace(".","_").replace("-","_"), s.find("total_bytes_sent").text)

else:
	durations = {}
	for s in sources:
		durations[s["mount"]] = []
		f = opener.open("http://%s/admin/listclients?mount=%s" % (server, s["mount"]))
		tree = ElementTree()
		tree.parse(f)
		f.close()
		for l in tree.getiterator("listener"):
			if l.find("IP").text not in exclude:
				durations[s["mount"]].append(int(l.find("Connected").text))

	if plugin_name == "icecast_duration":
		if not durations:
			exit(0)
		alldurations = reduce(lambda x, y: x+y, durations.values())
		alldurations.sort()
		print "avg.value %s" % (sum(alldurations) / float(len(alldurations)) / 60.,)
		if len(alldurations) % 2:
			median = alldurations[len(alldurations) / 2] / 60.
		elif len(alldurations):
			median = (alldurations[len(alldurations) / 2 - 1] + alldurations[len(alldurations) / 2]) / 2. / 60.
		else:
			median = 0
		print "mdn.value %s" % median
	else:
		for s in sources:
			print "%s.value %s" % (s["mount"].strip("/").replace(".","_").replace("-","_"), len(durations[s["mount"]]))

