<?php

#
# ss_cisco_cpu_usage.php
# version 0.9d
# September 13, 2009
#
# Copyright (C) 2006-2009, Eric A. Hall
# http://www.eric-a-hall.com/
#
# This software is licensed under the same terms as Cacti itself
#

#
# load the Cacti configuration settings if they aren't already present
#
if (isset($config) == FALSE) {

	if (file_exists(dirname(__FILE__) . "/../include/config.php")) {
		include_once(dirname(__FILE__) . "/../include/config.php");
	}

	if (file_exists(dirname(__FILE__) . "/../include/global.php")) {
		include_once(dirname(__FILE__) . "/../include/global.php");
	}

	if (isset($config) == FALSE) {
		echo ("FATAL: Unable to load Cacti configuration files \n");
		return;
	}
}

#
# load the Cacti SNMP libraries if they aren't already present
#
if (defined('SNMP_METHOD_PHP') == FALSE) {

	if (file_exists(dirname(__FILE__) . "/../lib/snmp.php")) {
		include_once(dirname(__FILE__) . "/../lib/snmp.php");
	}

	if (defined('SNMP_METHOD_PHP') == FALSE) {
		echo ("FATAL: Unable to load SNMP libraries \n");
		return;
	}
}

#
# call the main function manually if executed outside the Cacti script server
#
if (isset($GLOBALS['called_by_script_server']) == FALSE) {

	array_shift($_SERVER["argv"]);
	print call_user_func_array("ss_cisco_cpu_usage", $_SERVER["argv"]);
}

