From 561efa41be5e22614912300ac9242582340e0662 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Mon, 22 Aug 2016 21:35:13 -0500 Subject: [PATCH] SNMP extend scripts from the main repo --- snmp/apache-stats | 89 +++ snmp/apache-stats.py | 87 +++ snmp/mailscanner.php | 76 +++ snmp/mysql-stats | 131 ++++ snmp/mysql_stats.php | 1247 +++++++++++++++++++++++++++++++++++ snmp/nfs-stats.sh | 0 snmp/nginx-stats | 37 ++ snmp/postfix-queues | 13 + snmp/powerdns.php | 76 +++ snmp/raspberry.sh | 0 snmp/shoutcast.default.conf | 3 + snmp/shoutcast.php | 122 ++++ 12 files changed, 1881 insertions(+) create mode 100755 snmp/apache-stats create mode 100755 snmp/apache-stats.py create mode 100755 snmp/mailscanner.php create mode 100755 snmp/mysql-stats create mode 100755 snmp/mysql_stats.php mode change 100644 => 100755 snmp/nfs-stats.sh create mode 100755 snmp/nginx-stats create mode 100755 snmp/postfix-queues create mode 100755 snmp/powerdns.php mode change 100644 => 100755 snmp/raspberry.sh create mode 100644 snmp/shoutcast.default.conf create mode 100755 snmp/shoutcast.php diff --git a/snmp/apache-stats b/snmp/apache-stats new file mode 100755 index 0000000..863514a --- /dev/null +++ b/snmp/apache-stats @@ -0,0 +1,89 @@ +#!/usr/bin/env perl +# Original python script Copyright (C) 2009 Glen Pitt-Pladdy +# +# 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 2 +# 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. + +use LWP::Simple; + +$CACHETIME = 30; +$CACHEFILE = '/tmp/snmp-cache-apache'; + +# check for cache file newer CACHETIME seconds ago +if ( -f $CACHEFILE && time - (stat( $CACHEFILE ))[9] < $CACHETIME) { + # use cached data +#print "Using cached data from file $CACHEFILE.\n"; + open (INFILE, "<$CACHEFILE" ) + or die "File open failure: $CACHEFILE\n"; + @data = ; + close INFILE; +} else { + # grab the status URL (fresh data) + @data = split /(\n)/, LWP::Simple::get( 'http://localhost/server-status?auto' ) + or die "Data fetch failure.\n"; + + # write file + $tmpfile = "$CACHEFILE.TMP.$PID"; + open (OUTFILE, ">$tmpfile") + or die "File open failure: $tmpfile\n"; + print OUTFILE @data; + close OUTFILE; + rename ( $tmpfile, $CACHEFILE ); +} + +# dice up the data +@scoreboardkey = ( '_', 'S', 'R', 'W', 'K', 'D', 'C', 'L', 'G', 'I', '.' ); +%params = {}; +foreach $line (@data) { + chomp $line; + @fields = split( /: /, $line); + if ($fields[0] eq 'Scoreboard') { + # count up the scoreboard into states + %states = {}; + foreach $state (@scoreboardkey) { + $states{$state} = 0; + } + foreach $state ( split(//, $fields[1]) ) { + $states{$state}++; + } + } elsif ($fields[0] eq 'Total kBytes') { + # turn into base (byte) value + $params{$fields[0]} = int($fields[1])*1024; + } else { + # just store everything else + $params{$fields[0]} = $fields[1]; + } +} + +# output the data in order (this is because some platforms don't have them all) +@dataorder = ( + 'Total Accesses', + 'Total kBytes', + 'CPULoad', + 'Uptime', + 'ReqPerSec', + 'BytesPerSec', + 'BytesPerReq', + 'BusyServers', + 'IdleServers' +); +foreach $param (@dataorder) { + if (exists $params{$param}) { + print $params{$param}."\n"; + } else { + # not all Apache's have all stats + print "U\n"; + } +} + +# print the scoreboard +foreach $state (@scoreboardkey) { + print $states{$state}."\n"; +} diff --git a/snmp/apache-stats.py b/snmp/apache-stats.py new file mode 100755 index 0000000..891bd19 --- /dev/null +++ b/snmp/apache-stats.py @@ -0,0 +1,87 @@ +#!/usr/bin/python +# Copyright (C) 2009 Glen Pitt-Pladdy +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# +# + +CACHETIME = 30 +CACHEFILE = '/tmp/apache-snmp' + +# check for cache file newer CACHETIME seconds ago +import os +import time +if os.path.isfile ( CACHEFILE ) \ + and ( time.time() - os.stat ( CACHEFILE )[8] ) < CACHETIME: + # use cached data + f = open ( CACHEFILE, 'r' ) + data = f.read() + f.close() +else: + # grab the status URL (fresh data) + # need debian package python-urlgrabber + from urlgrabber import urlread + data = urlread ( 'http://localhost/server-status?auto', + user_agent = 'SNMP Apache Stats' ) + # write file + f = open ( CACHEFILE+'.TMP.'+`os.getpid()`, 'w' ) + f.write ( data ) + f.close() + os.rename ( CACHEFILE+'.TMP.'+`os.getpid()`, CACHEFILE ) + + +# dice up the data +scoreboardkey = [ '_', 'S', 'R', 'W', 'K', 'D', 'C', 'L', 'G', 'I', '.' ] +params = {} +for line in data.splitlines(): + fields = line.split( ': ' ) + if fields[0] == 'Scoreboard': + # count up the scoreboard into states + states = {} + for state in scoreboardkey: + states[state] = 0 + for state in fields[1]: + states[state] += 1 + elif fields[0] == 'Total kBytes': + # turn into base (byte) value + params[fields[0]] = int(fields[1])*1024 + else: + # just store everything else + params[fields[0]] = fields[1] + +# output the data in order (this is because some platforms don't have them all) +dataorder = [ + 'Total Accesses', + 'Total kBytes', + 'CPULoad', + 'Uptime', + 'ReqPerSec', + 'BytesPerSec', + 'BytesPerReq', + 'BusyWorkers', + 'IdleWorkers' +] +for param in dataorder: + try: + print params[param] +# print param + except: # not all Apache's have all stats + print 'U' + +# print the scoreboard +for state in scoreboardkey: + print states[state] +# print state diff --git a/snmp/mailscanner.php b/snmp/mailscanner.php new file mode 100755 index 0000000..b80fdb9 --- /dev/null +++ b/snmp/mailscanner.php @@ -0,0 +1,76 @@ +#!/usr/bin/env php + + /// + /////////////////////////////////////////////////////////////////////////////////////// + + + // START SETTINGS /// + + $mailstats = "/opt/librenms/scripts/watchmaillog/watchmaillog_counters"; + + // END SETTINGS /// + + + /// + // DO NOT EDIT BENETH THIS LINE + /// + /////////////////////////////////////////////////////////////////////////////////////// + + function doSNMPv2($vars) { + $stats = array(); + if (file_exists($vars)) { + $data = file($vars); + foreach ($data as $item=>$value) { + if (!empty($value)) { + $temp = explode(':', trim($value)); + if (isset($temp[1])) { + $stats[$temp[0]] = $temp[1]; + } + } + } + } + $var = array(); + $var['mess_recv'] = (isset($stats['mess_recv']) ? $stats['mess_recv'] : "U"); + $var['mess_rejected'] = (isset($stats['mess_rejected']) ? $stats['mess_rejected'] : "U"); + $var['mess_relay'] = (isset($stats['mess_relay']) ? $stats['mess_relay'] : "U"); + $var['mess_sent'] = (isset($stats['mess_sent']) ? $stats['mess_sent'] : "U"); + $var['mess_waiting'] = (isset($stats['mess_waiting']) ? $stats['mess_waiting'] : "U"); + $var['spam'] = (isset($stats['spam']) ? $stats['spam'] : "U"); + $var['virus'] = (isset($stats['virus']) ? $stats['virus'] : "U"); + foreach ($var as $item=>$count) { + echo $count."\n"; + } + } + + function clearStats($mailstats) { + if (file_exists($mailstats)) { + $fp = fopen($mailstats, 'w'); + fwrite($fp, "mess_recv:0\n"); + fwrite($fp, "mess_rejected:0\n"); + fwrite($fp, "mess_relay:0\n"); + fwrite($fp, "mess_sent:0\n"); + fwrite($fp, "mess_waiting:0\n"); + fwrite($fp, "spam:0\n"); + fwrite($fp, "virus:0\n"); + fclose($fp); + } + } + + doSNMPv2($mailstats); + //clearStats($mailstats); + +?> diff --git a/snmp/mysql-stats b/snmp/mysql-stats new file mode 100755 index 0000000..d191bdb --- /dev/null +++ b/snmp/mysql-stats @@ -0,0 +1,131 @@ +#!/usr/bin/env python2 +import warnings +import re +warnings.filterwarnings(action="ignore", message='the sets module is deprecated') +import sets +import MySQLdb +import base64 +conn = MySQLdb.connect(host='', + user='', + passwd='', + db='') + +cursor = conn.cursor () + + +cursor.execute ("SHOW GLOBAL STATUS") +rows = cursor.fetchall() + +datavariables = { + 'Command Counters': ['Com_delete','Com_insert','Com_insert_select','Com_load','Com_replace','Com_replace_select', 'Com_select', 'Com_update', 'Com_update_multi'], + 'Connections': ['max_connections', 'Max_used_connections', 'Aborted_clients', 'Aborted_connects','Threads_connected','Connections'], + 'Files and Tables': ['table_open_cache','Open_files','Open_tables','Opened_tables'], + 'InnoDB Buffer Pool': ['ib_bpool_size','ib_bpool_dbpages', 'ib_bpool_free','ib_bpool_modpages'], + 'InnoDB Buffer Pool Activity': ['ib_bpool_read','ib_bpool_created', 'ib_bpool_written'], + 'InnoDB Insert Buffer': ['ib_ibuf_inserts','ib_ibuf_merged_rec', 'ib_ibuf_merges'], + 'InnoDB IO': ['ib_io_read','ib_io_write','ib_io_log', 'ib_io_fsync'], + 'InnoDB IO Pending': ['ib_iop_log','ib_iop_sync', 'ib_iop_flush_log', 'ib_iop_flush_bpool', 'ib_iop_ibuf_aio','ib_iop_aioread','ib_iop_aiowrite'], + 'InnoDB Log': ['innodb_log_buffer_size','ib_log_flush','ib_log_written'], + 'InnoDB Row Operations': ['Innodb_rows_deleted','Innodb_rows_inserted','Innodb_rows_read','Innodb_rows_updated'], + 'InnoDB Semaphores': ['ib_spin_rounds','ib_spin_waits','ib_os_waits'], + 'InnoDB Transactions': ['ib_tnx'], + 'MyISAM Indexes': ['Key_read_requests','Key_reads','Key_write_requests','Key_writes'], + 'Network Traffic': ['Bytes_received','Bytes_sent'], + 'Query Cache': ['Qcache_queries_in_cache','Qcache_hits','Qcache_inserts','Qcache_not_cached','Qcache_lowmem_prunes'], + 'Query Cache Memory': ['query_cache_size','Qcache_free_memory'], + 'Select Types': ['Select_full_join','Select_full_range_join','Select_range','Select_range_check','Select_scan'], + 'Slow Queries': ['Slow_queries'], + 'Sorts': ['Sort_rows','Sort_range','Sort_merge_passes','Sort_scan'], + 'Table Locks': ['Table_locks_immediate','Table_locks_waited'], + 'Temporary Objects': ['Created_tmp_disk_tables','Created_tmp_tables','Created_tmp_files'] + } + +data = {} +for row in rows: + data[row[0]] = row[1] + +cursor = "" +cursor = conn.cursor () +cursor.execute ("SHOW VARIABLES") +rows = cursor.fetchall() + +for row in rows: + data[row[0]] = row[1] + +cursor = "" +cursor = conn.cursor() +cursor.execute("SHOW ENGINE INNODB STATUS") +rows = cursor.fetchall() + +for row in rows: + for line in row[2].split("\n"): + ib_bpool_size = re.match(r"Buffer\spool\ssize\s+(\d+)", line) + ib_bpool_free = re.match(r"Free\sbuffers\s+(\d+)", line) + ib_bpool_dbpages = re.match(r"Database\spages\s+(\d+)", line) + ib_bpool_modpages = re.match(r"Modified\sdb\spages\s+(\d+)", line) + ib_b_reg = re.match(r"Pages\sread\s(\d+),\screated\s(\d+),\swritten (\d+)", line) + ib_insert_buffer = re.match(r"(\d+)\sinserts,\s(\d+)\smerged\srecs,\s(\d+)", line) + ib_io = re.match(r"(\d+)\sOS\sfile\sreads,\s(\d+)\sOS\sfile\swrites,\s(\d+)\sOS\sfsyncs", line) + ib_io_log = re.match(r"(\d+)\slog\si\/o's\sdone.*", line) + ib_io_p1 = re.match(r"Pending\snormal\saio\sreads:\s(\d+),\saio\swrites:\s(\d+),", line) + ib_io_p2 = re.match(r"\s?ibuf\saio\sreads:\s(\d+),\slog\si\/o's:\s(\d+),\ssync\si\/o's:\s(\d+)", line) + ib_io_p3 = re.match(r"\s?Pending\sflushes\s\(fsync\)\slog:\s(\d+);\sbuffer\spool:\s(\d+)\s?", line) + ib_log_p1 = re.match(r"\s?Log\ssequence\snumber\s([[a-fA-F\d]+)(?: (\d+))?", line) + ib_log_p2 = re.match(r"\s?Log\sflushed\sup\sto\s+([[a-fA-F\d]+)(?: (\d+))?", line) + ib_semaphore = re.match(r"\s?Mutex\sspin\swaits\s(\d+),\srounds\s(\d+),\sOS waits\s(\d+)", line) + ib_tnx = re.match(r"\s?Trx\sid\scounter\s([[a-fA-F\d]+)(?: (\d+))?", line) + + if ib_bpool_size: + data['ib_bpool_size'] = ib_bpool_size.group(1) + elif ib_bpool_free: + data['ib_bpool_free'] = ib_bpool_free.group(1) + elif ib_bpool_dbpages: + data['ib_bpool_dbpages'] = ib_bpool_dbpages.group(1) + elif ib_bpool_modpages: + data['ib_bpool_modpages'] = ib_bpool_modpages.group(1) + elif ib_insert_buffer: + data['ib_ibuf_inserts'] = ib_insert_buffer.group(1) + data['ib_ibuf_merged_rec'] = ib_insert_buffer.group(2) + data['ib_ibuf_merges'] = ib_insert_buffer.group(3) + elif ib_io: + data['ib_io_read'] = ib_io.group(1) + data['ib_io_write'] = ib_io.group(2) + data['ib_io_fsync'] = ib_io.group(3) + elif ib_io_log: + data['ib_io_log'] = ib_io_log.group(1) + elif ib_io_p1: + data['ib_iop_aioread'] = ib_io_p1.group(1) + data['ib_iop_aiowrite'] = ib_io_p1.group(2) + elif ib_io_p2: + data['ib_iop_ibuf_aio'] = ib_io_p2.group(1) + data['ib_iop_log'] = ib_io_p2.group(2) + data['ib_iop_sync'] = ib_io_p2.group(3) + elif ib_io_p3: + data['ib_iop_flush_log'] = ib_io_p3.group(1) + data['ib_iop_flush_bpool'] = ib_io_p3.group(2) + elif ib_log_p1: + data['ib_log_written'] = ib_log_p1.group(1) + if ib_log_p1.group(2): + data['ib_log_written'] = int(data['ib_log_written']) + int(ib_log_p1.group(2)) + elif ib_log_p2: + data['ib_log_flush'] = ib_log_p2.group(1) + if ib_log_p2.group(2): + data['ib_log_flush'] = int(data['ib_log_flush']) + int(ib_log_p2.group(2)) + elif ib_semaphore: + data['ib_spin_waits'] = ib_semaphore.group(1) + data['ib_spin_rounds'] = ib_semaphore.group(2) + data['ib_os_waits'] = ib_semaphore.group(3) + elif ib_tnx: + data['ib_tnx'] = ib_tnx.group(1) + if ib_tnx.group(2): + data['ib_tnx'] = int(data['ib_tnx']) + int(ib_tnx.group(2)) + elif ib_b_reg: + data['ib_bpool_read'] = ib_b_reg.group(1) + data['ib_bpool_created'] = ib_b_reg.group(2) + data['ib_bpool_written'] = ib_b_reg.group(3) + + +for category in datavariables: + for variable in datavariables[category]: + if variable in data: + print data[variable] diff --git a/snmp/mysql_stats.php b/snmp/mysql_stats.php new file mode 100755 index 0000000..89522a6 --- /dev/null +++ b/snmp/mysql_stats.php @@ -0,0 +1,1247 @@ + true, # Do you want to check InnoDB statistics? + 'master' => true, # Do you want to check binary logging? + 'slave' => true, # Do you want to check slave status? + 'procs' => true, # Do you want to check SHOW PROCESSLIST? +); + +$use_ss = FALSE; # Whether to use the script server or not +$debug = FALSE; # Define whether you want debugging behavior. +$debug_log = FALSE; # If $debug_log is a filename, it'll be used. + +# ============================================================================ +# You should not need to change anything below this line. +# ============================================================================ +$version = "1.1.7"; + +# ============================================================================ +# Include settings from an external config file (issue 39). +# ============================================================================ +if (file_exists(__FILE__ . '.cnf' ) ) { + require(__FILE__ . '.cnf'); +} + +# Make this a happy little script even when there are errors. +$no_http_headers = true; +ini_set('implicit_flush', false); # No output, ever. +if ($debug ) { + ini_set('display_errors', true); + ini_set('display_startup_errors', true); + ini_set('error_reporting', 2147483647); +} +else { + ini_set('error_reporting', E_ERROR); +} +ob_start(); # Catch all output such as notices of undefined array indexes. +function error_handler($errno, $errstr, $errfile, $errline) { + print("$errstr at $errfile line $errline\n"); + debug("$errstr at $errfile line $errline"); +} +# ============================================================================ +# Set up the stuff we need to be called by the script server. +# ============================================================================ +if ($use_ss ) { + if (file_exists( dirname(__FILE__) . "/../include/global.php") ) { + # See issue 5 for the reasoning behind this. + debug("including " . dirname(__FILE__) . "/../include/global.php"); + include_once(dirname(__FILE__) . "/../include/global.php"); + } + elseif (file_exists( dirname(__FILE__) . "/../include/config.php" ) ) { + # Some Cacti installations don't have global.php. + debug("including " . dirname(__FILE__) . "/../include/config.php"); + include_once(dirname(__FILE__) . "/../include/config.php"); + } +} + +# ============================================================================ +# Make sure we can also be called as a script. +# ============================================================================ +if (!isset($called_by_script_server)) { + debug($_SERVER["argv"]); + array_shift($_SERVER["argv"]); # Strip off this script's filename + $options = parse_cmdline($_SERVER["argv"]); + validate_options($options); + $result = ss_get_mysql_stats($options); + + debug($result); + if (!$debug ) { + # Throw away the buffer, which ought to contain only errors. + ob_end_clean(); + } + else { + ob_end_flush(); # In debugging mode, print out the errors. + } + + # Split the result up and extract only the desired parts of it. + $options['items'] = ""; + $wanted = explode(',', $options['items']); + $output = array(); + foreach ( explode(' ', $result) as $item ) { + if (in_array(substr($item, 0, 2), $wanted) ) { + $output[] = $item; + } + list($short, $val) = explode(":", $item); + echo(strtolower($short).":".strtolower($val)."\n"); + } + debug(array("Final result", $output)); + print(implode(' ', $output)); +} + +# ============================================================================ +# End "if file was not included" section. +# ============================================================================ +} + +# ============================================================================ +# Work around the lack of array_change_key_case in older PHP. +# ============================================================================ +if (!function_exists('array_change_key_case') ) { + function array_change_key_case($arr) { + $res = array(); + foreach ( $arr as $key => $val ) { + $res[strtolower($key)] = $val; + } + return $res; + } +} + +# ============================================================================ +# Validate that the command-line options are here and correct +# ============================================================================ +function validate_options($options) { + debug($options); + $opts = array('items', 'user', 'pass', 'heartbeat', 'nocache', 'port'); + # Required command-line options + foreach ( array() as $option ) { + if (!isset($options[$option]) || !$options[$option] ) { + usage("Required option --$option is missing"); + } + } + foreach ( $options as $key => $val ) { + if (!in_array($key, $opts) ) { + usage("Unknown option --$key"); + } + } +} + +# ============================================================================ +# Print out a brief usage summary +# ============================================================================ +function usage($message) { + global $mysql_host, $mysql_user, $mysql_pass, $mysql_port, $heartbeat; + + $usage = << --items [OPTION] + + --host Hostname to connect to; use host:port syntax to specify a port + Use :/path/to/socket if you want to connect via a UNIX socket + --items Comma-separated list of the items whose data you want + --user MySQL username; defaults to $mysql_user if not given + --pass MySQL password; defaults to $mysql_pass if not given + --heartbeat MySQL heartbeat table; defaults to '$heartbeat' (see mk-heartbeat) + --nocache Do not cache results in a file + --port MySQL port; defaults to $mysql_port if not given + --mysql_ssl Add the MYSQL_CLIENT_SSL flag to mysql_connect() call + +EOF; + die($usage); +} + +# ============================================================================ +# Parse command-line arguments, in the format --arg value --arg value, and +# return them as an array ( arg => value ) +# ============================================================================ +function parse_cmdline( $args ) { + $result = array(); + $cur_arg = ''; + foreach ($args as $val) { + if (strpos($val, '--') === 0 ) { + if (strpos($val, '--no') === 0 ) { + # It's an option without an argument, but it's a --nosomething so + # it's OK. + $result[substr($val, 2)] = 1; + $cur_arg = ''; + } + elseif ($cur_arg ) { # Maybe the last --arg was an option with no arg + if ($cur_arg == '--user' || $cur_arg == '--pass' || $cur_arg == '--port' ) { + # Special case because Cacti will pass these without an arg + $cur_arg = ''; + } + else { + die("No arg: $cur_arg\n"); + } + } + else { + $cur_arg = $val; + } + } + else { + $result[substr($cur_arg, 2)] = $val; + $cur_arg = ''; + } + } + if ($cur_arg && ($cur_arg != '--user' && $cur_arg != '--pass' && $cur_arg != '--port') ) { + die("No arg: $cur_arg\n"); + } + debug($result); + return $result; +} + +# ============================================================================ +# This is the main function. Some parameters are filled in from defaults at the +# top of this file. +# ============================================================================ +function ss_get_mysql_stats( $options ) { + # Process connection options and connect to MySQL. + global $debug, $mysql_user, $mysql_pass, $heartbeat, $cache_dir, $poll_time, + $chk_options, $mysql_host, $mysql_port, $mysql_ssl; + + # Connect to MySQL. + $user = isset($options['user']) ? $options['user'] : $mysql_user; + $pass = isset($options['pass']) ? $options['pass'] : $mysql_pass; + $port = isset($options['port']) ? $options['port'] : $mysql_port; + $host = isset($options['host']) ? $options['host'] : $mysql_host; + + $heartbeat = isset($options['heartbeat']) ? $options['heartbeat'] : $heartbeat; + # If there is a port, or if it's a non-standard port, we add ":$port" to the + # hostname. + $host_str = $host + . $port != 3306 ? ":$port" : ''; + debug(array('connecting to', $host_str, $user, $pass)); + if (!extension_loaded('mysql') ) { + debug("The MySQL extension is not loaded"); + die("The MySQL extension is not loaded"); + } + if ($mysql_ssl || (isset($options['mysql_ssl']) && $options['mysql_ssl']) ) { + $conn = mysql_connect($host_str, $user, $pass, true, MYSQL_CLIENT_SSL); + } + else { + $conn = mysql_connect($host_str, $user, $pass); + } + if (!$conn ) { + die("MySQL: " . mysql_error()); + } + + $sanitized_host = str_replace(array(":", "/"), array("", "_"), $host); + $cache_file = "$cache_dir/$sanitized_host-mysql_cacti_stats.txt" + . $port != 3306 ? ":$port" : ''; + debug("Cache file is $cache_file"); + + # First, check the cache. + $fp = null; + if (!isset($options['nocache']) ) { + if ($fp = fopen($cache_file, 'a+') ) { + $locked = flock($fp, 1); # LOCK_SH + if ($locked ) { + if (filesize($cache_file) > 0 + && filectime($cache_file) + ($poll_time/2) > time() + && ($arr = file($cache_file)) + ) {# The cache file is good to use. + debug("Using the cache file"); + fclose($fp); + return $arr[0]; + } + else { + debug("The cache file seems too small or stale"); + # Escalate the lock to exclusive, so we can write to it. + if (flock($fp, 2) ) { # LOCK_EX + # We might have blocked while waiting for that LOCK_EX, and + # another process ran and updated it. Let's see if we can just + # return the data now: + if (filesize($cache_file) > 0 + && filectime($cache_file) + ($poll_time/2) > time() + && ($arr = file($cache_file)) + ) {# The cache file is good to use. + debug("Using the cache file"); + fclose($fp); + return $arr[0]; + } + ftruncate($fp, 0); # Now it's ready for writing later. + } + } + } + else { + debug("Couldn't lock the cache file, ignoring it."); + $fp = null; + } + } + } + else { + $fp = null; + debug("Couldn't open the cache file"); + } + + # Set up variables. + $status = array( # Holds the result of SHOW STATUS, SHOW INNODB STATUS, etc + # Define some indexes so they don't cause errors with += operations. + 'relay_log_space' => null, + 'binary_log_space' => null, + 'current_transactions' => null, + 'locked_transactions' => null, + 'active_transactions' => null, + 'innodb_locked_tables' => null, + 'innodb_tables_in_use' => null, + 'innodb_lock_structs' => null, + 'innodb_lock_wait_secs' => null, + 'innodb_sem_waits' => null, + 'innodb_sem_wait_time_ms'=> null, + # Values for the 'state' column from SHOW PROCESSLIST (converted to + # lowercase, with spaces replaced by underscores) + 'State_closing_tables' => null, + 'State_copying_to_tmp_table' => null, + 'State_end' => null, + 'State_freeing_items' => null, + 'State_init' => null, + 'State_locked' => null, + 'State_login' => null, + 'State_preparing' => null, + 'State_reading_from_net' => null, + 'State_sending_data' => null, + 'State_sorting_result' => null, + 'State_statistics' => null, + 'State_updating' => null, + 'State_writing_to_net' => null, + 'State_none' => null, + 'State_other' => null, # Everything not listed above + ); + + # Get SHOW STATUS and convert the name-value array into a simple + # associative array. + $result = run_query("SHOW /*!50002 GLOBAL */ STATUS", $conn); + foreach ( $result as $row ) { + $status[$row[0]] = $row[1]; + } + + # Get SHOW VARIABLES and do the same thing, adding it to the $status array. + $result = run_query("SHOW VARIABLES", $conn); + foreach ( $result as $row ) { + $status[$row[0]] = $row[1]; + } + + # Get SHOW SLAVE STATUS, and add it to the $status array. + if ($chk_options['slave'] ) { + $result = run_query("SHOW SLAVE STATUS", $conn); + $slave_status_rows_gotten = 0; + foreach ( $result as $row ) { + $slave_status_rows_gotten++; + # Must lowercase keys because different MySQL versions have different + # lettercase. + $row = array_change_key_case($row, CASE_LOWER); + $status['relay_log_space'] = $row['relay_log_space']; + $status['slave_lag'] = $row['seconds_behind_master']; + + # Check replication heartbeat, if present. + if ($heartbeat ) { + $result2 = run_query( + "SELECT GREATEST(0, UNIX_TIMESTAMP() - UNIX_TIMESTAMP(ts) - 1)" + . " AS delay FROM $heartbeat WHERE id = 1", $conn); + $slave_delay_rows_gotten = 0; + foreach ( $result2 as $row2 ) { + $slave_delay_rows_gotten++; + if ($row2 && is_array($row2) + && array_key_exists('delay', $row2) ) + { + $status['slave_lag'] = $row2['delay']; + } + else { + debug("Couldn't get slave lag from $heartbeat"); + } + } + if ($slave_delay_rows_gotten == 0 ) { + debug("Got nothing from heartbeat query"); + } + } + + # Scale slave_running and slave_stopped relative to the slave lag. + $status['slave_running'] = ($row['slave_sql_running'] == 'Yes') + ? $status['slave_lag'] : 0; + $status['slave_stopped'] = ($row['slave_sql_running'] == 'Yes') + ? 0 : $status['slave_lag']; + } + if ($slave_status_rows_gotten == 0 ) { + debug("Got nothing from SHOW SLAVE STATUS"); + } + } + + # Get SHOW MASTER STATUS, and add it to the $status array. + if ($chk_options['master'] + && array_key_exists('log_bin', $status) + && $status['log_bin'] == 'ON' + ) { # See issue #8 + $binlogs = array(0); + $result = run_query("SHOW MASTER LOGS", $conn); + foreach ( $result as $row ) { + $row = array_change_key_case($row, CASE_LOWER); + # Older versions of MySQL may not have the File_size column in the + # results of the command. Zero-size files indicate the user is + # deleting binlogs manually from disk (bad user! bad!). + if (array_key_exists('file_size', $row) && $row['file_size'] > 0 ) { + $binlogs[] = $row['file_size']; + } + } + if (count($binlogs)) { + $status['binary_log_space'] = to_int(array_sum($binlogs)); + } + } + + # Get SHOW PROCESSLIST and aggregate it by state, then add it to the array + # too. + if ($chk_options['procs'] ) { + $result = run_query('SHOW PROCESSLIST', $conn); + foreach ( $result as $row ) { + $state = $row['State']; + if (is_null($state) ) { + $state = 'NULL'; + } + if ($state == '' ) { + $state = 'none'; + } + $state = str_replace(' ', '_', strtolower($state)); + if (array_key_exists("State_$state", $status) ) { + increment($status, "State_$state", 1); + } + else { + increment($status, "State_other", 1); + } + } + } + + # Get SHOW INNODB STATUS and extract the desired metrics from it, then add + # those to the array too. + if ($chk_options['innodb'] + && array_key_exists('have_innodb', $status) + && $status['have_innodb'] == 'YES' + ) { + $result = run_query("SHOW /*!50000 ENGINE*/ INNODB STATUS", $conn); + $istatus_text = $result[0]['Status']; + $istatus_vals = get_innodb_array($istatus_text); + + # Override values from InnoDB parsing with values from SHOW STATUS, + # because InnoDB status might not have everything and the SHOW STATUS is + # to be preferred where possible. + $overrides = array( + 'Innodb_buffer_pool_pages_data' => 'database_pages', + 'Innodb_buffer_pool_pages_dirty' => 'modified_pages', + 'Innodb_buffer_pool_pages_free' => 'free_pages', + 'Innodb_buffer_pool_pages_total' => 'pool_size', + 'Innodb_data_fsyncs' => 'file_fsyncs', + 'Innodb_data_pending_reads' => 'pending_normal_aio_reads', + 'Innodb_data_pending_writes' => 'pending_normal_aio_writes', + 'Innodb_os_log_pending_fsyncs' => 'pending_log_flushes', + 'Innodb_pages_created' => 'pages_created', + 'Innodb_pages_read' => 'pages_read', + 'Innodb_pages_written' => 'pages_written', + 'Innodb_rows_deleted' => 'rows_deleted', + 'Innodb_rows_inserted' => 'rows_inserted', + 'Innodb_rows_read' => 'rows_read', + 'Innodb_rows_updated' => 'rows_updated', + ); + + # If the SHOW STATUS value exists, override... + foreach ( $overrides as $key => $val ) { + if (array_key_exists($key, $status) ) { + debug("Override $key"); + $istatus_vals[$val] = $status[$key]; + } + } + + # Now copy the values into $status. + foreach ( $istatus_vals as $key => $val ) { + $status[$key] = $istatus_vals[$key]; + } + } + + # Make table_open_cache backwards-compatible (issue 63). + if (array_key_exists('table_open_cache', $status) ) { + $status['table_cache'] = $status['table_open_cache']; + } + + # Compute how much of the key buffer is used and unflushed (issue 127). + $status['Key_buf_bytes_used'] + = big_sub($status['key_buffer_size'], + big_multiply($status['Key_blocks_unused'], + $status['key_cache_block_size'])); + $status['Key_buf_bytes_unflushed'] + = big_multiply($status['Key_blocks_not_flushed'], + $status['key_cache_block_size']); + + if (array_key_exists('unflushed_log', $status) + && $status['unflushed_log'] + ) { + # TODO: I'm not sure what the deal is here; need to debug this. But the + # unflushed log bytes spikes a lot sometimes and it's impossible for it to + # be more than the log buffer. + debug("Unflushed log: $status[unflushed_log]"); + $status['unflushed_log'] + = max($status['unflushed_log'], $status['innodb_log_buffer_size']); + } + + # Define the variables to output. I use shortened variable names so maybe + # it'll all fit in 1024 bytes for Cactid and Spine's benefit. This list must + # come right after the word MAGIC_VARS_DEFINITIONS. The Perl script parses + # it and uses it as a Perl variable. + $keys = array( + 'Key_read_requests' => 'a0', + 'Key_reads' => 'a1', + 'Key_write_requests' => 'a2', + 'Key_writes' => 'a3', + 'history_list' => 'a4', + 'innodb_transactions' => 'a5', + 'read_views' => 'a6', + 'current_transactions' => 'a7', + 'locked_transactions' => 'a8', + 'active_transactions' => 'a9', + 'pool_size' => 'aa', + 'free_pages' => 'ab', + 'database_pages' => 'ac', + 'modified_pages' => 'ad', + 'pages_read' => 'ae', + 'pages_created' => 'af', + 'pages_written' => 'ag', + 'file_fsyncs' => 'ah', + 'file_reads' => 'ai', + 'file_writes' => 'aj', + 'log_writes' => 'ak', + 'pending_aio_log_ios' => 'al', + 'pending_aio_sync_ios' => 'am', + 'pending_buf_pool_flushes' => 'an', + 'pending_chkp_writes' => 'ao', + 'pending_ibuf_aio_reads' => 'ap', + 'pending_log_flushes' => 'aq', + 'pending_log_writes' => 'ar', + 'pending_normal_aio_reads' => 'as', + 'pending_normal_aio_writes' => 'at', + 'ibuf_inserts' => 'au', + 'ibuf_merged' => 'av', + 'ibuf_merges' => 'aw', + 'spin_waits' => 'ax', + 'spin_rounds' => 'ay', + 'os_waits' => 'az', + 'rows_inserted' => 'b0', + 'rows_updated' => 'b1', + 'rows_deleted' => 'b2', + 'rows_read' => 'b3', + 'Table_locks_waited' => 'b4', + 'Table_locks_immediate' => 'b5', + 'Slow_queries' => 'b6', + 'Open_files' => 'b7', + 'Open_tables' => 'b8', + 'Opened_tables' => 'b9', + 'innodb_open_files' => 'ba', + 'open_files_limit' => 'bb', + 'table_cache' => 'bc', + 'Aborted_clients' => 'bd', + 'Aborted_connects' => 'be', + 'Max_used_connections' => 'bf', + 'Slow_launch_threads' => 'bg', + 'Threads_cached' => 'bh', + 'Threads_connected' => 'bi', + 'Threads_created' => 'bj', + 'Threads_running' => 'bk', + 'max_connections' => 'bl', + 'thread_cache_size' => 'bm', + 'Connections' => 'bn', + 'slave_running' => 'bo', + 'slave_stopped' => 'bp', + 'Slave_retried_transactions' => 'bq', + 'slave_lag' => 'br', + 'Slave_open_temp_tables' => 'bs', + 'Qcache_free_blocks' => 'bt', + 'Qcache_free_memory' => 'bu', + 'Qcache_hits' => 'bv', + 'Qcache_inserts' => 'bw', + 'Qcache_lowmem_prunes' => 'bx', + 'Qcache_not_cached' => 'by', + 'Qcache_queries_in_cache' => 'bz', + 'Qcache_total_blocks' => 'c0', + 'query_cache_size' => 'c1', + 'Questions' => 'c2', + 'Com_update' => 'c3', + 'Com_insert' => 'c4', + 'Com_select' => 'c5', + 'Com_delete' => 'c6', + 'Com_replace' => 'c7', + 'Com_load' => 'c8', + 'Com_update_multi' => 'c9', + 'Com_insert_select' => 'ca', + 'Com_delete_multi' => 'cb', + 'Com_replace_select' => 'cc', + 'Select_full_join' => 'cd', + 'Select_full_range_join' => 'ce', + 'Select_range' => 'cf', + 'Select_range_check' => 'cg', + 'Select_scan' => 'ch', + 'Sort_merge_passes' => 'ci', + 'Sort_range' => 'cj', + 'Sort_rows' => 'ck', + 'Sort_scan' => 'cl', + 'Created_tmp_tables' => 'cm', + 'Created_tmp_disk_tables' => 'cn', + 'Created_tmp_files' => 'co', + 'Bytes_sent' => 'cp', + 'Bytes_received' => 'cq', + 'innodb_log_buffer_size' => 'cr', + 'unflushed_log' => 'cs', + 'log_bytes_flushed' => 'ct', + 'log_bytes_written' => 'cu', + 'relay_log_space' => 'cv', + 'binlog_cache_size' => 'cw', + 'Binlog_cache_disk_use' => 'cx', + 'Binlog_cache_use' => 'cy', + 'binary_log_space' => 'cz', + 'innodb_locked_tables' => 'd0', + 'innodb_lock_structs' => 'd1', + 'State_closing_tables' => 'd2', + 'State_copying_to_tmp_table' => 'd3', + 'State_end' => 'd4', + 'State_freeing_items' => 'd5', + 'State_init' => 'd6', + 'State_locked' => 'd7', + 'State_login' => 'd8', + 'State_preparing' => 'd9', + 'State_reading_from_net' => 'da', + 'State_sending_data' => 'db', + 'State_sorting_result' => 'dc', + 'State_statistics' => 'dd', + 'State_updating' => 'de', + 'State_writing_to_net' => 'df', + 'State_none' => 'dg', + 'State_other' => 'dh', + 'Handler_commit' => 'di', + 'Handler_delete' => 'dj', + 'Handler_discover' => 'dk', + 'Handler_prepare' => 'dl', + 'Handler_read_first' => 'dm', + 'Handler_read_key' => 'dn', + 'Handler_read_next' => 'do', + 'Handler_read_prev' => 'dp', + 'Handler_read_rnd' => 'dq', + 'Handler_read_rnd_next' => 'dr', + 'Handler_rollback' => 'ds', + 'Handler_savepoint' => 'dt', + 'Handler_savepoint_rollback' => 'du', + 'Handler_update' => 'dv', + 'Handler_write' => 'dw', + # Some InnoDB stats added later... + 'innodb_tables_in_use' => 'dx', + 'innodb_lock_wait_secs' => 'dy', + 'hash_index_cells_total' => 'dz', + 'hash_index_cells_used' => 'e0', + 'total_mem_alloc' => 'e1', + 'additional_pool_alloc' => 'e2', + 'uncheckpointed_bytes' => 'e3', + 'ibuf_used_cells' => 'e4', + 'ibuf_free_cells' => 'e5', + 'ibuf_cell_count' => 'e6', + 'adaptive_hash_memory' => 'e7', + 'page_hash_memory' => 'e8', + 'dictionary_cache_memory' => 'e9', + 'file_system_memory' => 'ea', + 'lock_system_memory' => 'eb', + 'recovery_system_memory' => 'ec', + 'thread_hash_memory' => 'ed', + 'innodb_sem_waits' => 'ee', + 'innodb_sem_wait_time_ms' => 'ef', + 'Key_buf_bytes_unflushed' => 'eg', + 'Key_buf_bytes_used' => 'eh', + 'key_buffer_size' => 'ei', + 'Innodb_row_lock_time' => 'ej', + 'Innodb_row_lock_waits' => 'ek', + ); + + # Return the output. + $output = array(); + foreach ($keys as $key => $short ) { + # If the value isn't defined, return -1 which is lower than (most graphs') + # minimum value of 0, so it'll be regarded as a missing value. + $val = isset($status[$key]) ? $status[$key] : -1; + $output[] = "$short:$val"; + } + $result = implode(' ', $output); + if ($fp ) { + if (fwrite($fp, $result) === FALSE ) { + die("Can't write '$cache_file'"); + } + fclose($fp); + } + + return $result; + +} + +# ============================================================================ +# Given INNODB STATUS text, returns a key-value array of the parsed text. Each +# line shows a sample of the input for both standard InnoDB as you would find in +# MySQL 5.0, and XtraDB or enhanced InnoDB from Percona if applicable. Note +# that extra leading spaces are ignored due to trim(). +# ============================================================================ +function get_innodb_array($text) { + $results = array( + 'spin_waits' => array(), + 'spin_rounds' => array(), + 'os_waits' => array(), + 'pending_normal_aio_reads' => null, + 'pending_normal_aio_writes' => null, + 'pending_ibuf_aio_reads' => null, + 'pending_aio_log_ios' => null, + 'pending_aio_sync_ios' => null, + 'pending_log_flushes' => null, + 'pending_buf_pool_flushes' => null, + 'file_reads' => null, + 'file_writes' => null, + 'file_fsyncs' => null, + 'ibuf_inserts' => null, + 'ibuf_merged' => null, + 'ibuf_merges' => null, + 'log_bytes_written' => null, + 'unflushed_log' => null, + 'log_bytes_flushed' => null, + 'pending_log_writes' => null, + 'pending_chkp_writes' => null, + 'log_writes' => null, + 'pool_size' => null, + 'free_pages' => null, + 'database_pages' => null, + 'modified_pages' => null, + 'pages_read' => null, + 'pages_created' => null, + 'pages_written' => null, + 'queries_inside' => null, + 'queries_queued' => null, + 'read_views' => null, + 'rows_inserted' => null, + 'rows_updated' => null, + 'rows_deleted' => null, + 'rows_read' => null, + 'innodb_transactions' => null, + 'unpurged_txns' => null, + 'history_list' => null, + 'current_transactions' => null, + 'hash_index_cells_total' => null, + 'hash_index_cells_used' => null, + 'total_mem_alloc' => null, + 'additional_pool_alloc' => null, + 'last_checkpoint' => null, + 'uncheckpointed_bytes' => null, + 'ibuf_used_cells' => null, + 'ibuf_free_cells' => null, + 'ibuf_cell_count' => null, + 'adaptive_hash_memory' => null, + 'page_hash_memory' => null, + 'dictionary_cache_memory' => null, + 'file_system_memory' => null, + 'lock_system_memory' => null, + 'recovery_system_memory' => null, + 'thread_hash_memory' => null, + 'innodb_sem_waits' => null, + 'innodb_sem_wait_time_ms' => null, + ); + $txn_seen = FALSE; + foreach ( explode("\n", $text) as $line ) { + $line = trim($line); + $row = preg_split('/ +/', $line); + + # SEMAPHORES + if (strpos($line, 'Mutex spin waits') === 0 ) { + # Mutex spin waits 79626940, rounds 157459864, OS waits 698719 + # Mutex spin waits 0, rounds 247280272495, OS waits 316513438 + $results['spin_waits'][] = to_int($row[3]); + $results['spin_rounds'][] = to_int($row[5]); + $results['os_waits'][] = to_int($row[8]); + } + elseif (strpos($line, 'RW-shared spins') === 0 ) { + # RW-shared spins 3859028, OS waits 2100750; RW-excl spins 4641946, OS waits 1530310 + $results['spin_waits'][] = to_int($row[2]); + $results['spin_waits'][] = to_int($row[8]); + $results['os_waits'][] = to_int($row[5]); + $results['os_waits'][] = to_int($row[11]); + } + elseif (strpos($line, 'seconds the semaphore:') > 0) { + # --Thread 907205 has waited at handler/ha_innodb.cc line 7156 for 1.00 seconds the semaphore: + increment($results, 'innodb_sem_waits', 1); + increment($results, + 'innodb_sem_wait_time_ms', to_int($row[9]) * 1000); + } + + # TRANSACTIONS + elseif (strpos($line, 'Trx id counter') === 0 ) { + # The beginning of the TRANSACTIONS section: start counting + # transactions + # Trx id counter 0 1170664159 + # Trx id counter 861B144C + $results['innodb_transactions'] = make_bigint($row[3], $row[4]); + $txn_seen = TRUE; + } + elseif (strpos($line, 'Purge done for trx') === 0 ) { + # Purge done for trx's n:o < 0 1170663853 undo n:o < 0 0 + # Purge done for trx's n:o < 861B135D undo n:o < 0 + $purged_to = make_bigint($row[6], $row[7] == 'undo' ? null : $row[7]); + $results['unpurged_txns'] + = big_sub($results['innodb_transactions'], $purged_to); + } + elseif (strpos($line, 'History list length') === 0 ) { + # History list length 132 + $results['history_list'] = to_int($row[3]); + } + elseif ($txn_seen && strpos($line, '---TRANSACTION') === 0 ) { + # ---TRANSACTION 0, not started, process no 13510, OS thread id 1170446656 + increment($results, 'current_transactions', 1); + if (strpos($line, 'ACTIVE') > 0 ) { + increment($results, 'active_transactions', 1); + } + } + elseif ($txn_seen && strpos($line, '------- TRX HAS BEEN') === 0 ) { + # ------- TRX HAS BEEN WAITING 32 SEC FOR THIS LOCK TO BE GRANTED: + increment($results, 'innodb_lock_wait_secs', to_int($row[5])); + } + elseif (strpos($line, 'read views open inside InnoDB') > 0 ) { + # 1 read views open inside InnoDB + $results['read_views'] = to_int($row[0]); + } + elseif (strpos($line, 'mysql tables in use') === 0 ) { + # mysql tables in use 2, locked 2 + increment($results, 'innodb_tables_in_use', to_int($row[4])); + increment($results, 'innodb_locked_tables', to_int($row[6])); + } + elseif ($txn_seen && strpos($line, 'lock struct(s)') > 0 ) { + # 23 lock struct(s), heap size 3024, undo log entries 27 + # LOCK WAIT 12 lock struct(s), heap size 3024, undo log entries 5 + # LOCK WAIT 2 lock struct(s), heap size 368 + if (strpos($line, 'LOCK WAIT') === 0 ) { + increment($results, 'innodb_lock_structs', to_int($row[2])); + increment($results, 'locked_transactions', 1); + } + else { + increment($results, 'innodb_lock_structs', to_int($row[0])); + } + } + + # FILE I/O + elseif (strpos($line, ' OS file reads, ') > 0 ) { + # 8782182 OS file reads, 15635445 OS file writes, 947800 OS fsyncs + $results['file_reads'] = to_int($row[0]); + $results['file_writes'] = to_int($row[4]); + $results['file_fsyncs'] = to_int($row[8]); + } + elseif (strpos($line, 'Pending normal aio reads:') === 0 ) { + # Pending normal aio reads: 0, aio writes: 0, + $results['pending_normal_aio_reads'] = to_int($row[4]); + $results['pending_normal_aio_writes'] = to_int($row[7]); + } + elseif (strpos($line, 'ibuf aio reads') === 0 ) { + # ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0 + $results['pending_ibuf_aio_reads'] = to_int($row[3]); + $results['pending_aio_log_ios'] = to_int($row[6]); + $results['pending_aio_sync_ios'] = to_int($row[9]); + } + elseif (strpos($line, 'Pending flushes (fsync)') === 0 ) { + # Pending flushes (fsync) log: 0; buffer pool: 0 + $results['pending_log_flushes'] = to_int($row[4]); + $results['pending_buf_pool_flushes'] = to_int($row[7]); + } + + # INSERT BUFFER AND ADAPTIVE HASH INDEX + elseif (strpos($line, 'Ibuf for space 0: size ') === 0 ) { + # Older InnoDB code seemed to be ready for an ibuf per tablespace. It + # had two lines in the output. Newer has just one line, see below. + # Ibuf for space 0: size 1, free list len 887, seg size 889, is not empty + # Ibuf for space 0: size 1, free list len 887, seg size 889, + $results['ibuf_used_cells'] = to_int($row[5]); + $results['ibuf_free_cells'] = to_int($row[9]); + $results['ibuf_cell_count'] = to_int($row[12]); + } + elseif (strpos($line, 'Ibuf: size ') === 0 ) { + # Ibuf: size 1, free list len 4634, seg size 4636, + $results['ibuf_used_cells'] = to_int($row[2]); + $results['ibuf_free_cells'] = to_int($row[6]); + $results['ibuf_cell_count'] = to_int($row[9]); + } + elseif (strpos($line, ' merged recs, ') > 0 ) { + # 19817685 inserts, 19817684 merged recs, 3552620 merges + $results['ibuf_inserts'] = to_int($row[0]); + $results['ibuf_merged'] = to_int($row[2]); + $results['ibuf_merges'] = to_int($row[5]); + } + elseif (strpos($line, 'Hash table size ') === 0 ) { + # In some versions of InnoDB, the used cells is omitted. + # Hash table size 4425293, used cells 4229064, .... + # Hash table size 57374437, node heap has 72964 buffer(s) <-- no used cells + $results['hash_index_cells_total'] = to_int($row[3]); + $results['hash_index_cells_used'] + = strpos($line, 'used cells') > 0 ? to_int($row[6]) : '0'; + } + + # LOG + elseif (strpos($line, " log i/o's done, ") > 0 ) { + # 3430041 log i/o's done, 17.44 log i/o's/second + # 520835887 log i/o's done, 17.28 log i/o's/second, 518724686 syncs, 2980893 checkpoints + # TODO: graph syncs and checkpoints + $results['log_writes'] = to_int($row[0]); + } + elseif (strpos($line, " pending log writes, ") > 0 ) { + # 0 pending log writes, 0 pending chkp writes + $results['pending_log_writes'] = to_int($row[0]); + $results['pending_chkp_writes'] = to_int($row[4]); + } + elseif (strpos($line, "Log sequence number") === 0 ) { + # This number is NOT printed in hex in InnoDB plugin. + # Log sequence number 13093949495856 //plugin + # Log sequence number 125 3934414864 //normal + $results['log_bytes_written'] + = isset($row[4]) + ? make_bigint($row[3], $row[4]) + : to_int($row[3]); + } + elseif (strpos($line, "Log flushed up to") === 0 ) { + # This number is NOT printed in hex in InnoDB plugin. + # Log flushed up to 13093948219327 + # Log flushed up to 125 3934414864 + $results['log_bytes_flushed'] + = isset($row[5]) + ? make_bigint($row[4], $row[5]) + : to_int($row[4]); + } + elseif (strpos($line, "Last checkpoint at") === 0 ) { + # Last checkpoint at 125 3934293461 + $results['last_checkpoint'] + = isset($row[4]) + ? make_bigint($row[3], $row[4]) + : to_int($row[3]); + } + + # BUFFER POOL AND MEMORY + elseif (strpos($line, "Total memory allocated") === 0 ) { + # Total memory allocated 29642194944; in additional pool allocated 0 + $results['total_mem_alloc'] = to_int($row[3]); + $results['additional_pool_alloc'] = to_int($row[8]); + } + elseif (strpos($line, 'Adaptive hash index ') === 0 ) { + # Adaptive hash index 1538240664 (186998824 + 1351241840) + $results['adaptive_hash_memory'] = to_int($row[3]); + } + elseif (strpos($line, 'Page hash ') === 0 ) { + # Page hash 11688584 + $results['page_hash_memory'] = to_int($row[2]); + } + elseif (strpos($line, 'Dictionary cache ') === 0 ) { + # Dictionary cache 145525560 (140250984 + 5274576) + $results['dictionary_cache_memory'] = to_int($row[2]); + } + elseif (strpos($line, 'File system ') === 0 ) { + # File system 313848 (82672 + 231176) + $results['file_system_memory'] = to_int($row[2]); + } + elseif (strpos($line, 'Lock system ') === 0 ) { + # Lock system 29232616 (29219368 + 13248) + $results['lock_system_memory'] = to_int($row[2]); + } + elseif (strpos($line, 'Recovery system ') === 0 ) { + # Recovery system 0 (0 + 0) + $results['recovery_system_memory'] = to_int($row[2]); + } + elseif (strpos($line, 'Threads ') === 0 ) { + # Threads 409336 (406936 + 2400) + $results['thread_hash_memory'] = to_int($row[1]); + } + elseif (strpos($line, 'innodb_io_pattern ') === 0 ) { + # innodb_io_pattern 0 (0 + 0) + $results['innodb_io_pattern_memory'] = to_int($row[1]); + } + elseif (strpos($line, "Buffer pool size ") === 0 ) { + # The " " after size is necessary to avoid matching the wrong line: + # Buffer pool size 1769471 + # Buffer pool size, bytes 28991012864 + $results['pool_size'] = to_int($row[3]); + } + elseif (strpos($line, "Free buffers") === 0 ) { + # Free buffers 0 + $results['free_pages'] = to_int($row[2]); + } + elseif (strpos($line, "Database pages") === 0 ) { + # Database pages 1696503 + $results['database_pages'] = to_int($row[2]); + } + elseif (strpos($line, "Modified db pages") === 0 ) { + # Modified db pages 160602 + $results['modified_pages'] = to_int($row[3]); + } + elseif (strpos($line, "Pages read ahead") === 0 ) { + # Must do this BEFORE the next test, otherwise it'll get fooled by this + # line from the new plugin (see samples/innodb-015.txt): + # Pages read ahead 0.00/s, evicted without access 0.06/s + # TODO: No-op for now, see issue 134. + } + elseif (strpos($line, "Pages read") === 0 ) { + # Pages read 15240822, created 1770238, written 21705836 + $results['pages_read'] = to_int($row[2]); + $results['pages_created'] = to_int($row[4]); + $results['pages_written'] = to_int($row[6]); + } + + # ROW OPERATIONS + elseif (strpos($line, 'Number of rows inserted') === 0 ) { + # Number of rows inserted 50678311, updated 66425915, deleted 20605903, read 454561562 + $results['rows_inserted'] = to_int($row[4]); + $results['rows_updated'] = to_int($row[6]); + $results['rows_deleted'] = to_int($row[8]); + $results['rows_read'] = to_int($row[10]); + } + elseif (strpos($line, " queries inside InnoDB, ") > 0 ) { + # 0 queries inside InnoDB, 0 queries in queue + $results['queries_inside'] = to_int($row[0]); + $results['queries_queued'] = to_int($row[4]); + } + } + + foreach ( array('spin_waits', 'spin_rounds', 'os_waits') as $key ) { + $results[$key] = to_int(array_sum($results[$key])); + } + $results['unflushed_log'] + = big_sub($results['log_bytes_written'], $results['log_bytes_flushed']); + $results['uncheckpointed_bytes'] + = big_sub($results['log_bytes_written'], $results['last_checkpoint']); + + +# foreach ($results as $key => $value) { +# echo(strtolower($key).":".strtolower($value)."\n"); +# } + + + return $results; +} + + +# ============================================================================ +# Returns a bigint from two ulint or a single hex number. This is tested in +# t/mysql_stats.php and copied, without tests, to ss_get_by_ssh.php. +# ============================================================================ +function make_bigint ($hi, $lo = null) { + debug(array($hi, $lo)); + if (is_null($lo) ) { + # Assume it is a hex string representation. + return base_convert($hi, 16, 10); + } + else { + $hi = $hi ? $hi : '0'; # Handle empty-string or whatnot + $lo = $lo ? $lo : '0'; + return big_add(big_multiply($hi, 4294967296), $lo); + } +} + +# ============================================================================ +# Extracts the numbers from a string. You can't reliably do this by casting to +# an int, because numbers that are bigger than PHP's int (varies by platform) +# will be truncated. And you can't use sprintf(%u) either, because the maximum +# value that will return on some platforms is 4022289582. So this just handles +# them as a string instead. It extracts digits until it finds a non-digit and +# quits. This is tested in t/mysql_stats.php and copied, without tests, to +# ss_get_by_ssh.php. +# ============================================================================ +function to_int ( $str ) { + debug($str); + global $debug; + preg_match('{(\d+)}', $str, $m); + if (isset($m[1]) ) { + return $m[1]; + } + elseif ($debug ) { + print_r(debug_backtrace()); + } + else { + return 0; + } +} + +# ============================================================================ +# Wrap mysql_query in error-handling, and instead of returning the result, +# return an array of arrays in the result. +# ============================================================================ +function run_query($sql, $conn) { + global $debug; + debug($sql); + $result = @mysql_query($sql, $conn); + if ($debug ) { + $error = @mysql_error($conn); + if ($error ) { + debug(array($sql, $error)); + die("SQLERR $error in $sql"); + } + } + $array = array(); + while ( $row = @mysql_fetch_array($result) ) { + $array[] = $row; + } + debug(array($sql, $array)); + return $array; +} + +# ============================================================================ +# Safely increments a value that might be null. +# ============================================================================ +function increment(&$arr, $key, $howmuch) { + debug(array($key, $howmuch)); + if (array_key_exists($key, $arr) && isset($arr[$key]) ) { + $arr[$key] = big_add($arr[$key], $howmuch); + } + else { + $arr[$key] = $howmuch; + } +} + +# ============================================================================ +# Multiply two big integers together as accurately as possible with reasonable +# effort. This is tested in t/mysql_stats.php and copied, without tests, to +# ss_get_by_ssh.php. $force is for testability. +# ============================================================================ +function big_multiply ($left, $right, $force = null) { + if (function_exists("gmp_mul") && (is_null($force) || $force == 'gmp') ) { + debug(array('gmp_mul', $left, $right)); + return gmp_strval( gmp_mul( $left, $right )); + } + elseif (function_exists("bcmul") && (is_null($force) || $force == 'bc') ) { + debug(array('bcmul', $left, $right)); + return bcmul( $left, $right ); + } + else { # Or $force == 'something else' + debug(array('sprintf', $left, $right)); + return sprintf("%.0f", $left * $right); + } +} + +# ============================================================================ +# Subtract two big integers as accurately as possible with reasonable effort. +# This is tested in t/mysql_stats.php and copied, without tests, to +# ss_get_by_ssh.php. $force is for testability. +# ============================================================================ +function big_sub ($left, $right, $force = null) { + debug(array($left, $right)); + if (is_null($left) ) { $left = 0; } + if (is_null($right) ) { $right = 0; } + if (function_exists("gmp_sub") && (is_null($force) || $force == 'gmp')) { + debug(array('gmp_sub', $left, $right)); + return gmp_strval( gmp_sub( $left, $right )); + } + elseif (function_exists("bcsub") && (is_null($force) || $force == 'bc')) { + debug(array('bcsub', $left, $right)); + return bcsub( $left, $right ); + } + else { # Or $force == 'something else' + debug(array('to_int', $left, $right)); + return to_int($left - $right); + } +} + +# ============================================================================ +# Add two big integers together as accurately as possible with reasonable +# effort. This is tested in t/mysql_stats.php and copied, without tests, to +# ss_get_by_ssh.php. $force is for testability. +# ============================================================================ +function big_add ($left, $right, $force = null) { + if (is_null($left) ) { $left = 0; } + if (is_null($right) ) { $right = 0; } + if (function_exists("gmp_add") && (is_null($force) || $force == 'gmp')) { + debug(array('gmp_add', $left, $right)); + return gmp_strval( gmp_add( $left, $right )); + } + elseif (function_exists("bcadd") && (is_null($force) || $force == 'bc')) { + debug(array('bcadd', $left, $right)); + return bcadd( $left, $right ); + } + else { # Or $force == 'something else' + debug(array('to_int', $left, $right)); + return to_int($left + $right); + } +} + +# ============================================================================ +# Writes to a debugging log. +# ============================================================================ +function debug($val) { + global $debug_log; + if (!$debug_log ) { + return; + } + if ($fp = fopen($debug_log, 'a+') ) { + $trace = debug_backtrace(); + $calls = array(); + $i = 0; + $line = 0; + $file = ''; + foreach ( debug_backtrace() as $arr ) { + if ($i++ ) { + $calls[] = "$arr[function]() at $file:$line"; + } + $line = array_key_exists('line', $arr) ? $arr['line'] : '?'; + $file = array_key_exists('file', $arr) ? $arr['file'] : '?'; + } + if (!count($calls) ) { + $calls[] = "at $file:$line"; + } + fwrite($fp, date('Y-m-d h:i:s') . ' ' . implode(' <- ', $calls)); + fwrite($fp, "\n" . var_export($val, TRUE) . "\n"); + fclose($fp); + } + else { # Disable logging + print("Warning: disabling debug logging to $debug_log\n"); + $debug_log = FALSE; + } +} + +?> diff --git a/snmp/nfs-stats.sh b/snmp/nfs-stats.sh old mode 100644 new mode 100755 diff --git a/snmp/nginx-stats b/snmp/nginx-stats new file mode 100755 index 0000000..1cedca5 --- /dev/null +++ b/snmp/nginx-stats @@ -0,0 +1,37 @@ +#!/usr/bin/env python2 +import urllib2 +import re + + +data = urllib2.urlopen('http://127.0.0.1/nginx-status').read() + +params = {} + +for line in data.split("\n"): + smallstat = re.match(r"\s?Reading:\s(.*)\sWriting:\s(.*)\sWaiting:\s(.*)$", line) + req = re.match(r"\s+(\d+)\s+(\d+)\s+(\d+)", line) + if smallstat: + params["Reading"] = smallstat.group(1) + params["Writing"] = smallstat.group(2) + params["Waiting"] = smallstat.group(3) + elif req: + params["Requests"] = req.group(3) + else: + pass + + +dataorder = [ + "Active", + "Reading", + "Writing", + "Waiting", + "Requests" + ] + + +for param in dataorder: + if param == "Active": + Active = int(params["Reading"]) + int(params["Writing"]) + int(params["Waiting"]) + print Active + else: + print params[param] diff --git a/snmp/postfix-queues b/snmp/postfix-queues new file mode 100755 index 0000000..dc1951c --- /dev/null +++ b/snmp/postfix-queues @@ -0,0 +1,13 @@ +#!/bin/bash + +#Written by Valec 2006. Steal and share. +#Get postfix queue lengths + +#extend mailq /opt/observer/scripts/getmailq.sh + +QUEUES="incoming active deferred hold" + +for i in $QUEUES; do + COUNT=`qshape $i | grep TOTAL | awk '{print $2}'` + printf "$COUNT\n" +done diff --git a/snmp/powerdns.php b/snmp/powerdns.php new file mode 100755 index 0000000..1410312 --- /dev/null +++ b/snmp/powerdns.php @@ -0,0 +1,76 @@ +#!/usr/bin/env php + + +// START SETTINGS /// +$pdnscontrol = '/usr/bin/pdns_control'; +// END SETTINGS /// + +// DO NOT EDIT UNDER THIS LINE +// +$cmd = shell_exec($pdnscontrol.' show \*'); +$vars = array(); +$vars = explode(',', $cmd); + + +function doSNMP($vars) { + foreach ($vars as $item => $value) { + $value = trim($value); + if (!empty($value)) { + echo $value."\n"; + } + } + +}//end doSNMP() + +function doSNMPv2($vars) { + $pdns = array(); + foreach ($vars as $item => $value) { + if (!empty($value)) { + $temp = explode('=', $value); + if (isset($temp[1])) { + $pdns[$temp[0]] = $temp[1]; + } + } + } + + $var = array(); + $var['corrupt-packets'] = (isset($pdns['corrupt-packets']) ? $pdns['corrupt-packets'] : 'U'); + $var['deferred-cache-inserts'] = (isset($pdns['deferred-cache-inserts']) ? $pdns['deferred-cache-inserts'] : 'U'); + $var['deferred-cache-lookup'] = (isset($pdns['deferred-cache-lookup']) ? $pdns['deferred-cache-lookup'] : 'U'); + $var['latency'] = (isset($pdns['latency']) ? $pdns['latency'] : 'U'); + $var['packetcache-hit'] = (isset($pdns['packetcache-hit']) ? $pdns['packetcache-hit'] : 'U'); + $var['packetcache-miss'] = (isset($pdns['packetcache-miss']) ? $pdns['packetcache-miss'] : 'U'); + $var['packetcache-size'] = (isset($pdns['packetcache-size']) ? $pdns['packetcache-size'] : 'U'); + $var['qsize-q'] = (isset($pdns['qsize-q']) ? $pdns['qsize-q'] : 'U'); + $var['query-cache-hit'] = (isset($pdns['query-cache-hit']) ? $pdns['query-cache-hit'] : 'U'); + $var['query-cache-miss'] = (isset($pdns['query-cache-miss']) ? $pdns['query-cache-miss'] : 'U'); + $var['recursing-answers'] = (isset($pdns['recursing-answers']) ? $pdns['recursing-answers'] : 'U'); + $var['recursing-questions'] = (isset($pdns['recursing-questions']) ? $pdns['recursing-questions'] : 'U'); + $var['servfail-packets'] = (isset($pdns['servfail-packets']) ? $pdns['servfail-packets'] : 'U'); + $var['tcp-answers'] = (isset($pdns['tcp-answers']) ? $pdns['tcp-answers'] : 'U'); + $var['tcp-queries'] = (isset($pdns['tcp-queries']) ? $pdns['tcp-queries'] : 'U'); + $var['timedout-packets'] = (isset($pdns['timedout-packets']) ? $pdns['timedout-packets'] : 'U'); + $var['udp-answers'] = (isset($pdns['udp-answers']) ? $pdns['udp-answers'] : 'U'); + $var['udp-queries'] = (isset($pdns['udp-queries']) ? $pdns['udp-queries'] : 'U'); + $var['udp4-answers'] = (isset($pdns['udp4-answers']) ? $pdns['udp4-answers'] : 'U'); + $var['udp4-queries'] = (isset($pdns['udp4-queries']) ? $pdns['udp4-queries'] : 'U'); + $var['udp6-answers'] = (isset($pdns['udp6-answers']) ? $pdns['udp6-answers'] : 'U'); + $var['udp6-queries'] = (isset($pdns['udp6-queries']) ? $pdns['udp6-queries'] : 'U'); + foreach ($var as $item => $count) { + echo $count."\n"; + } + +}//end doSNMPv2() + + +doSNMPv2($vars); diff --git a/snmp/raspberry.sh b/snmp/raspberry.sh old mode 100644 new mode 100755 diff --git a/snmp/shoutcast.default.conf b/snmp/shoutcast.default.conf new file mode 100644 index 0000000..d38367b --- /dev/null +++ b/snmp/shoutcast.default.conf @@ -0,0 +1,3 @@ +server1:8050 +server2:8000 +server2:8010 diff --git a/snmp/shoutcast.php b/snmp/shoutcast.php new file mode 100755 index 0000000..4c58835 --- /dev/null +++ b/snmp/shoutcast.php @@ -0,0 +1,122 @@ +#!/usr/bin/env php + + /// + /////////////////////////////////////////////////////////////////////////////////////// + + + // START SETTINGS /// + + $config = "/opt/librenms/scripts/shoutcast.conf"; + $cache = "/opt/librenms/scripts/shoutcast.cache"; + + // END SETTINGS /// + + + /// + // DO NOT EDIT BENETH THIS LINE + /// + /////////////////////////////////////////////////////////////////////////////////////// + + /* Do NOT run this script through a web browser */ + if (!isset($_SERVER["argv"][0]) || isset($_SERVER['REQUEST_METHOD']) || isset($_SERVER['REMOTE_ADDR'])) { + die('This script is only meant to run at the command line.'); + } + + $cmd = (isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ""); + + function get_data($host, $port) { + $fp = @fsockopen($host, $port, $errno, $errstr, 5); + if(!$fp) { $connect = 0; } + if (!isset($connect)) { + fputs($fp, "GET /7.html HTTP/1.0\r\n" + . "User-Agent: All In One - SHOUTcast Stats Parser" + . " (Mozilla Compatible)\r\n\r\n"); + while (!feof($fp)) { + $rawdata = fgets($fp, 1024); + } + fclose($fp); + } + preg_match('/body>(.*)<\/body/', $rawdata, $matches); + $res = explode(',', $matches[1], 7); + $res[7] = $host; + $res[8] = $port; + return $res; + } + + function get_list($config) { + if (file_exists($config)) { + $servers = file($config); + $data = array(); + foreach ($servers as $item=>$server) { + list($host, $port) = explode(":", $server, 2); + array_push($data, get_data(trim($host), trim($port))); + } + return $data; + } + } + + function doSNMPv2($vars) { + $res = array(); + foreach ($vars as $items=>$server) { + $var = array(); + $var['bitrate'] = (isset($server['5']) ? (($server['5'] / 8) * 1000) : "0"); + //$var['bitrate'] = (isset($server['5']) ? ($server['5'] * 1024) : "0"); + $var['traf_in'] = (isset($server['1']) ? ($var['bitrate'] * $server['1']) : "0"); + $var['traf_out'] = (isset($server['0']) ? ($var['bitrate'] * $server['0']) : "0"); + $var['current'] = (isset($server['0']) ? $server['0'] : "0"); + $var['status'] = (isset($server['1']) ? $server['1'] : "0"); + $var['peak'] = (isset($server['2']) ? $server['2'] : "0"); + $var['max'] = (isset($server['3']) ? $server['3'] : "0"); + $var['unique'] = (isset($server['4']) ? $server['4'] : "0"); + $host = (isset($server['7']) ? $server['7'] : "unknown"); + $port = (isset($server['8']) ? $server['8'] : "unknown"); + $tmp = $host.":".$port; + foreach ($var as $item=>$value) { + $tmp .= ";".$value; + } + array_push($res, $tmp); + } + return $res; + } + + function makeCacheFile($data, $cache) { + $fp = fopen($cache, 'w'); + foreach ($data as $item=>$value) { + fwrite($fp, $value."\n"); + } + fclose($fp); + } + + function readCacheFile($cache) { + if (file_exists($cache)) { + $data = file($cache); + foreach ($data as $item=>$value) { + echo trim($value)."\n"; + } + } + } + + if ($cmd == "makeCache") { + $servers = get_list($config); + $data = doSNMPv2($servers); + makeCacheFile($data, $cache); + } else { + readCacheFile($cache); + } + +?>