PageRenderTime 48ms CodeModel.GetById 3ms app.highlight 34ms RepoModel.GetById 1ms app.codeStats 1ms

/scripts/ss_get_by_ssh.php

http://mysql-cacti-templates.googlecode.com/
PHP | 1485 lines | 1143 code | 98 blank | 244 comment | 152 complexity | 5a3cb6dcd8c6b109f1c7b8cfe16fc6f2 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2
   3# ============================================================================
   4# This is a script to retrieve information over SSH for a Cacti graphing
   5# process.  It is hosted at http://code.google.com/p/mysql-cacti-templates/.
   6#
   7# This program is copyright (c) 2008 Baron Schwartz. Feedback and improvements
   8# are welcome.
   9#
  10# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
  11# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
  12# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  13#
  14# This program is free software; you can redistribute it and/or modify it under
  15# the terms of the GNU General Public License as published by the Free Software
  16# Foundation, version 2.
  17#
  18# You should have received a copy of the GNU General Public License along with
  19# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
  20# Place, Suite 330, Boston, MA  02111-1307  USA.
  21# ============================================================================
  22
  23# ============================================================================
  24# To make this code testable, we need to prevent code from running when it is
  25# included from the test script.  The test script and this file have different
  26# filenames, so we can compare them.  In some cases $_SERVER['SCRIPT_FILENAME']
  27# seems not to be defined, so we skip the check -- this check should certainly
  28# pass in the test environment.
  29# ============================================================================
  30if ( !array_key_exists('SCRIPT_FILENAME', $_SERVER)
  31   || basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME']) ) {
  32
  33# ============================================================================
  34# CONFIGURATION
  35# ============================================================================
  36# Define parameters.  Instead of defining parameters here, you can define them
  37# in another file named the same as this file, with a .cnf extension.
  38# ============================================================================
  39$ssh_user   = 'cacti';                          # SSH username
  40$ssh_port   = 22;                               # SSH port
  41$ssh_iden   = '-i /var/www/cacti/.ssh/id_rsa';  # SSH identity
  42$ssh_tout   = 10;                               # SSH connect timeout
  43$nc_cmd     = 'nc -C -q1';                      # How to invoke netcat
  44$cache_dir  = '/tmp';  # If set, this uses caching to avoid multiple calls.
  45$poll_time  = 300; # Adjust to match your polling interval.
  46$use_ss     = FALSE; # Whether to use the script server or not
  47$use_ssh    = TRUE;  # Whether to connect via SSH or not (default yes).
  48$debug      = FALSE; # Define whether you want debugging behavior.
  49$debug_log  = FALSE; # If $debug_log is a filename, it'll be used.
  50
  51# Parameters for specific graphs can be specified here, or in the .cnf file.
  52$status_server = 'localhost';             # Which server to query
  53$status_url    = '/server-status';        # Where Apache status lives
  54$http_user     = '';
  55$http_pass     = '';
  56$memcache_port = 11211;                   # Which port memcached listens on
  57$redis_port    = 6379;                    # Which port redis listens on
  58                                          # How to get openvz stats
  59$openvz_cmd    = 'cat /proc/user_beancounters';
  60
  61# ============================================================================
  62# You should not need to change anything below this line.
  63# ============================================================================
  64$version = '1.1.8';
  65
  66# ============================================================================
  67# Include settings from an external config file (issue 39).
  68# ============================================================================
  69if ( file_exists(__FILE__ . '.cnf' ) ) {
  70   require(__FILE__ . '.cnf');
  71}
  72
  73# Make this a happy little script even when there are errors.
  74$no_http_headers = true;
  75ini_set('implicit_flush', false); # No output, ever.
  76if ( $debug ) {
  77   ini_set('display_errors', true);
  78   ini_set('display_startup_errors', true);
  79   ini_set('error_reporting', 2147483647);
  80}
  81else {
  82   ini_set('error_reporting', E_ERROR);
  83}
  84ob_start(); # Catch all output such as notices of undefined array indexes.
  85function error_handler($errno, $errstr, $errfile, $errline) {
  86   print("$errstr at $errfile line $errline\n");
  87   debug("$errstr at $errfile line $errline");
  88}
  89# ============================================================================
  90# Set up the stuff we need to be called by the script server.
  91# ============================================================================
  92if ( $use_ss ) {
  93   if ( file_exists( dirname(__FILE__) . "/../include/global.php") ) {
  94      # See issue 5 for the reasoning behind this.
  95      debug("including " . dirname(__FILE__) . "/../include/global.php");
  96      include_once(dirname(__FILE__) . "/../include/global.php");
  97   }
  98   elseif ( file_exists( dirname(__FILE__) . "/../include/config.php" ) ) {
  99      # Some Cacti installations don't have global.php.
 100      debug("including " . dirname(__FILE__) . "/../include/config.php");
 101      include_once(dirname(__FILE__) . "/../include/config.php");
 102   }
 103}
 104
 105# ============================================================================
 106# Make sure we can also be called as a script.
 107# ============================================================================
 108if (!isset($called_by_script_server)) {
 109   debug($_SERVER["argv"]);
 110   array_shift($_SERVER["argv"]); # Strip off this script's filename
 111   $options = parse_cmdline($_SERVER["argv"]);
 112   validate_options($options);
 113   $result = ss_get_by_ssh($options);
 114   debug($result);
 115   if ( !$debug ) {
 116      # Throw away the buffer, which ought to contain only errors.
 117      ob_end_clean();
 118   }
 119   else {
 120      ob_end_flush(); # In debugging mode, print out the errors.
 121   }
 122   print($result);
 123}
 124
 125# ============================================================================
 126# End "if file was not included" section.
 127# ============================================================================
 128}
 129
 130# ============================================================================
 131# Work around the lack of array_change_key_case in older PHP.
 132# ============================================================================
 133if ( !function_exists('array_change_key_case') ) {
 134   function array_change_key_case($arr) {
 135      $res = array();
 136      foreach ( $arr as $key => $val ) {
 137         $res[strtolower($key)] = $val;
 138      }
 139      return $res;
 140   }
 141}
 142
 143# ============================================================================
 144# Extracts the desired bits from a string and returns them.
 145# ============================================================================
 146function extract_desired ( $options, $text ) {
 147   debug($text);
 148   # Split the result up and extract only the desired parts of it.
 149   $wanted = explode(',', $options['items']);
 150   $output = array();
 151   foreach ( explode(' ', $text) as $item ) {
 152      if ( in_array(substr($item, 0, 2), $wanted) ) {
 153         $output[] = $item;
 154      }
 155   }
 156   $result = implode(' ', $output);
 157   debug($result);
 158   return $result;
 159}
 160
 161# ============================================================================
 162# Validate that the command-line options are here and correct
 163# ============================================================================
 164function validate_options($options) {
 165   debug($options);
 166   $opts = array('host', 'port', 'items', 'nocache', 'type', 'url', 'http-user',
 167                 'file', 'http-password', 'server', 'port2', 'use-ssh', 
 168                 'device', 'volume', 'threadpool');
 169   # Required command-line options
 170   foreach ( array('host', 'items', 'type') as $option ) {
 171      if ( !isset($options[$option]) || !$options[$option] ) {
 172         usage("Required option --$option is missing");
 173      }
 174   }
 175   foreach ( $options as $key => $val ) {
 176      if ( !in_array($key, $opts) ) {
 177         usage("Unknown option --$key");
 178      }
 179   }
 180}
 181
 182# ============================================================================
 183# Print out a brief usage summary
 184# ============================================================================
 185function usage($message) {
 186   $usage = <<<EOF
 187$message
 188Usage: php ss_get_by_ssh.php --host <host> --items <item,...> [OPTION]
 189
 190Command-line options ALWAYS require a value after them.  If you specify an
 191option without a value after it, the option is ignored.  For options such as
 192--nocache, you can say "--nocache 1".
 193
 194General options:
 195
 196   --device          The device name for diskstats and netdev
 197   --file            Get input from this file instead of via SSH command
 198   --host            Hostname to connect to (via SSH)
 199   --items           Comma-separated list of the items whose data you want
 200   --nocache         Do not cache results in a file
 201   --port            SSH port to connect to (SSH port, not application port!)
 202   --port2           Port on which the application listens, such as memcached
 203                     port, redis port, or apache port.
 204   --server          The server (DNS name or IP address) from which to fetch the
 205                     desired data after SSHing.  Default is 'localhost' for HTTP
 206                     stats and --host for memcached stats.
 207   --threadpool      Name of ThreadPool in JMX (i.e. http-8080 or jk-8009)
 208   --type            One of apache, nginx, proc_stat, w, memory, memcached,
 209                     diskstats, openvz, redis, jmx, mongodb, df, netdev, 
 210                     netstat, vmstat (more are TODO)
 211   --url             The url, such as /server-status, where server status lives
 212   --use-ssh         Whether to connect via SSH to gather info (default yes).
 213   --http-user       The HTTP authentication user
 214   --http-password   The HTTP authentication password
 215   --openvz_cmd      The command to use when fetching OpenVZ statistics
 216   --volume          The volume name for df
 217
 218EOF;
 219   die($usage);
 220}
 221
 222# ============================================================================
 223# Parse command-line arguments, in the format --arg value --arg value, and
 224# return them as an array ( arg => value )
 225# ============================================================================
 226function parse_cmdline( $args ) {
 227   $result = array();
 228   for ( $i = 0; $i < count($args); ++$i ) {
 229      if ( strpos($args[$i], '--') === 0 ) {
 230         if ( $i + 1 < count($args) && strpos($args[$i + 1], '--') !== 0 ) {
 231            # The next element should be the value for this option.
 232            $result[substr($args[$i], 2)] = $args[$i + 1];
 233            ++$i;
 234         }
 235      }
 236   }
 237   debug($result);
 238   return $result;
 239}
 240
 241# ============================================================================
 242# This is the main function.  Some parameters are filled in from defaults at the
 243# top of this file.
 244# ============================================================================
 245function ss_get_by_ssh( $options ) {
 246   global $debug, $cache_dir, $poll_time;
 247
 248   # Build and test the type-specific function names.
 249   $caching_func = "$options[type]_cachefile";
 250   $cmdline_func = "$options[type]_cmdline";
 251   $parsing_func = "$options[type]_parse";
 252   $getting_func = "$options[type]_get";
 253   debug("Functions: '$caching_func', '$cmdline_func', '$parsing_func'");
 254   if ( !function_exists($cmdline_func) ) {
 255      die("The parsing function '$cmdline_func' does not exist");
 256   }
 257   if ( !function_exists($parsing_func) ) {
 258      die("The parsing function '$parsing_func' does not exist");
 259   }
 260
 261   # Check the cache.
 262   $fp = null;
 263   if ( !isset($options['file']) && $cache_dir && !isset($options['nocache'])
 264      && function_exists($caching_func)
 265   ) {
 266      $cache_file = call_user_func($caching_func, $options);
 267      $cache_res  = check_cache($cache_dir, $poll_time, $cache_file, $options);
 268      if ( $cache_res[1] ) {
 269         debug("The cache is usable.");
 270         return extract_desired($options, $cache_res[1]);
 271      }
 272      elseif ( $cache_res[0] ) {
 273         $fp = $cache_res[0];
 274         debug("Got a filehandle to the cache file");
 275      }
 276   }
 277   if ( !$fp ) {
 278      debug("Caching is disabled.");
 279   }
 280
 281   # There might be a custom function that overrides the SSH fetch.
 282   if ( !isset($options['file']) && function_exists($getting_func) ) {
 283      debug("$getting_func() is defined, will call it");
 284      $output = call_user_func($getting_func, $options);
 285   }
 286   else {
 287      # Get the command-line to fetch the data, then fetch and parse the data.
 288      debug("No getting_func(), will use normal code path");
 289      $cmd = call_user_func($cmdline_func, $options);
 290      debug($cmd);
 291      $output = get_command_result($cmd, $options);
 292   }
 293   debug($output);
 294   $result = call_user_func($parsing_func, $options, $output);
 295
 296   # Define the variables to output.  I use shortened variable names so maybe
 297   # it'll all fit in 1024 bytes for Cactid and Spine's benefit.  This list must
 298   # come right after the word MAGIC_VARS_DEFINITIONS.  The Perl script parses
 299   # it and uses it as a Perl variable.
 300   $keys = array(
 301      # Apache stuff.  Don't emulate -- the naming convention here lacks a name
 302      # prefix, which really ought to be added to all of these.
 303      'Requests'               => 'a0',
 304      'Bytes_sent'             => 'a1',
 305      'Idle_workers'           => 'a2',
 306      'Busy_workers'           => 'a3',
 307      'CPU_Load'               => 'a4',
 308      'Waiting_for_connection' => 'a5',
 309      'Starting_up'            => 'a6',
 310      'Reading_request'        => 'a7',
 311      'Sending_reply'          => 'a8',
 312      'Keepalive'              => 'a9',
 313      'DNS_lookup'             => 'aa',
 314      'Closing_connection'     => 'ab',
 315      'Logging'                => 'ac',
 316      'Gracefully_finishing'   => 'ad',
 317      'Idle_cleanup'           => 'ae',
 318      'Open_slot'              => 'af',
 319      # /proc/stat stuff
 320      'STAT_CPU_user'          => 'ag',
 321      'STAT_CPU_nice'          => 'ah',
 322      'STAT_CPU_system'        => 'ai',
 323      'STAT_CPU_idle'          => 'aj',
 324      'STAT_CPU_iowait'        => 'ak',
 325      'STAT_CPU_irq'           => 'al',
 326      'STAT_CPU_softirq'       => 'am',
 327      'STAT_CPU_steal'         => 'an',
 328      'STAT_CPU_guest'         => 'ao',
 329      'STAT_interrupts'        => 'ap',
 330      'STAT_context_switches'  => 'aq',
 331      'STAT_forks'             => 'ar',
 332      # Stuff from 'w'
 333      'STAT_loadavg'           => 'as',
 334      'STAT_numusers'          => 'at',
 335      # Stuff from 'free'
 336      'STAT_memcached'         => 'au',
 337      'STAT_membuffer'         => 'av',
 338      'STAT_memshared'         => 'aw',
 339      'STAT_memfree'           => 'ax',
 340      'STAT_memused'           => 'ay',
 341      'STAT_memtotal'          => 'dv',
 342      # Stuff from Nginx
 343      'NGINX_active_connections' => 'az',
 344      'NGINX_server_accepts'     => 'b0',
 345      'NGINX_server_handled'     => 'b1',
 346      'NGINX_server_requests'    => 'b2',
 347      'NGINX_reading'            => 'b3',
 348      'NGINX_writing'            => 'b4',
 349      'NGINX_waiting'            => 'b5',
 350      # Stuff from memcached
 351      'MEMC_rusage_user'       => 'b6',
 352      'MEMC_rusage_system'     => 'b7',
 353      'MEMC_curr_items'        => 'b8',
 354      'MEMC_total_items'       => 'b9',
 355      'MEMC_bytes'             => 'ba',
 356      'MEMC_curr_connections'  => 'bb',
 357      'MEMC_total_connections' => 'bc',
 358      'MEMC_cmd_get'           => 'bd',
 359      'MEMC_cmd_set'           => 'be',
 360      'MEMC_get_misses'        => 'bf',
 361      'MEMC_evictions'         => 'bg',
 362      'MEMC_bytes_read'        => 'bh',
 363      'MEMC_bytes_written'     => 'bi',
 364      # Diskstats stuff
 365      'DISK_reads'              => 'bj',
 366      'DISK_reads_merged'       => 'bk',
 367      'DISK_sectors_read'       => 'bl',
 368      'DISK_time_spent_reading' => 'bm',
 369      'DISK_writes'             => 'bn',
 370      'DISK_writes_merged'      => 'bo',
 371      'DISK_sectors_written'    => 'bp',
 372      'DISK_time_spent_writing' => 'bq',
 373      'DISK_io_ops_in_progress' => 'br',
 374      'DISK_io_time'            => 'bs',
 375      'DISK_io_time_weighted'   => 'bt',
 376      # OpenVZ (/proc/user_beancounters) stuff.
 377      'OPVZ_kmemsize_held'        => 'bu',
 378      'OPVZ_kmemsize_failcnt'     => 'bv',
 379      'OPVZ_lockedpages_held'     => 'bw',
 380      'OPVZ_lockedpages_failcnt'  => 'bx',
 381      'OPVZ_privvmpages_held'     => 'by',
 382      'OPVZ_privvmpages_failcnt'  => 'bz',
 383      'OPVZ_shmpages_held'        => 'c0',
 384      'OPVZ_shmpages_failcnt'     => 'c1',
 385      'OPVZ_numproc_held'         => 'c2',
 386      'OPVZ_numproc_failcnt'      => 'c3',
 387      'OPVZ_physpages_held'       => 'c4',
 388      'OPVZ_physpages_failcnt'    => 'c5',
 389      'OPVZ_vmguarpages_held'     => 'c6',
 390      'OPVZ_vmguarpages_failcnt'  => 'c7',
 391      'OPVZ_oomguarpages_held'    => 'c8',
 392      'OPVZ_oomguarpages_failcnt' => 'c9',
 393      'OPVZ_numtcpsock_held'      => 'ca',
 394      'OPVZ_numtcpsock_failcnt'   => 'cb',
 395      'OPVZ_numflock_held'        => 'cc',
 396      'OPVZ_numflock_failcnt'     => 'cd',
 397      'OPVZ_numpty_held'          => 'ce',
 398      'OPVZ_numpty_failcnt'       => 'cf',
 399      'OPVZ_numsiginfo_held'      => 'cg',
 400      'OPVZ_numsiginfo_failcnt'   => 'ch',
 401      'OPVZ_tcpsndbuf_held'       => 'ci',
 402      'OPVZ_tcpsndbuf_failcnt'    => 'cj',
 403      'OPVZ_tcprcvbuf_held'       => 'ck',
 404      'OPVZ_tcprcvbuf_failcnt'    => 'cl',
 405      'OPVZ_othersockbuf_held'    => 'cm',
 406      'OPVZ_othersockbuf_failcnt' => 'cn',
 407      'OPVZ_dgramrcvbuf_held'     => 'co',
 408      'OPVZ_dgramrcvbuf_failcnt'  => 'cp',
 409      'OPVZ_numothersock_held'    => 'cq',
 410      'OPVZ_numothersock_failcnt' => 'cr',
 411      'OPVZ_dcachesize_held'      => 'cs',
 412      'OPVZ_dcachesize_failcnt'   => 'ct',
 413      'OPVZ_numfile_held'         => 'cu',
 414      'OPVZ_numfile_failcnt'      => 'cv',
 415      'OPVZ_numiptent_held'       => 'cw',
 416      'OPVZ_numiptent_failcnt'    => 'cx',
 417      # Stuff from redis
 418      'REDIS_connected_clients'          => 'cy',
 419      'REDIS_connected_slaves'           => 'cz',
 420      'REDIS_used_memory'                => 'd0',
 421      'REDIS_changes_since_last_save'    => 'd1',
 422      'REDIS_total_connections_received' => 'd2',
 423      'REDIS_total_commands_processed'   => 'd3',
 424      # Stuff from jmx
 425      'JMX_heap_memory_used'             => 'd4',
 426      'JMX_heap_memory_committed'        => 'd5',
 427      'JMX_heap_memory_max'              => 'd6',
 428      'JMX_non_heap_memory_used'         => 'd7',
 429      'JMX_non_heap_memory_committed'    => 'd8',
 430      'JMX_non_heap_memory_max'          => 'd9',
 431      'JMX_open_file_descriptors'        => 'da',
 432      'JMX_max_file_descriptors'         => 'db',
 433      'JMX_current_threads_busy'         => 'el',
 434      'JMX_current_thread_count'         => 'em',
 435      'JMX_max_threads'                  => 'en',
 436      # Stuff from mongodb
 437      'MONGODB_connected_clients'        => 'dc',
 438      'MONGODB_used_resident_memory'     => 'dd',
 439      'MONGODB_used_mapped_memory'       => 'de',
 440      'MONGODB_used_virtual_memory'      => 'df',
 441      'MONGODB_index_accesses'           => 'dg',
 442      'MONGODB_index_hits'               => 'dh',
 443      'MONGODB_index_misses'             => 'di',
 444      'MONGODB_index_resets'             => 'dj',
 445      'MONGODB_back_flushes'             => 'dk',
 446      'MONGODB_back_total_ms'            => 'dl',
 447      'MONGODB_back_average_ms'          => 'dm',
 448      'MONGODB_back_last_ms'             => 'dn',
 449      'MONGODB_op_inserts'               => 'do',
 450      'MONGODB_op_queries'               => 'dp',
 451      'MONGODB_op_updates'               => 'dq',
 452      'MONGODB_op_deletes'               => 'dr',
 453      'MONGODB_op_getmores'              => 'ds',
 454      'MONGODB_op_commands'              => 'dt',
 455      'MONGODB_slave_lag'                => 'du',
 456      # used by STAT_memtotal            => 'dv',
 457      # Stuff from 'df'
 458      'DISKFREE_used'                    => 'dw',
 459      'DISKFREE_available'               => 'dx',
 460      # Stuff from '/proc/net/dev'
 461      'NETDEV_rxbytes'                   => 'dy',
 462      'NETDEV_rxerrs'                    => 'dz',
 463      'NETDEV_rxdrop'                    => 'e0',
 464      'NETDEV_rxfifo'                    => 'e1',
 465      'NETDEV_rxframe'                   => 'e2',
 466      'NETDEV_txbytes'                   => 'e3',
 467      'NETDEV_txerrs'                    => 'e4',
 468      'NETDEV_txdrop'                    => 'e5',
 469      'NETDEV_txfifo'                    => 'e6',
 470      'NETDEV_txcolls'                   => 'e7',
 471      'NETDEV_txcarrier'                 => 'e8',
 472      # Stuff from 'netstat'
 473      'NETSTAT_established'              => 'e9',
 474      'NETSTAT_syn_sent'                 => 'ea',
 475      'NETSTAT_syn_recv'                 => 'eb',
 476      'NETSTAT_fin_wait1'                => 'ec',
 477      'NETSTAT_fin_wait2'                => 'ed',
 478      'NETSTAT_time_wait'                => 'ee',
 479      'NETSTAT_close'                    => 'ef',
 480      'NETSTAT_close_wait'               => 'eg',
 481      'NETSTAT_last_ack'                 => 'eh',
 482      'NETSTAT_listen'                   => 'ei',
 483      'NETSTAT_closing'                  => 'ej',
 484      'NETSTAT_unknown'                  => 'ek',
 485      # used by 'JMX_current_threads_busy' => 'el',
 486      # used by 'JMX_current_thread_count' => 'em',
 487      # used by 'JMX_max_threads'          => 'en',
 488      # Stuff from 'vmstat' (swap)
 489      'VMSTAT_pswpin'                    => 'eo',
 490      'VMSTAT_pswpout'                   => 'ep',
 491   );
 492
 493   # Prepare and return the output.  The output we have right now is the whole
 494   # info, and we need that -- to write it to the cache file -- but what we
 495   # return should be only the desired items.
 496   $output = array();
 497   foreach ($keys as $key => $short ) {
 498      # If the value isn't defined, return -1 which is lower than (most graphs')
 499      # minimum value of 0, so it'll be regarded as a missing value.
 500      $val      = isset($result[$key]) ? $result[$key] : -1;
 501      $output[] = "$short:$val";
 502   }
 503   $result = implode(' ', $output);
 504   if ( $fp ) {
 505      if ( fwrite($fp, $result) === FALSE ) {
 506         die("Cannot write to '$cache_file'");
 507      }
 508      fclose($fp);
 509   }
 510   return extract_desired($options, $result);
 511}
 512
 513# ============================================================================
 514# Checks the cache file.  If its contents are valid, return them.  Otherwise
 515# return the filehandle, locked with flock() and ready for writing.  Return an
 516# array:
 517#  0 => $fp       The file pointer for the cache file
 518#  1 => $arr      The contents of the file, if it's valid.
 519# ============================================================================
 520function check_cache ( $cache_dir, $poll_time, $filename, $options ) {
 521   $cache_file = "$cache_dir/${filename}_cacti_stats.txt";
 522   debug("Cache file: $cache_file");
 523   if ( $fp = fopen($cache_file, 'a+') ) {
 524      $locked = flock($fp, 1); # LOCK_SH
 525      if ( $locked ) {
 526         if ( filesize($cache_file) > 0
 527            && filectime($cache_file) + ($poll_time/2) > time()
 528            && ($arr = file($cache_file))
 529         ) {# The cache file is good to use.
 530            debug("Using the cache file");
 531            fclose($fp);
 532            return array(null, $arr[0]);
 533         }
 534         else {
 535            debug("The cache file seems too small or stale");
 536            # Escalate the lock to exclusive, so we can write to it.
 537            if ( flock($fp, 2) ) { # LOCK_EX
 538               # We might have blocked while waiting for that LOCK_EX, and
 539               # another process ran and updated it.  Let's see if we can just
 540               # return the data now:
 541               if ( filesize($cache_file) > 0
 542                  && filectime($cache_file) + ($poll_time/2) > time()
 543                  && ($arr = file($cache_file))
 544               ) { # The cache file is good to use.
 545                  debug("Using the cache file");
 546                  fclose($fp);
 547                  return array(null, $arr[0]);
 548               }
 549               ftruncate($fp, 0); # Now it's ready for writing later.
 550            }
 551         }
 552      }
 553      else {
 554         debug("Couldn't lock the cache file, ignoring it.");
 555         $fp = null;
 556      }
 557   }
 558
 559   return array($fp, null);
 560}
 561
 562# ============================================================================
 563# Simple function to replicate PHP 5 behaviour
 564# ============================================================================
 565function microtime_float() {
 566   list( $usec, $sec ) = explode( " ", microtime() );
 567   return ( (float) $usec + (float) $sec );
 568}
 569
 570# ============================================================================
 571# Function to sanitize filenames
 572# ============================================================================
 573function sanitize_filename($options, $keys, $tail) {
 574   $result = "";
 575   foreach ( $keys as $k ) {
 576      if ( isset($options[$k]) ) {
 577         $result .= $options[$k] . '_';
 578      }
 579   }
 580   return str_replace(array(":", "/"), array("", "_"), $result . $tail);
 581}
 582
 583# ============================================================================
 584# Execute the command to get the output and return it.
 585# ============================================================================
 586function get_command_result($cmd, $options) {
 587   global $debug, $ssh_user, $ssh_port, $ssh_iden, $ssh_tout, $use_ssh;
 588   $use_ssh = isset($options['use-ssh']) ? $options['use-ssh'] : $use_ssh;
 589
 590   # If there is a --file, we just use that.
 591   if ( isset($options['file']) ) {
 592      return implode("\n", file($options['file']));
 593   }
 594
 595   # Build the SSH command line.
 596   $port = isset($options['port']) ? $options['port'] : $ssh_port;
 597   $ssh  = "ssh -q -o \"ConnectTimeout $ssh_tout\" -o \"StrictHostKeyChecking no\" "
 598         . "$ssh_user@$options[host] -p $port $ssh_iden";
 599   debug($ssh);
 600   $final_cmd = $use_ssh ? "$ssh '$cmd'" : $cmd;
 601   debug($final_cmd);
 602   $start = microtime_float();
 603   $result = `$final_cmd`; # XXX this is the ssh command.
 604   $end = microtime_float();
 605   debug(array("Time taken to exec: ", $end - $start));
 606   debug(array("result of $final_cmd", $result));
 607   return $result;
 608}
 609
 610# ============================================================================
 611# Extracts the numbers from a string.  You can't reliably do this by casting to
 612# an int, because numbers that are bigger than PHP's int (varies by platform)
 613# will be truncated.  So this just handles them as a string instead.
 614# ============================================================================
 615function to_int ( $str ) {
 616   debug($str);
 617   global $debug;
 618   preg_match('{(\d+)}', $str, $m);
 619   if ( isset($m[1]) ) {
 620      return $m[1];
 621   }
 622   elseif ( $debug ) {
 623      print_r(debug_backtrace());
 624   }
 625   else {
 626      return 0;
 627   }
 628}
 629
 630# ============================================================================
 631# Extracts a float from a string.  See to_int().  This is tested in
 632# get_by_ssh.php.
 633# ============================================================================
 634function to_float ( $str ) {
 635   debug($str);
 636   global $debug;
 637   preg_match('{([0-9.]+)}', $str, $m); 
 638   if ( isset($m[1]) ) {
 639      return $m[1];
 640   }
 641   elseif ( $debug ) {
 642      print_r(debug_backtrace());
 643   }
 644   else {
 645      return 0;
 646   }
 647}
 648
 649# ============================================================================
 650# Safely increments a value that might be null.
 651# ============================================================================
 652function increment(&$arr, $key, $howmuch) {
 653   debug(array($key, $howmuch));
 654   if ( array_key_exists($key, $arr) && isset($arr[$key]) ) {
 655      $arr[$key] = big_add($arr[$key], $howmuch);
 656   }
 657   else {
 658      $arr[$key] = $howmuch;
 659   }
 660}
 661
 662# ============================================================================
 663# Multiply two big integers together as accurately as possible with reasonable
 664# effort.  This is tested in t/mysql_stats.php and copied, without tests, to
 665# ss_get_by_ssh.php.  $force is for testability.
 666# ============================================================================
 667function big_multiply ($left, $right, $force = null) {
 668   if ( function_exists("gmp_mul") && (is_null($force) || $force == 'gmp') ) {
 669      debug(array('gmp_mul', $left, $right));
 670      return gmp_strval( gmp_mul( $left, $right ));
 671   }
 672   elseif ( function_exists("bcmul") && (is_null($force) || $force == 'bc') ) {
 673      debug(array('bcmul', $left, $right));
 674      return bcmul( $left, $right );
 675   }
 676   else { # Or $force == 'something else'
 677      debug(array('sprintf', $left, $right));
 678      return sprintf("%.0f", $left * $right);
 679   }
 680}
 681
 682# ============================================================================
 683# Subtract two big integers as accurately as possible with reasonable effort.
 684# This is tested in t/mysql_stats.php and copied, without tests, to
 685# ss_get_by_ssh.php.  $force is for testability.
 686# ============================================================================
 687function big_sub ($left, $right, $force = null) {
 688   debug(array($left, $right));
 689   if ( is_null($left)  ) { $left = 0; }
 690   if ( is_null($right) ) { $right = 0; }
 691   if ( function_exists("gmp_sub") && (is_null($force) || $force == 'gmp')) {
 692      debug(array('gmp_sub', $left, $right));
 693      return gmp_strval( gmp_sub( $left, $right ));
 694   }
 695   elseif ( function_exists("bcsub") && (is_null($force) || $force == 'bc')) {
 696      debug(array('bcsub', $left, $right));
 697      return bcsub( $left, $right );
 698   }
 699   else { # Or $force == 'something else'
 700      debug(array('to_int', $left, $right));
 701      return to_int($left - $right);
 702   }
 703}
 704
 705# ============================================================================
 706# Add two big integers together as accurately as possible with reasonable
 707# effort.  This is tested in t/mysql_stats.php and copied, without tests, to
 708# ss_get_by_ssh.php.  $force is for testability.
 709# ============================================================================
 710function big_add ($left, $right, $force = null) {
 711   if ( is_null($left)  ) { $left = 0; }
 712   if ( is_null($right) ) { $right = 0; }
 713   if ( function_exists("gmp_add") && (is_null($force) || $force == 'gmp')) {
 714      debug(array('gmp_add', $left, $right));
 715      return gmp_strval( gmp_add( $left, $right ));
 716   }
 717   elseif ( function_exists("bcadd") && (is_null($force) || $force == 'bc')) {
 718      debug(array('bcadd', $left, $right));
 719      return bcadd( $left, $right );
 720   }
 721   else { # Or $force == 'something else'
 722      debug(array('to_int', $left, $right));
 723      return to_int($left + $right);
 724   }
 725}
 726
 727# ============================================================================
 728# Writes to a debugging log.
 729# ============================================================================
 730function debug($val) {
 731   global $debug_log;
 732   if ( !$debug_log ) {
 733      return;
 734   }
 735   if ( $fp = fopen($debug_log, 'a+') ) {
 736      $trace = debug_backtrace();
 737      $calls = array();
 738      $i    = 0;
 739      $line = 0;
 740      $file = '';
 741      foreach ( debug_backtrace() as $arr ) {
 742         if ( $i++ ) {
 743            $calls[] = "$arr[function]() at $file:$line";
 744         }
 745         $line = array_key_exists('line', $arr) ? $arr['line'] : '?';
 746         $file = array_key_exists('file', $arr) ? $arr['file'] : '?';
 747      }
 748      if ( !count($calls) ) {
 749         $calls[] = "at $file:$line";
 750      }
 751      fwrite($fp, date('Y-m-d h:i:s') . ' ' . implode(' <- ', $calls));
 752      fwrite($fp, "\n" . var_export($val, TRUE) . "\n");
 753      fclose($fp);
 754   }
 755   else { # Disable logging
 756      print("Warning: disabling debug logging to $debug_log\n");
 757      $debug_log = FALSE;
 758   }
 759}
 760
 761# ============================================================================
 762# Everything from this point down is the functions that do the specific work to
 763# get and parse command output.  These are called from get_by_ssh().  The work
 764# is broken down into parts by several functions, one set for each type of data
 765# collection, based on the --type option:
 766# 0) Build a cache file name.
 767#    This is done in $type_cachefile().
 768# 1) Build a command-line string.
 769#    This is done in $type_cmdline() and will often be trivially simple.  The
 770#    resulting command-line string should use double-quotes wherever quotes
 771#    are needed, because it'll end up being enclosed in single-quotes if it
 772#    is executed remotely via SSH (which is typically the case).
 773# 2) SSH to the server and execute that command to get its output.
 774#    This is common code called from get_by_ssh(), in get_command_result().
 775# 3) Parse the result.
 776#    This is done in $type_parse().
 777# ============================================================================
 778
 779# ============================================================================
 780# Gets and parses /proc/stat from Linux.
 781# Options used: none.
 782# You can test it like this, as root:
 783# su - cacti -c 'env -i php /var/www/cacti/scripts/ss_get_by_ssh.php --type proc_stat --host 127.0.0.1 --items ag,ah'
 784# ============================================================================
 785function proc_stat_cachefile ( $options ) {
 786   return sanitize_filename($options, array('host', 'port'), 'proc_stat');
 787}
 788
 789function proc_stat_cmdline ( $options ) {
 790   return "cat /proc/stat";
 791}
 792
 793function proc_stat_parse ( $options, $output ) {
 794   $result = array(
 795      'STAT_interrupts'        => null,
 796      'STAT_context_switches'  => null,
 797      'STAT_forks'             => null,
 798   );
 799   $cpu_types = array(
 800      'STAT_CPU_user',
 801      'STAT_CPU_nice',
 802      'STAT_CPU_system',
 803      'STAT_CPU_idle',
 804      'STAT_CPU_iowait',
 805      'STAT_CPU_irq',
 806      'STAT_CPU_softirq',
 807      'STAT_CPU_steal',
 808      'STAT_CPU_guest',
 809   );
 810   foreach ( $cpu_types as $key ) {
 811      $result[$key] = null;
 812   }
 813
 814   foreach ( explode("\n", $output) as $line ) {
 815      if ( preg_match_all('/\w+/', $line, $words) ) {
 816         $words = $words[0];
 817         if ( $words[0] == "cpu" ) {
 818            for ( $i = 1; $i < count($words); ++$i ) {
 819               $result[$cpu_types[$i - 1]] = $words[$i];
 820            }
 821         }
 822         elseif ( $words[0] == "intr" ) {
 823            $result['STAT_interrupts'] = $words[1];
 824         }
 825         elseif ( $words[0] == "ctxt" ) {
 826            $result['STAT_context_switches'] = $words[1];
 827         }
 828         elseif ( $words[0] == "processes" ) {
 829            $result['STAT_forks'] = $words[1];
 830         }
 831      }
 832   }
 833   return $result;
 834}
 835
 836# ============================================================================
 837# Gets and parses the 'free' command from Linux.
 838# Options used: none.
 839# You can test it like this, as root:
 840# su - cacti -c 'env -i php /var/www/cacti/scripts/ss_get_by_ssh.php --type memory --host 127.0.0.1 --items au,av'
 841# ============================================================================
 842function memory_cachefile ( $options ) {
 843   return sanitize_filename($options, array('host', 'port'), 'memory');
 844}
 845
 846function memory_cmdline ( $options ) {
 847   return "free -ob";
 848}
 849
 850function memory_parse ( $options, $output ) {
 851   $result = array(
 852      'STAT_memcached' => null,
 853      'STAT_membuffer' => null,
 854      'STAT_memshared' => null,
 855      'STAT_memfree'   => null,
 856      'STAT_memused'   => null,
 857      'STAT_memtotal'  => null,
 858   );
 859
 860   foreach ( explode("\n", $output) as $line ) {
 861      if ( preg_match_all('/\S+/', $line, $words) ) {
 862         $words = $words[0];
 863         if ( $words[0] == "Mem:" ) {
 864            $result['STAT_memcached'] = $words[6];
 865            $result['STAT_membuffer'] = $words[5];
 866            $result['STAT_memshared'] = $words[4];
 867            $result['STAT_memfree']   = $words[3];
 868            $result['STAT_memtotal']  = $words[1];
 869            $result['STAT_memused']   = sprintf('%.0f',
 870               $words[2] - $words[4] - $words[5] - $words[6]);
 871         }
 872      }
 873   }
 874   return $result;
 875}
 876
 877# ============================================================================
 878# Gets and parses the results of the 'w' command from Linux.  Actually it's
 879# designed to get loadavg and number of users, so it uses 'uptime' instead; it
 880# used to use 'w' but uptime prints the same thing.
 881# Options used: none.
 882# You can test it like this, as root:
 883# su - cacti -c 'env -i php /var/www/cacti/scripts/ss_get_by_ssh.php --type w --host 127.0.0.1 --items as,at'
 884# ============================================================================
 885function w_cachefile ( $options ) {
 886   return sanitize_filename($options, array('host', 'port'), 'w');
 887}
 888
 889function w_cmdline ( $options ) {
 890   return "uptime";
 891}
 892
 893function w_parse ( $options, $output ) {
 894   $result = array(
 895      'STAT_loadavg'        => null,
 896      'STAT_numusers'       => null,
 897   );
 898
 899   foreach ( explode("\n", $output) as $line ) {
 900      if ( preg_match_all('/(\d+) user[s]*, .*?(\d+\.\d+)$/', $line, $words) ) {
 901         $result['STAT_numusers'] = $words[1][0];
 902         $result['STAT_loadavg']  = $words[2][0];
 903      }
 904   }
 905   return $result;
 906}
 907
 908# ============================================================================
 909# Gets and parses /server-status from Apache.
 910# You can test it like this, as root:
 911# su - cacti -c 'env -i php /var/www/cacti/scripts/ss_get_by_ssh.php --type apache --host 127.0.0.1 --items ae,af'
 912# ============================================================================
 913function apache_cachefile ( $options ) {
 914   return sanitize_filename($options, array('host', 'port', 'server'), 'apache');
 915}
 916
 917function apache_cmdline ( $options ) {
 918   global $status_server, $status_url, $http_user, $http_pass;
 919   $srv = isset($options['server']) ? $options['server'] : $status_server;
 920   $url = isset($options['url'])    ? $options['url']    : $status_url;
 921   $user = isset($options['http-user'])     ? $options['http-user']     : $http_user;
 922   $pass = isset($options['http-password']) ? $options['http-password'] : $http_pass;
 923   $port = isset($options['port2']) ? ":$options[port2]" : '';
 924   $auth = ($user ? "--http-user=$user" : '') . ' ' . ($pass ? "--http-password=$pass" : '');
 925   return "wget $auth -U Cacti/1.0 -q -O - -T 5 \"http://$srv$port$url?auto\"";
 926}
 927
 928function apache_parse ( $options, $output ) {
 929   $result = array(
 930      'Requests'     => null,
 931      'Bytes_sent'   => null,
 932      'Idle_workers' => null,
 933      'Busy_workers' => null,
 934      'CPU_Load'     => null,
 935      # More are added from $scoreboard below.
 936   );
 937
 938   # Mapping from Scoreboard statuses to friendly labels
 939   $scoreboard = array(
 940      '_' => 'Waiting_for_connection',
 941      'S' => 'Starting_up',
 942      'R' => 'Reading_request',
 943      'W' => 'Sending_reply',
 944      'K' => 'Keepalive',
 945      'D' => 'DNS_lookup',
 946      'C' => 'Closing_connection',
 947      'L' => 'Logging',
 948      'G' => 'Gracefully_finishing',
 949      'I' => 'Idle_cleanup',
 950      '.' => 'Open_slot',
 951   );
 952   foreach ( $scoreboard as $key => $val ) {
 953      # These are not null, they are zero, when they aren't in the output.
 954      $result[$val] = 0;
 955   }
 956
 957   # Mapping from line prefix to data item name
 958   $mapping = array (
 959      "Total Accesses" => 'Requests',
 960      "Total kBytes"   => 'Bytes_sent',
 961      "CPULoad"        => 'CPU_Load',
 962      "BusyWorkers"    => 'Busy_workers',
 963      "IdleWorkers"    => 'Idle_workers',
 964   );
 965
 966   foreach ( explode("\n", $output ) as $line ) {
 967      $words = explode(": ", $line);
 968      if ( $words[0] == "Total kBytes" ) {
 969         $words[1] = big_multiply($words[1], 1024);
 970      }
 971
 972      if ( array_key_exists($words[0], $mapping) ) {
 973         # Check for really small values indistinguishable from 0, but otherwise
 974         # just copy the value to the output.
 975         $result[$mapping[$words[0]]] = strstr($words[1], 'e') ? 0 : $words[1];
 976      }
 977      elseif ( $words[0] == "Scoreboard" ) {
 978         $string = $words[1];
 979         $length = strlen($string);
 980         for ( $i = 0; $i < $length ; $i++ ) {
 981            increment($result, $scoreboard[$string[$i]], 1);
 982         }
 983      }
 984   }
 985   return $result;
 986}
 987
 988# ============================================================================
 989# Gets /server-status from Nginx.
 990# You can test it like this, as root:
 991# su - cacti -c 'env -i php /var/www/cacti/scripts/ss_get_by_ssh.php --type nginx --host 127.0.0.1 --items az,b0'
 992# ============================================================================
 993function nginx_cachefile ( $options ) {
 994   return sanitize_filename($options, array('host', 'port', 'port2', 'server'), 'nginx');
 995}
 996
 997function nginx_cmdline ( $options ) {
 998   global $status_server, $status_url, $http_user, $http_pass;
 999   $srv = isset($options['server']) ? $options['server'] : $status_server;
1000   $url = isset($options['url'])    ? $options['url']    : $status_url;
1001   $user = isset($options['http-user'])     ? $options['http-user']     : $http_user;
1002   $pass = isset($options['http-password']) ? $options['http-password'] : $http_pass;
1003   $port = isset($options['port2']) ? ":$options[port2]" : '';
1004   $auth = ($user ? "--http-user=$user" : '') . ' ' . ($pass ? "--http-password=$pass" : '');
1005   return "wget $auth -U Cacti/1.0 -q -O - -T 5 \"http://$srv$port$url?auto\"";
1006}
1007
1008function nginx_parse ( $options, $output ) {
1009   $result = array(
1010      'NGINX_active_connections' => null,
1011      'NGINX_server_accepts'     => null,
1012      'NGINX_server_handled'     => null,
1013      'NGINX_server_requests'    => null,
1014      'NGINX_reading'            => null,
1015      'NGINX_writing'            => null,
1016      'NGINX_waiting'            => null,
1017   );
1018
1019   foreach ( explode("\n", $output) as $line ) {
1020      if ( preg_match_all('/\S+/', $line, $words) ) {
1021         $words = $words[0];
1022         if ( $words[0] == 'Active' ) {
1023            $result['NGINX_active_connections'] = $words[2];
1024         }
1025         elseif ( $words[0] == 'Reading:' ) {
1026            $result['NGINX_reading'] = $words[1];
1027            $result['NGINX_writing'] = $words[3];
1028            $result['NGINX_waiting'] = $words[5];
1029         }
1030         elseif ( preg_match('/^\d+$/', $words[0]) ) {
1031            $result['NGINX_server_accepts']  = $words[0];
1032            $result['NGINX_server_handled']  = $words[1];
1033            $result['NGINX_server_requests'] = $words[2];
1034         }
1035      }
1036   }
1037   return $result;
1038}
1039
1040# ============================================================================
1041# Get and parse stats from memcached, using nc (netcat).
1042# You can test it like this, as root:
1043# su - cacti -c 'env -i php /var/www/cacti/scripts/ss_get_by_ssh.php --type memcached --host 127.0.0.1 --items b6,b7'
1044# ============================================================================
1045function memcached_cachefile ( $options ) {
1046   return sanitize_filename($options, array('host', 'port', 'port2', 'server'), 'memcached');
1047}
1048
1049function memcached_cmdline ( $options ) {
1050   global $memcache_port, $nc_cmd;
1051   $srv = isset($options['server']) ? $options['server'] : $options['host'];
1052   $prt = isset($options['port2'])  ? $options['port2']  : $memcache_port;
1053   return "echo \"stats\nquit\" | $nc_cmd $srv $prt";
1054}
1055
1056function memcached_parse ( $options, $output ) {
1057   $result = array();
1058   foreach ( explode("\n", $output) as $line ) {
1059      $words = explode(' ', $line);
1060      if ( count($words) && $words[0] === "STAT" ) {
1061         # rusage are in microseconds, but COUNTER does not accept fractions
1062         if ( $words[1] === 'rusage_user' || $words[1] === 'rusage_system' ) {
1063            $result["MEMC_$words[1]"]
1064               = sprintf('%.0f', 1000000 * trim($words[2]));
1065         }
1066         else {
1067            $result["MEMC_$words[1]"] = trim($words[2]);
1068         }
1069      }
1070   }
1071   return $result;
1072}
1073
1074# ============================================================================
1075# Get and parse stats from /proc/diskstats
1076# You can test it like this, as root:
1077# su - cacti -c 'env -i php /var/www/cacti/scripts/ss_get_by_ssh.php --type diskstats --device sda4 --host 127.0.0.1 --items bj,bk,bl,bm,bn,bo,bp'
1078# ============================================================================
1079function diskstats_cachefile ( $options ) {
1080   if ( !isset($options['device']) ) {
1081      die("--device is required for --type diskstats");
1082   }
1083   return sanitize_filename($options, array('host', 'port', 'device'), 'diskstats');
1084}
1085
1086function diskstats_cmdline ( $options ) {
1087   return "cat /proc/diskstats";
1088}
1089
1090function diskstats_parse ( $options, $output ) {
1091   if ( !isset($options['device']) ) {
1092      die("--device is required for --type diskstats");
1093   }
1094   foreach ( explode("\n", $output) as $line ) {
1095      if ( preg_match_all('/\S+/', $line, $words) ) {
1096         $words = $words[0];
1097         if ( count($words) > 2 && $words[2] === $options['device'] ) {
1098            if ( count($words) > 10 ) {
1099               return array(
1100                  'DISK_reads'              => $words[3],
1101                  'DISK_reads_merged'       => $words[4],
1102                  'DISK_sectors_read'       => $words[5],
1103                  'DISK_time_spent_reading' => $words[6],
1104                  'DISK_writes'             => $words[7],
1105                  'DISK_writes_merged'      => $words[8],
1106                  'DISK_sectors_written'    => $words[9],
1107                  'DISK_time_spent_writing' => $words[10],
1108                  'DISK_io_ops_in_progress' => $words[11],
1109                  'DISK_io_time'            => $words[12],
1110                  'DISK_io_time_weighted'   => $words[13],
1111               );
1112            }
1113            else { # Early 2.6 kernels had only 4 fields for partitions.
1114               return array(
1115                  'DISK_reads'              => $words[3],
1116                  'DISK_reads_merged'       => 0,
1117                  'DISK_sectors_read'       => $words[4],
1118                  'DISK_time_spent_reading' => 0,
1119                  'DISK_writes'             => $words[5],
1120                  'DISK_writes_merged'      => 0,
1121                  'DISK_sectors_written'    => $words[6],
1122                  'DISK_time_spent_writing' => 0,
1123                  'DISK_io_ops_in_progress' => 0,
1124                  'DISK_io_time'            => 0,
1125                  'DISK_io_time_weighted'   => 0,
1126               );
1127            }
1128         }
1129      }
1130   }
1131   debug("Looks like we did not find $options[device] in the output");
1132   return array();
1133}
1134
1135# ============================================================================
1136# Get and parse stats from /proc/user_beancounters for openvz graphs
1137# You can test it like this, as root:
1138# su - cacti -c 'env -i php /var/www/cacti/scripts/ss_get_by_ssh.php --type openvz --host 127.0.0.1 --items bu,bv,bw,bx,by,bz,c0'
1139# ============================================================================
1140function openvz_cachefile ( $options ) {
1141   return sanitize_filename($options, array('host', 'port'), 'openvz');
1142}
1143
1144function openvz_cmdline ( $options ) {
1145   global $openvz_cmd;
1146   $meth = isset($options['openvz_cmd'])
1147         ? $options['openvz_cmd']
1148         : $openvz_cmd;
1149   return $meth;
1150}
1151
1152function openvz_parse ( $options, $output ) {
1153   $headers = array();
1154   $result  = array();
1155   foreach ( explode("\n", $output) as $line ) {
1156      if ( preg_match_all('/\S+/', $line, $words) ) {
1157         $words = $words[0];
1158         if ( $words[0] === 'Version:' || $words[0] === 'dummy' ) {
1159            # An intro line or a dummy line
1160            continue;
1161         }
1162         else if ( $words[0] === 'uid' ) {
1163            # It's the header row.  Get the headers into the header array,
1164            # except for the UID header, which we don't need, and the resource
1165            # header, which just defines the leftmost header that's in every
1166            # subsequent line but isn't itself a statistic.
1167            array_shift($words);
1168            array_shift($words);
1169            $headers = $words;
1170            continue;
1171         }
1172         elseif ( strpos(strrev($words[0]), ':') === 0 ) {
1173            # This is a line that starts with something like "200:" and we want
1174            # to toss the first word in that line.  It's the UID of the
1175            # container.
1176            array_shift($words);
1177         }
1178         $row_hdr = array_shift($words);
1179         for ( $i = 0; $i < count($words); ++$i ) {
1180            $col_hdr = $headers[$i];
1181            if ( $col_hdr !== 'held' && $col_hdr !== 'failcnt' ) {
1182               continue; # Those are the only ones we're interested in.
1183            }
1184            $key = "OPVZ_${row_hdr}_${col_hdr}";
1185            $result[$key] = $words[$i];
1186         }
1187      }
1188   }
1189   return $result;
1190}
1191
1192# ============================================================================
1193# Get and parse stats from redis, using nc (netcat).
1194# You can test it like this, as root:
1195# su - cacti -c 'env -i php /var/www/cacti/scripts/ss_get_by_ssh.php --type redis --host 127.0.0.1 --items cy,cz,d2,d3
1196# ============================================================================
1197function redis_cachefile ( $options ) {
1198   global $status_server, $redis_port;
1199   return sanitize_filename($options, array('host', 'port', 'port2', 'server'), 'redis');
1200}
1201
1202# The function redis_get is defined below to use a TCP socket directly, so
1203# really the command-line won't be called.  The problem is that nc is so
1204# different in different systems.  We really need the -C option, but some nc
1205# don't have -C.
1206function redis_cmdline ( $options ) {
1207   global $redis_port, $nc_cmd;
1208   $srv = isset($options['server']) ? $options['server'] : $options['host'];
1209   $prt = isset($options['port2'])  ? $options['port2']  : $redis_port;
1210   return "echo INFO | $nc_cmd $srv $prt";
1211}
1212
1213function redis_parse ( $options, $output ) {
1214   $result = array();
1215   $wanted = array(
1216      'connected_clients',
1217      'connected_slaves',
1218      'used_memory',
1219      'changes_since_last_save',
1220      'total_connections_received',
1221      'total_commands_processed'
1222   );
1223   foreach ( explode("\n", $output) as $line ) {
1224      $words = explode(':', $line);
1225      if ( count($words) && in_array($words[0], $wanted) ) {
1226         $result["REDIS_$words[0]"] = trim($words[1]);
1227      }
1228   }
1229   return $result;
1230}
1231
1232function redis_get ( $options ) {
1233   global $redis_port;
1234   $port = isset($options['port2'])  ? $options[

Large files files are truncated, but you can click here to view the full file