#/**********************************************************************************
#	Disk IO Ganglia Module
#	Sid Stuart 8/27/2008
#	copyright Kongregate.com
#
#   License to use, modiy, and distribute under the GPL
#   http://www.gnu.org/licenses/gpl.txt
#
#	This module collects disk i/o statistics provided by iostat. It is very
#	Linux specific. 
#
#**********************************************************************************/

import subprocess
Popen = subprocess.Popen
PIPE = subprocess.PIPE

DISKSTATS="/proc/diskstats"
DISKSTATS_TEST="diskstats"

# Positions of values in /proc/diskstats
NAME=2			# Name of the device
RIO=3			# Number of read IO requests COMPLETED
RMERGE=4		# Number of SUBMITTED read requests that were merged into existing requests
RSECT=5			# Number of read IO sectors SUBMITTED
RUSE=6			# Total length of time all completed read requests have taken
WIO=7
WMERGE=8
WSECT=9
WUSE=10
RUNNING=11
USE=12
AVEQ=13


######
#	Average time to complete a read (in ms) over the last second. ruse/rio
#	

def avg_read_time (name):
	global _last_result
	drive = parsename (name)
	# Pull out the cached data from last time, if it is there
	if (_last_result.has_key(drive)):
		last_data = _last_result [drive]
		last_rio = float (last_data[RIO])
		last_ruse = float (last_data[RUSE])
	else:
		last_rio = 0
		last_ruse = 0
	data = getData () [drive]
	rio = float (data[RIO])
	ruse = float (data[RUSE])
	return (ruse-last_ruse)/(rio-last_rio)

#	Average time to complete a write (in ms) over the last second. wuse/wio
#	
def avg_write_time (name):	
	global _last_result
	drive = parsename (name)
	if (_last_result.has_key(drive)):
		last_data = _last_result [drive]
		last_wio = float (last_data[WIO])
		last_wuse = float (last_data[WUSE])
	else:
		last_wio = 0
		last_wuse = 0
	data = getData () [drive]
	wio = float (data[WIO])
	wuse = float (data[WUSE])
	return (wuse-last_wuse)/(wio - last_wio)

#	Average sectors per request
#
def avg_sectors_per_request (name):
	global _last_result
	drive = parsename (name)
	if (_last_result.has_key(drive)):
		last_data = _last_result [drive]
		last_rsect = int (last_data[RSECT])
		last_wsect = int (last_data[WSECT])
		last_rio = int (last_data[RIO])
		last_wio = int (last_data[WIO])
	else:
		last_rsect = 0
		last_wsect = 0
		last_rio   = 0
		last_wio   = 0
	drive = parsename (name)
	data = getData () [drive]
	rsect = int(data[RSECT])
	wsect = int(data[WSECT])
	rio = int(data[RIO])
	wio = int(data[WIO])
	return (rsect-last_rsect + wsect-last_wsect)/(rio-last_rio + wio-last_wio)
	
#  Kilobytes per second read requested
#
SECTOR = 512     # bytes
def kbytes_read_per_sec (name):
	global _last_result
	drive = parsename (name)
	if (_last_result.has_key(drive)):
		last_data = _last_result [drive]
		last_rsect = int (last_data[RSECT])
	else:
		return 0
	data = getData () [drive]
	rsect = int(data[RSECT])
	return ((rsect-last_rsect)*SECTOR)/1000
	
#  Kilobytes per second write requested
#
def kbytes_write_per_sec (name):
	global _last_result
	drive = parsename (name)
	if (_last_result.has_key(drive)):
		last_data = _last_result [drive]
		last_wsect = int (last_data[WSECT])
	else:
		return 0
	data = getData () [drive]
	wsect = int(data[WSECT])
	return ((wsect-last_wsect)*SECTOR)/1000

#	Operations per second
#
def operations_per_sec (name):
	global _last_result
	drive = parsename (name)
	if (_last_result.has_key(drive)):
		last_data = _last_result [drive]
		last_rio = int (last_data[RIO])
		last_wio = int (last_data[WIO])
	else:
		return 0  #Should figure out the total uptime and do an average instead
	data = getData () [drive]
	rio = int(data[RIO])
	wio = int(data[WIO])
	return rio-last_rio + wio-last_wio
		