#
# main function
#
function ss_cisco_cpu_usage($protocol_bundle="",
	$cacti_request="", $data_request="", $data_request_key="") {

	#
	# 1st function argument contains the protocol-specific bundle
	#
	# use '===' matching for strpos in case colon is 1st character
	#
	if ((trim($protocol_bundle) == "") || (strpos($protocol_bundle, ":") === FALSE)) {

		echo ("FATAL: No SNMP parameter bundle provided\n");
		ss_cisco_cpu_usage_syntax();
		return;
	}

	$protocol_array = explode(":", $protocol_bundle);

	if (count($protocol_array) < 11) {

		echo ("FATAL: Not enough elements in SNMP parameter bundle\n");
		ss_cisco_cpu_usage_syntax();
		return;
	}

	if (count($protocol_array) > 11) {

		echo ("FATAL: Too many elements in SNMP parameter bundle\n");
		ss_cisco_cpu_usage_syntax();
		return;
	}

	#
	# 1st bundle element is $snmp_hostname
	#
	$snmp_hostname = trim($protocol_array[0]);

	if ($snmp_hostname == "") {

		echo ("FATAL: Hostname not specified in SNMP parameter bundle\n");
		ss_cisco_cpu_usage_syntax();
		return;
	}

	#
	# 2nd bundle element is $snmp_version
	#
	$snmp_version = trim($protocol_array[1]);

	if ($snmp_version == "") {

		echo ("FATAL: SNMP version not specified in SNMP parameter bundle\n");
		ss_cisco_cpu_usage_syntax();
		return;
	}

	if (($snmp_version != 1) and ($snmp_version != 2) and ($snmp_version != 3)) {

		echo ("FATAL: \"$snmp_version\" is not a valid SNMP version\n");
		ss_cisco_cpu_usage_syntax();
		return;
	}

	#
	# 3rd bundle element is $snmp_community
	#
	$snmp_community = trim($protocol_array[2]);

	if (($snmp_version != 3) and ($snmp_community == "")) {

		echo ("FATAL: SNMP v$snmp_version community not specified in SNMP parameter bundle\n");
		ss_cisco_cpu_usage_syntax();
		return;
	}

	#
	# 4th bundle element is $snmp_v3_username
	#
	$snmp_v3_username = trim($protocol_array[3]);

	#
	# 5th bundle element is $snmp_v3_password
	#
	$snmp_v3_password = trim($protocol_array[4]);

	#
	# 6th bundle element is $snmp_v3_authproto
	#
	$snmp_v3_authproto = trim($protocol_array[5]);

	#
	# 7th bundle element is $snmp_v3_privpass
	#
	$snmp_v3_privpass = trim($protocol_array[6]);

	#
	# 8th bundle element is $snmp_v3_privproto
	#
	$snmp_v3_privproto = trim($protocol_array[7]);

	#
	# 9th bundle element is $snmp_v3_context
	#
	$snmp_v3_context = trim($protocol_array[8]);

	#
	# 10th bundle element is $snmp_port
	#
	$snmp_port = trim($protocol_array[9]);

	if ($snmp_port == "") {

		#
		# if the value was omitted use the default port number
		#
		$snmp_port = 161;
	}

	if (is_numeric($snmp_port) == FALSE) {

		echo ("FATAL: Non-numeric SNMP port \"$snmp_port\" specified in SNMP parameter bundle\n");
		ss_cisco_cpu_usage_syntax();
		return;
	}

	#
	# 11th bundle element is $snmp_timeout
	#
	$snmp_timeout = trim($protocol_array[10]);

	if ($snmp_timeout == "") {

		#
		# if the value was omitted use the Cacti SNMP timeout
		#
		if (isset($GLOBALS['config']['config_options_array']['snmp_timeout'])) {

			$snmp_timeout = trim($GLOBALS['config']['config_options_array']['snmp_timeout']);
		}

		elseif (isset($GLOBALS['_SESSION']['sess_config_array']['snmp_timeout'])) {

			$snmp_timeout = trim($GLOBALS['_SESSION']['sess_config_array']['snmp_timeout']);
		}

		else {
			echo ("FATAL: Timeout not specified in SNMP parameter bundle and " .
				"unable to determine Cacti default\n");
			ss_cisco_cpu_usage_syntax();
			return;
		}
	}

	if (is_numeric($snmp_timeout) == FALSE) {

		echo ("FATAL: Non-numeric SNMP timeout \"$snmp_timeout\" specified in SNMP parameter bundle\n");
		ss_cisco_cpu_usage_syntax();
		return;
	}

	#
	# Cacti's SNMP timeout uses milliseconds, while PHP uses Net-SNMP foormat, which is
	# typically microseconds. Normalize by multiplying the timeout value by 1000.
	#
	$snmp_timeout = ($snmp_timeout * 1000);

	#
	# 2nd function argument is $cacti_request
	#
	$cacti_request = strtolower(trim($cacti_request));

	if ($cacti_request == "") {

		echo ("FATAL: No Cacti request provided\n");
		ss_cisco_cpu_usage_syntax();
		return;
	}

	if (($cacti_request != "index") &&
		($cacti_request != "query") &&
		($cacti_request != "get")) {

		echo ("FATAL: \"$cacti_request\" is not a valid Cacti request\n");
		ss_cisco_cpu_usage_syntax();
		return;
	}

	#
	# remaining function arguments are $data_request and $data_request_key
	#
	if (($cacti_request == "query") || ($cacti_request == "get")) {

		$data_request = strtolower(trim($data_request));

		if ($data_request == "") {

			echo ("FATAL: No data requested for Cacti \"$cacti_request\" request\n");
			ss_cisco_cpu_usage_syntax();
			return;
		}

		if (($data_request != "cpudevice") &&
			($data_request != "cpuname") &&
			($data_request != "cpudesc") &&
			($data_request != "onemin") &&
			($data_request != "fivemin")) {

			echo ("FATAL: \"$data_request\" is not a valid data request\n");
			ss_cisco_cpu_usage_syntax();
			return;
		}

		#
		# get the index variable
		#
		if ($cacti_request == "get") {

			$data_request_key = strtolower(trim($data_request_key));

			if ($data_request_key == "") {

				echo ("FATAL: No index value provided for \"$data_request\" data request\n");
				ss_cisco_cpu_usage_syntax();
				return;
			}
		}

		#
		# clear out spurious command-line parameters on query requests
		#
		else {
			$data_request_key = "";
		}
	}

	#
	# build a nested array of data elements for future use
	#
	$oid_array = array ("cpuIndex" => ".1.3.6.1.4.1.9.9.109.1.1.1.1.2",
		"cpuDesc" => ".1.3.6.1.2.1.47.1.1.1.1.7",
		"cpu1min" => ".1.3.6.1.4.1.9.9.109.1.1.1.1.7",
		"cpu5min" => ".1.3.6.1.4.1.9.9.109.1.1.1.1.8");

	#
	# build the snmp_arguments array for future use
	#
	# note that the array structure varies according to the version of Cacti in use
	#
	if (isset($GLOBALS['config']['cacti_version']) == FALSE) {

		echo ("FATAL: Unable to determine Cacti version\n");
		return;
	}

	elseif (substr($GLOBALS['config']['cacti_version'],0,5) == "0.8.6") {

		$snmp_arguments = array(
			$snmp_hostname,
			$snmp_community,
			"",
			$snmp_version,
			$snmp_v3_username,
			$snmp_v3_password,
			$snmp_port,
			$snmp_timeout,
			"",
			"");
		}

	elseif (substr($GLOBALS['config']['cacti_version'],0,5) >= "0.8.7") {

		$snmp_arguments = array(
			$snmp_hostname,
			$snmp_community,
			"",
			$snmp_version,
			$snmp_v3_username,
			$snmp_v3_password,
			$snmp_v3_authproto,
			$snmp_v3_privpass,
			$snmp_v3_privproto,
			$snmp_v3_context,
			$snmp_port,
			$snmp_timeout,
			"",
			"");
		}

	else {
		echo ("FATAL: \"" . $GLOBALS['config']['cacti_version'] .
			"\" is not a supported Cacti version\n");
		return;
	}

	#
	# if they want data for just one cpu, use the input data to seed the array
	#
	if ($cacti_request == "get") {

		#
		# set snmp_arguments to cpuIndex plus the requested index value and query
		#
		$snmp_arguments[2] = $oid_array['cpuIndex'] . "." . $data_request_key;
		$snmp_test = trim(call_user_func_array("cacti_snmp_get", $snmp_arguments));

		#
		# the snmp response should contain a numeric identifier for the CPU component
		#
		if ((isset($snmp_test) == FALSE) ||
			(substr($snmp_test, 0, 16) == "No Such Instance") ||
			(is_numeric($snmp_test) == FALSE) ||
			($snmp_test == "")) {

			echo ("FATAL: No CPU data was returned from SNMP\n");
			return;
		}

		#
		# response looks okay, so assume the requested index value is valid
		#
		$cpu_array[0]['index'] = $data_request_key;
	}

	#
	# if they want data for all CPUs, use snmpwalk to seed the array
	#
	else {
		#
		# set the snmp_arguments array to the cpudevice OID
		#
		$snmp_arguments[2] = $oid_array['cpuIndex'];

		#
		# walk the tree and capture the resulting array of CPUs
		#
		$snmp_array = call_user_func_array("cacti_snmp_walk", $snmp_arguments);

		#
		# verify that the response contains expected data structures
		#
		if ((isset($snmp_array) == FALSE) ||
			(count($snmp_array) == 0) ||
			(array_key_exists('oid', $snmp_array[0]) == FALSE) || 
			(array_key_exists('value', $snmp_array[0]) == FALSE) ||
			(substr($snmp_array[0]['value'],0,16) == "No Such Instance") ||
			(is_numeric($snmp_array[0]['value']) == FALSE) ||
			(trim($snmp_array[0]['value']) == "")) {

			echo ("FATAL: No CPU data was returned from SNMP\n");
			return;
		}

		#
		# create the array entries
		#
		$cpu_count = 0;

		foreach ($snmp_array as $snmp_response) {

			#
			# the trailing block of digits in each response OID identifies the CPU index
			#
			# remove whitespace from around the OIDs in $snmp_array so we can match the digits
			#
			$snmp_response['oid'] = trim($snmp_response['oid']);

			#
			# use regex to locate the relative OID
			#
			# exit if no match found
			#
			if (preg_match('/(\d+)$/', $snmp_response['oid'], $scratch) == 0) {

				echo ("FATAL: Unable to determine CPU index number from SNMP results\n");
				return;
			}

			else {
				#
				# match was found so use relative OID for the CPU index
				#
				$cpu_array[$cpu_count]['index'] = $scratch[1];
			}

			#
			# increment the cpu counter
			#
			$cpu_count++;
		}
	}

	#
	# verify that the cpu_array exists and has data
	#
	if ((isset($cpu_array) == FALSE) || (count($cpu_array) == 0)) {

		echo ("FATAL: No matching CPUs were returned from SNMP\n");
		return;
	}

	#
	# requests for data other than index values require additional processing
	#
	if ((($cacti_request == "query") || ($cacti_request == "get")) &&
		($data_request != "cpudevice")) {

		#
		# cycle through the CPUs and populate the data
		#
		$cpu_count = 0;

		foreach ($cpu_array as $cpu) {

			#
			# only fill in the requested data
			#
			# updates MUST reference the canonical array because foreach refs are just copies
			#
			switch ($data_request) {

				case "cpuname":

					#
					# create a psuedo-name from the index value
					#
					$cpu_array[$cpu_count]['name'] = "CPU" . $cpu['index'];

					break;

				case "cpudesc":

					#
					# the 'value' field from the SNMP CPU index contains a hardware ID
					#
					# set the snmp_arguments array and query
					#
					$snmp_arguments[2] = $oid_array['cpuIndex'] . "." . $cpu['index'];
					$scratch = trim(call_user_func_array("cacti_snmp_get", $snmp_arguments));

					#
					# snmp response should contain the CPU type ID
					#
					if ((isset($scratch) == FALSE) ||
						(substr($scratch, 0, 16) == "No Such Instance") ||
						(is_numeric($scratch) == FALSE) ||
						($scratch == "")) {

						#
						# CPU type is unknown, so call it "Unnamed CPU"
						#
						$cpu_array[$cpu_count]['desc'] = "Unnamed CPU";
					}

					else {
						#
						# set the snmp_arguments array to the hardware naming OID and query
						#
						$snmp_arguments[2] = $oid_array['cpuDesc'] . "." . $scratch;
						$scratch = trim(call_user_func_array("cacti_snmp_get",
							$snmp_arguments));

						#
						# snmp response should contain the CPU hardware string
						#
						if ((isset($scratch) == FALSE) ||
							(substr($scratch, 0, 16) == "No Such Instance") ||
							($scratch == "")) {
	
							#
							# CPU type is unknown, so call it "Unnamed CPU"
							#
							$cpu_array[$cpu_count]['desc'] = "Unnamed CPU";
						}

						else {
							#
							# CPU has a formal device name
							#
							$cpu_array[$cpu_count]['desc'] = $scratch;
						}
					}

					break;

				case "onemin":

					#
					# fetch the 1min utilization for the current CPU using the newer Cisco OID
					#
					# set the snmp_arguments array to the newer OID and query
					#
					$snmp_arguments[2] = $oid_array['cpu1min'] . "." . $cpu['index'];
					$scratch = trim(call_user_func_array("cacti_snmp_get", $snmp_arguments));

					#
					# if no useful data was returned, switch to using the old CPU OIDs
					#
					if ((isset($scratch) == FALSE) ||
						(substr($scratch, 0, 16) == "No Such Instance") ||
						(is_numeric($scratch) == FALSE) ||
						($scratch == "")) {

						#
						# set the snmp_arguments array to the old CPU 1min OID and query
						#
						$snmp_arguments[2] = ".1.3.6.1.4.1.9.9.109.1.1.1.1.4" . "." .
							$cpu['index'];

						$scratch = trim(call_user_func_array("cacti_snmp_get",
							$snmp_arguments));

						#
						# if no useful data was returned, null the results
						#
						if ((isset($scratch) == FALSE) ||
							(substr($scratch, 0, 16) == "No Such Instance") ||
							(is_numeric($scratch) == FALSE) ||
							($scratch == "")) {

							$scratch = "";
						}
					}

					$cpu_array[$cpu_count]['1min'] = $scratch;

					break;
	
				case "fivemin":

					#
					# fetch the 5min utilization for the current CPU using the newer Cisco OID
					#
					# set the snmp_arguments array to the newer OID and query
					#
					$snmp_arguments[2] = $oid_array['cpu5min'] . "." . $cpu['index'];
					$scratch = trim(call_user_func_array("cacti_snmp_get", $snmp_arguments));

					#
					# if no useful data was returned, switch to using the old CPU OIDs
					#
					if ((isset($scratch) == FALSE) ||
						(substr($scratch, 0, 16) == "No Such Instance") ||
						(is_numeric($scratch) == FALSE) ||
						($scratch == "")) {

						#
						# set the snmp_arguments array to the old CPU 1min OID and query
						#
						$snmp_arguments[2] = ".1.3.6.1.4.1.9.9.109.1.1.1.1.5" . "." .
							$cpu['index'];

						$scratch = trim(call_user_func_array("cacti_snmp_get",
							$snmp_arguments));

						#
						# if no useful data was returned, null the results
						#
						if ((isset($scratch) == FALSE) ||
							(substr($scratch, 0, 16) == "No Such Instance") ||
							(is_numeric($scratch) == FALSE) ||
							($scratch == "")) {

							$scratch = "";
						}
					}

					$cpu_array[$cpu_count]['5min'] = $scratch;

					break;

			}

			#
			# increment the cpu counter
			#
			$cpu_count++;
		}
	}

	#
	# generate output
	#
	foreach ($cpu_array as $cpu) {

		#
		# return output data according to $cacti_request
		#
		switch ($cacti_request) {

			#
			# for "index" requests, dump the device column
			#
			case "index":

				echo ($cpu['index'] . "\n");
				break;

			#
			# if the user queried for all entries in a column, return that column
			#
			case "query";

				switch ($data_request) {

					case "cpudevice":

						echo ($cpu['index'] . ":" . $cpu['index'] . "\n");
						break;

					case "cpuname":

						echo ($cpu['index'] . ":" . $cpu['name'] . "\n");
						break;

					case "cpudesc":

						echo ($cpu['index'] . ":" . $cpu['desc'] . "\n");
						break;

					case "onemin":

						echo ($cpu['index'] . ":" . $cpu['1min'] . "\n");
						break;

					case "fivemin":

						echo ($cpu['index'] . ":" . $cpu['5min'] . "\n");
						break;
				}

				break;

			#
			# if the user requested the value for a specific entry, give them just that data
			#
			case "get":

				switch ($data_request) {

					case "cpudevice":

						echo ($cpu['index'] . "\n");
						break;

					case "cpuname":

						echo ($cpu['name'] . "\n");
						break;

					case "cpudesc":

						echo ($cpu['desc'] . "\n");
						break;

					case "onemin":

						if (isset($GLOBALS['called_by_script_server']) == TRUE) {

							return($cpu['1min']);
						}

						else {
							echo ($cpu['1min'] . "\n");
						}

						break;

					case "fivemin":

						if (isset($GLOBALS['called_by_script_server']) == TRUE) {

							return($cpu['5min']);
						}

						else {
							echo ($cpu['5min'] . "\n");
						}

						break;
				}

				break;
		}
	}
}

#
# display the syntax
#
function ss_cisco_cpu_usage_syntax() {

	echo ("Syntax: ss_cisco_cpu_usage.php <hostname>:<snmp_version>:[<snmp_community>]:\ \n" .
	"      [<snmp3_username>]:[<snmp3_password>]:[<snmp3_auth_protocol>]:[<snmp3_priv_password>]:\ \n" .
	"      [<snmp3_priv_protocol>]:[<snmp3_context>]:[<snmp_port>]:[<snmp_timeout>] \ \n" .
	"      (index|query <fieldname>|get <fieldname> <cpu-index>)\n");
}

?>
