PageRenderTime 67ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

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