######		
#	Descriptions of the metrics
_descriptors = [{'name': 'avg_read_time.sda',
		'call_back': avg_read_time,
		'time_max': 30,
		'value_type': 'float',
		'units': 'Milliseconds',
		'slope': 'both',
		'format': '%f',
		'description': 'Average time to complete a read, sda',
		'groups': 'Disk I/O'},
		{'name': 'avg_read_time.sdb',
		'call_back': avg_read_time,
		'time_max': 30,
		'value_type': 'float',
		'units': 'Milliseconds',
		'slope': 'both',
		'format': '%f',
		'description': 'Average time to complete a read, sdb',
		'groups': 'Disk I/O'},
		{'name': 'avg_read_time.sdc',
		'call_back': avg_read_time,
		'time_max': 30,
		'value_type': 'float',
		'units': 'Milliseconds',
		'slope': 'both',
		'format': '%f',
		'description': 'Average time to complete a read, sdc',
		'groups': 'Disk I/O'},
		{'name': 'avg_read_time.sdd',
		'call_back': avg_read_time,
		'time_max': 30,
		'value_type': 'float',
		'units': 'Milliseconds',
		'slope': 'both',
		'format': '%f',
		'description': 'Average time to complete a read, sdd',
		'groups': 'Disk I/O'},
		{'name': 'avg_write_time.sda',
		'call_back': avg_write_time,
		'time_max': 30,
		'value_type': 'float',
		'units': 'Milliseconds',
		'slope': 'both',
		'format': '%f',
		'description': 'Average time to complete a write, sda',
		'groups': 'Disk I/O'},
		{'name': 'avg_write_time.sdb',
		'call_back': avg_write_time,
		'time_max': 30,
		'value_type': 'float',
		'units': 'Milliseconds',
		'slope': 'both',
		'format': '%f',
		'description': 'Average time to complete a write, sdb',
		'groups': 'Disk I/O'},
		{'name': 'avg_write_time.sdc',
		'call_back': avg_write_time,
		'time_max': 30,
		'value_type': 'float',
		'units': 'Milliseconds',
		'slope': 'both',
		'format': '%f',
		'description': 'Average time to complete a write, sdc',
		'groups': 'Disk I/O'},
		{'name': 'avg_write_time.sdd',
		'call_back': avg_write_time,
		'time_max': 30,
		'value_type': 'float',
		'units': 'Milliseconds',
		'slope': 'both',
		'format': '%f',
		'description': 'Average time to complete a write, sdd',
		'groups': 'Disk I/O'},
		{'name': 'avg_sectors_per_request.sda',
		'call_back': avg_sectors_per_request,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Sectors',
		'slope': 'both',
		'format': '%u',
		'description': 'Average sectors per request, sda',
		'groups': 'Disk I/O'},
		{'name': 'avg_sectors_per_request.sdb',
		'call_back': avg_sectors_per_request,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Sectors',
		'slope': 'both',
		'format': '%u',
		'description': 'Average sectors per request, sdb',
		'groups': 'Disk I/O'},
		{'name': 'avg_sectors_per_request.sdc',
		'call_back': avg_sectors_per_request,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Sectors',
		'slope': 'both',
		'format': '%u',
		'description': 'Average sectors per request, sdc',
		'groups': 'Disk I/O'},
		{'name': 'avg_sectors_per_request.sdd',
		'call_back': avg_sectors_per_request,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Sectors',
		'slope': 'both',
		'format': '%u',
		'description': 'Average sectors per request, sdd',
		'groups': 'Disk I/O'},
		{'name': 'kbytes_read_per_sec.sda',
		'call_back': kbytes_read_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Kilobytes',
		'slope': 'both',
		'format': '%u',
		'description': 'Kbytes read per second, sda',
		'groups': 'Disk I/O'},
		{'name': 'kbytes_read_per_sec.sdb',
		'call_back': kbytes_read_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Kilobytes',
		'slope': 'both',
		'format': '%u',
		'description': 'Kbytes read per second, sdb',
		'groups': 'Disk I/O'},
		{'name': 'kbytes_read_per_sec.sdc',
		'call_back': kbytes_read_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Kilobytes',
		'slope': 'both',
		'format': '%u',
		'description': 'Kbytes read per second, sdc',
		'groups': 'Disk I/O'},
		{'name': 'kbytes_read_per_sec.sdd',
		'call_back': kbytes_read_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Kilobytes',
		'slope': 'both',
		'format': '%u',
		'description': 'Kbytes read per second, sdd',
		'groups': 'Disk I/O'},
		{'name': 'kbytes_write_per_sec.sda',
		'call_back': kbytes_write_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Kilobytes',
		'slope': 'both',
		'format': '%u',
		'description': 'Kbytes write per second, sda',
		'groups': 'Disk I/O'},
		{'name': 'kbytes_write_per_sec.sdb',
		'call_back': kbytes_write_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Kilobytes',
		'slope': 'both',
		'format': '%u',
		'description': 'Kbytes write per second, sdb',
		'groups': 'Disk I/O'},
		{'name': 'kbytes_write_per_sec.sdc',
		'call_back': kbytes_write_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Kilobytes',
		'slope': 'both',
		'format': '%u',
		'description': 'Kbytes write per second, sdc',
		'groups': 'Disk I/O'},
		{'name': 'kbytes_write_per_sec.sdd',
		'call_back': kbytes_write_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Kilobytes',
		'slope': 'both',
		'format': '%u',
		'description': 'Kbytes write per second, sdd',
		'groups': 'Disk I/O'},
		{'name': 'operations_per_sec.sda',
		'call_back': operations_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Read/Write Requests',
		'slope': 'both',
		'format': '%u',
		'description': 'operations per second, sda',
		'groups': 'Disk I/O'},
		{'name': 'operations_per_sec.sdb',
		'call_back': operations_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Read/Write Requests',
		'slope': 'both',
		'format': '%u',
		'description': 'operations per second, sdb',
		'groups': 'Disk I/O'},
		{'name': 'operations_per_sec.sdc',
		'call_back': operations_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Read/Write Requests',
		'slope': 'both',
		'format': '%u',
		'description': 'operations per second, sdc',
		'groups': 'Disk I/O'},
		{'name': 'operations_per_sec.sdd',
		'call_back': operations_per_sec,
		'time_max': 30,
		'value_type': 'uint',
		'units': 'Read/Write Requests',
		'slope': 'both',
		'format': '%u',
		'description': 'operations per second, sdd',
		'groups': 'Disk I/O'}
		]
		
###
#	Ganglia boilerplate, not much to do.
#
def metric_init (params):
	return _descriptors
	
###
#	Ganglia boilerplate, nothing to do.
#
def metric_cleanup ():
	pass
	
###
#	Pick up the data and store it in a dictionary.
#	
#
import time
_lasttime = 0.0
_last_result = {}
_cur_result = {}
def getData ():
	global _lasttime, _last_result, _cur_result
	result = {}
	# Use a local file for testing
	if __name__ == '__main__':
		filename = DISKSTATS_TEST
	else:
		filename = DISKSTATS
	# Check the time
	now = time.time()
	delta = now - _lasttime
	# If more than a second has lapsed, then grab a new set of results, save them and
	# return them. Else, just turn the saved results
	if (delta > 1):
		# Open the diskstat file and pull out the data
		try:
			fh = open(filename, mode='r', )
			for eachLine in fh:
				eachLine.strip ()
				words = eachLine.split ()
				result[words [2]] = words
			fh.close
			_last_result = _cur_result
			_cur_result = result
			# Reset the time check
			_lasttime = now
		except EnvironmentError, e:
			message = "Problem opening " + filename + " " + e.string()
	else:
		result = _cur_result
#	fh = open('/tmp/watch', mode='a')
	#print >> fh, _lasttime 
#	print >> fh, "Last Time:", _lasttime, "Now:", now, "Delta:", delta
#	print >> fh, result['sdb']
#	fh.close
	return result
	
#	Parse the name passed as a parameter to find which disk to report on
#
def parsename (name):
	words = name.split (".")
	return words[1]
	
#This code is for debugging and unit testing
if __name__ == '__main__':
    descriptors = metric_init("void")
    for d in descriptors:
        v = d['call_back'](d['name'])
        print 'value for %s is %u' % (d['name'],  v)
