nul'; } else { $command = read_config_option('path_rrdtool') . ' - > /dev/null 2>&1'; } return popen($command, 'w'); } function __rrd_proxy_init($logopt = 'WEBLOG') { global $encryption; $terminator = "_EOT_\r\n"; $encryption = true; $rsa = new \phpseclib\Crypt\RSA(); $rrdp_socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if ($rrdp_socket === false) { cacti_log('CACTI2RRDP ERROR: Unable to create socket to connect to RRDtool Proxy Server', false, $logopt, POLLER_VERBOSITY_LOW); return false; } if ( read_config_option('rrdp_load_balancing') == 'on' ) { $rrdp_id = rand(1,2); $rrdp = @socket_connect($rrdp_socket, (($rrdp_id == 1 ) ? read_config_option('rrdp_server') : read_config_option('rrdp_server_backup')), (($rrdp_id == 1 ) ? read_config_option('rrdp_port') : read_config_option('rrdp_port_backup')) ); } else { $rrdp_id = 1; $rrdp = @socket_connect($rrdp_socket, read_config_option('rrdp_server'), read_config_option('rrdp_port')); } if ($rrdp === false) { /* log entry ... */ cacti_log('CACTI2RRDP ERROR: Unable to connect to RRDtool Proxy Server #' . $rrdp_id, false, $logopt, POLLER_VERBOSITY_LOW); /* ... and try to use backup path */ $rrdp_id = ($rrdp_id + 1) % 2; $rrdp = @socket_connect($rrdp_socket, (($rrdp_id == 1 ) ? read_config_option('rrdp_server') : read_config_option('rrdp_server_backup')), (($rrdp_id == 1 ) ? read_config_option('rrdp_port') : read_config_option('rrdp_port_backup')) ); if ($rrdp === false) { cacti_log('CACTI2RRDP ERROR: Unable to connect to RRDtool Proxy Server #' . $rrdp_id, false, $logopt, POLLER_VERBOSITY_LOW); return false; } } $rrdp_fingerprint = ($rrdp_id == 1 ) ? read_config_option('rrdp_fingerprint') : read_config_option('rrdp_fingerprint_backup'); socket_write($rrdp_socket, read_config_option('rsa_public_key') . $terminator); /* read public key being returned by the proxy server */ $rrdp_public_key = ''; while(1) { $recv = socket_read($rrdp_socket, 1000, PHP_BINARY_READ ); if ($recv === false) { /* timeout */ cacti_log('CACTI2RRDP ERROR: Public RSA Key Exchange - Time-out while reading', false, $logopt, POLLER_VERBOSITY_LOW); $rrdp_public_key = false; break; } elseif ($recv == '') { cacti_log('CACTI2RRDP ERROR: Session closed by Proxy.', false, $logopt, POLLER_VERBOSITY_LOW); /* session closed by Proxy */ break; } else { $rrdp_public_key .= $recv; if (strpos($rrdp_public_key, $terminator) !== false) { $rrdp_public_key = trim(trim($rrdp_public_key, $terminator)); break; } } } $rsa->loadKey($rrdp_public_key); $fingerprint = $rsa->getPublicKeyFingerprint(); if ($rrdp_fingerprint != $fingerprint) { cacti_log('CACTI2RRDP ERROR: Mismatch RSA Fingerprint.', false, $logopt, POLLER_VERBOSITY_LOW); return false; } else { $rrdproxy = array($rrdp_socket, $rrdp_public_key); /* set the rrdtool default font */ if (read_config_option('path_rrdtool_default_font')) { rrdtool_execute("setenv RRD_DEFAULT_FONT '" . read_config_option('path_rrdtool_default_font') . "'", false, RRDTOOL_OUTPUT_NULL, $rrdproxy, $logopt = 'WEBLOG'); } /* disable encryption */ $encryption = rrdtool_execute('setcnn encryption off', false, RRDTOOL_OUTPUT_BOOLEAN, $rrdproxy, $logopt = 'WEBLOG') ? false : true; return $rrdproxy; } } function rrd_close() { global $config; $args = func_get_args(); $force_storage_location_local = (isset($config['force_storage_location_local']) && $config['force_storage_location_local'] === true) ? true : false; $function = ($force_storage_location_local === false && read_config_option('storage_location')) ? '__rrd_proxy_close' : '__rrd_close'; return call_user_func_array($function, $args); } function __rrd_close($rrdtool_pipe) { /* close the rrdtool file descriptor */ if (is_resource($rrdtool_pipe)) { pclose($rrdtool_pipe); } rrdtool_reset_language(); } function __rrd_proxy_close($rrdp) { /* close the rrdtool proxy server connection */ $terminator = "_EOT_\r\n"; if ($rrdp) { socket_write($rrdp[0], encrypt('quit', $rrdp[1]) . $terminator); @socket_shutdown($rrdp[0], 2); @socket_close($rrdp[0]); return; } } function encrypt($output, $rsa_key) { global $encryption; if ($encryption) { $rsa = new \phpseclib\Crypt\RSA(); $aes = new \phpseclib\Crypt\Rijndael(); $aes_key = \phpseclib\Crypt\Random::string(192); $aes->setKey($aes_key); $ciphertext = base64_encode($aes->encrypt($output)); $rsa->loadKey($rsa_key); $aes_key = base64_encode($rsa->encrypt($aes_key)); $aes_key_length = str_pad(dechex(strlen($aes_key)),3,'0',STR_PAD_LEFT); return $aes_key_length . $aes_key . $ciphertext; } else { return $output; } } function decrypt($input) { global $encryption; if ($encryption) { $rsa = new \phpseclib\Crypt\RSA(); $aes = new \phpseclib\Crypt\Rijndael(); $rsa_private_key = read_config_option('rsa_private_key'); $aes_key_length = hexdec(substr($input,0,3)); $aes_key = base64_decode(substr($input,3,$aes_key_length)); $ciphertext = base64_decode(substr($input,3+$aes_key_length)); $rsa->loadKey( $rsa_private_key ); $aes_key = $rsa->decrypt($aes_key); $aes->setKey($aes_key); $plaintext = $aes->decrypt($ciphertext); return $plaintext; } else { return $input; } } function rrdtool_execute() { global $config; $args = func_get_args(); $force_storage_location_local = (isset($config['force_storage_location_local']) && $config['force_storage_location_local'] === true) ? true : false; $function = ($force_storage_location_local === false && read_config_option('storage_location')) ? '__rrd_proxy_execute' : '__rrd_execute'; return call_user_func_array($function, $args); } function __rrd_execute($command_line, $log_to_stdout, $output_flag, $rrdtool_pipe = false, $logopt = 'WEBLOG') { global $config; static $last_command; if (!is_numeric($output_flag)) { $output_flag = RRDTOOL_OUTPUT_STDOUT; } /* WIN32: before sending this command off to rrdtool, get rid of all of the backslash (\) characters. Unix does not care; win32 does. Also make sure to replace all of the backslashes at the end of the line, but make sure not to get rid of newlines (\n) that are supposed to be in there (text format) */ $command_line = str_replace("\\\n", ' ', $command_line); /* output information to the log file if appropriate */ cacti_log('CACTI2RRD: ' . read_config_option('path_rrdtool') . " $command_line", $log_to_stdout, $logopt, POLLER_VERBOSITY_DEBUG); $debug = ''; /* if we want to see the error output from rrdtool; make sure to specify this */ if ($config['cacti_server_os'] != 'win32') { if (($output_flag == RRDTOOL_OUTPUT_STDERR || $output_flag == RRDTOOL_OUTPUT_RETURN_STDERR) && !is_resource($rrdtool_pipe)) { $debug .= ' 2>&1'; } } /* use popen to eliminate the zombie issue */ if ($config['cacti_server_os'] == 'unix') { $pipe_mode = 'r'; } else { $pipe_mode = 'rb'; } /* an empty $rrdtool_pipe array means no fp is available */ if (!is_resource($rrdtool_pipe)) { if (substr($command_line, 0, 5) == 'fetch' || substr($command_line, 0, 4) == 'info') { rrdtool_set_language('en'); } else { rrdtool_set_language(); } cacti_session_close(); if (is_file(read_config_option('path_rrdtool')) && is_executable(read_config_option('path_rrdtool'))) { $descriptorspec = array( 0 => array('pipe', 'r'), 1 => array('pipe', 'w') ); if ($config['is_web']) { if (isset($_COOKIE['CactiTimeZone'])) { $gmt_offset = $_COOKIE['CactiTimeZone']; cacti_time_zone_set($gmt_offset); } } $process = proc_open(read_config_option('path_rrdtool') . ' - ' . $debug, $descriptorspec, $pipes); if (!is_resource($process)) { unset($process); } else { fwrite($pipes[0], escape_command($command_line) . "\r\nquit\r\n"); fclose($pipes[0]); $fp = $pipes[1]; } } else { cacti_log("ERROR: RRDtool executable not found, not executable or error in path '" . read_config_option('path_rrdtool') . "'. No output written to RRDfile."); } rrdtool_reset_language(); } else { $i = 0; while (1) { if (fwrite($rrdtool_pipe, escape_command(" $command_line") . "\r\n") === false) { cacti_log("ERROR: Detected RRDtool Crash on '$command_line'. Last command was '$last_command'"); /* close the invalid pipe */ rrd_close($rrdtool_pipe); /* open a new rrdtool process */ $rrdtool_pipe = rrd_init(); if ($i > 4) { cacti_log("FATAL: RRDtool Restart Attempts Exceeded. Giving up on '$command_line'."); break; } else { $i++; } continue; } else { fflush($rrdtool_pipe); break; } } } /* store the last command to provide rrdtool segfault diagnostics */ $last_command = $command_line; if (!isset($fp)) { return; } switch ($output_flag) { case RRDTOOL_OUTPUT_STDOUT: case RRDTOOL_OUTPUT_GRAPH_DATA: $output = ''; while (!feof($fp)) { $output .= fgets($fp, 4096); } if (isset($process)) { fclose($fp); proc_close($process); } rrdtool_trim_output($output); return $output; break; case RRDTOOL_OUTPUT_STDERR: case RRDTOOL_OUTPUT_RETURN_STDERR: $output = fgets($fp, 1000000); if (isset($process)) { fclose($fp); proc_close($process); } rrdtool_trim_output($output); if (substr($output, 1, 3) == 'PNG') { return 'OK'; } if (substr($output, 0, 5) == '= 8192) { $command_line = gzencode($command_line, 1); } socket_write($rrdp_socket, encrypt($command_line, $rrdp_public_key) . $end_of_sequence); $input = ''; $output = ''; while(1) { $recv = socket_read($rrdp_socket, 100000, PHP_BINARY_READ ); if ($recv === false) { cacti_log('CACTI2RRDP ERROR: Data Transfer - Time-out while reading.', $log_to_stdout, $logopt, POLLER_VERBOSITY_LOW); break; } elseif ($recv == '') { /* session closed by Proxy */ if ($output) { cacti_log('CACTI2RRDP ERROR: Session closed by Proxy.', $log_to_stdout, $logopt, POLLER_VERBOSITY_LOW); } break; } else { $input .= $recv; if (strpos($input, $end_of_sequence) !== false) { $input = str_replace($end_of_sequence, '', $input); $transactions = explode($end_of_packet, $input); foreach ($transactions as $transaction) { $packet = $transaction; $transaction = decrypt($transaction); if ($transaction === false) { cacti_log("CACTI2RRDP ERROR: Proxy message decryption failed: ###". $packet . '###', $log_to_stdout, $logopt, POLLER_VERBOSITY_LOW); break 2; } if (strpos($transaction, "\x1f\x8b") === 0) { $transaction = gzdecode($transaction); } $output .= $transaction; if (substr_count($output, 'OK u') || substr_count($output, 'ERROR:')) { cacti_log('RRDP: ' . $output, $log_to_stdout, $logopt, POLLER_VERBOSITY_DEBUG); break 2; } } } } } if ($rrdp_auto_close) { __rrd_proxy_close($rrdp); } switch ($output_flag) { case RRDTOOL_OUTPUT_NULL: return; case RRDTOOL_OUTPUT_STDOUT: case RRDTOOL_OUTPUT_GRAPH_DATA: return rtrim(substr($output, 0, strpos($output, 'OK u'))); break; case RRDTOOL_OUTPUT_STDERR: if (substr($output, 1, 3) == 'PNG') { return 'OK'; } if (substr($output, 0, 5) == 'GIF87') { return 'OK'; } if (substr($output, 0, 5) == ' 0) { $data_sources = db_fetch_assoc_prepared('SELECT DISTINCT dtr.id, dtr.data_source_name, dtr.rrd_heartbeat, dtr.rrd_minimum, dtr.rrd_maximum, dtr.data_source_type_id FROM data_template_rrd AS dtr INNER JOIN graph_templates_item AS gti ON dtr.id = gti.task_item_id WHERE local_data_id = ? ORDER BY local_data_template_rrd_id', array($local_data_id) ); } else { $data_sources = db_fetch_assoc_prepared('SELECT DISTINCT dtr.id, dtr.data_source_name, dtr.rrd_heartbeat, dtr.rrd_minimum, dtr.rrd_maximum, dtr.data_source_type_id FROM data_template_rrd AS dtr WHERE local_data_id = ? ORDER BY local_data_template_rrd_id', array($local_data_id) ); } /** * ONLY make a new DS entry if: * * - There are multiple data sources and this item is not the main one. * - There are only one data source (then use it) */ if (cacti_sizeof($data_sources)) { $data_local = db_fetch_row_prepared('SELECT host_id, snmp_query_id, snmp_index FROM data_local WHERE id = ?', array($local_data_id) ); $speed = rrdtool_function_interface_speed($data_local); foreach ($data_sources as $data_source) { /* use the cacti ds name by default or the user defined one, if entered */ $data_source_name = get_data_source_item_name($data_source['id']); // Trim the data source maximum $data_source['rrd_maximum'] = trim($data_source['rrd_maximum']); if ($data_source['rrd_maximum'] == 'U') { /* in case no maximum is given, use "Undef" value */ $data_source['rrd_maximum'] = 'U'; } elseif (strpos($data_source['rrd_maximum'], '|query_') !== false) { /* in case a query variable is given, evaluate it */ if ($data_source['rrd_maximum'] == '|query_ifSpeed|' || $data_source['rrd_maximum'] == '|query_ifHighSpeed|') { $data_source['rrd_maximum'] = $speed; } else { $data_source['rrd_maximum'] = substitute_snmp_query_data($data_source['rrd_maximum'], $data_local['host_id'], $data_local['snmp_query_id'], $data_local['snmp_index']); } } elseif ($data_source['rrd_maximum'] != 'U' && (int)$data_source['rrd_maximum'] <= (int)$data_source['rrd_minimum']) { /* max > min required, but take care of an "Undef" value */ if ($data_source['data_source_type_id'] == 1 || $data_source['data_source_type_id'] == 4) { $data_source['rrd_maximum'] = 'U'; } else { $data_source['rrd_maximum'] = (int)$data_source['rrd_minimum'] + 1; } } /* min==max==0 won't work with rrdtool */ if ($data_source['rrd_minimum'] == 0 && $data_source['rrd_maximum'] == 0) { $data_source['rrd_maximum'] = 'U'; } $create_ds .= "DS:$data_source_name:" . $data_source_types[$data_source['data_source_type_id']] . ':' . $data_source['rrd_heartbeat'] . ':' . $data_source['rrd_minimum'] . ':' . $data_source['rrd_maximum'] . RRD_NL; } } $create_rra = ''; /* loop through each available RRA for this DS */ foreach ($rras as $rra) { $create_rra .= 'RRA:' . $consolidation_functions[$rra['consolidation_function_id']] . ':' . $rra['x_files_factor'] . ':' . $rra['steps'] . ':' . $rra['rows'] . RRD_NL; } if ($config['cacti_server_os'] != 'win32') { $owner_id = fileowner($config['rra_path']); $group_id = filegroup($config['rra_path']); } /** * check for structured path configuration, if in place verify directory * exists and if not create it. */ if (read_config_option('extended_paths') == 'on') { if (read_config_option('storage_location')) { if (false === rrdtool_execute('is_dir ' . dirname($data_source_path), true, RRDTOOL_OUTPUT_BOOLEAN, $rrdtool_pipe, 'POLLER') ) { if (false === rrdtool_execute('mkdir ' . dirname($data_source_path), true, RRDTOOL_OUTPUT_BOOLEAN, $rrdtool_pipe, 'POLLER') ) { cacti_log("ERROR: Unable to create directory '" . dirname($data_source_path) . "'", false); } } } elseif (!is_dir(dirname($data_source_path))) { if ($config['is_web'] == false || is_writable($config['rra_path'])) { if (mkdir(dirname($data_source_path), 0775, true)) { if ($config['cacti_server_os'] != 'win32' && posix_getuid() == 0) { $success = true; $paths = explode('/', str_replace($config['rra_path'], '/', dirname($data_source_path))); $spath = ''; foreach($paths as $path) { if ($path == '') { continue; } $spath .= '/' . $path; $powner_id = fileowner($config['rra_path'] . $spath); $pgroup_id = fileowner($config['rra_path'] . $spath); if ($powner_id != $owner_id) { $success = chown($config['rra_path'] . $spath, $owner_id); } if ($pgroup_id != $group_id && $success) { $success = chgrp($config['rra_path'] . $spath, $group_id); } if (!$success) { cacti_log("ERROR: Unable to set directory permissions for '" . $config['rra_path'] . $spath . "'", false); break; } } } } else { cacti_log("ERROR: Unable to create directory '" . dirname($data_source_path) . "'", false); } } else { cacti_log("WARNING: Poller has not created structured path '" . dirname($data_source_path) . "' yet.", false); } } } if ($show_source == true) { return read_config_option('path_rrdtool') . ' create' . RRD_NL . "$data_source_path$create_ds$create_rra"; } else { $success = rrdtool_execute("create $data_source_path $create_ds$create_rra", true, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'POLLER'); if ($config['cacti_server_os'] != 'win32' && posix_getuid() == 0) { shell_exec("chown $owner_id:$group_id $data_source_path"); } return $success; } } function rrdtool_function_update($update_cache_array, $rrdtool_pipe = false) { /* lets count the number of rrd files processed */ $rrds_processed = 0; foreach ($update_cache_array as $rrd_path => $rrd_fields) { $create_rrd_file = false; if (is_array($rrd_fields['times']) && cacti_sizeof($rrd_fields['times'])) { /* create the rrd if one does not already exist */ if (read_config_option('storage_location') > 0) { $file_exists = rrdtool_execute("file_exists $rrd_path", true, RRDTOOL_OUTPUT_BOOLEAN, $rrdtool_pipe, 'POLLER'); } else { $file_exists = file_exists($rrd_path); } ksort($rrd_fields['times']); if ($file_exists === false) { $times = array_keys($rrd_fields['times']); rrdtool_function_create($rrd_fields['local_data_id'], false, $rrdtool_pipe); $create_rrd_file = true; } if (isset($rrd_fields['data_template_id'])) { $data_template_id = $rrd_fields['data_template_id']; } else { $data_template_id = db_fetch_cell_prepared('SELECT data_template_id FROM data_local WHERE id = ?', array($rrd_fields['local_data_id'])); } if ($data_template_id > 0) { $unused_data_source_names = array_rekey( db_fetch_assoc_prepared('SELECT DISTINCT dtr.data_source_name, dtr.data_source_name FROM data_template_rrd AS dtr LEFT JOIN graph_templates_item AS gti ON dtr.id = gti.task_item_id WHERE dtr.local_data_id = ? AND gti.task_item_id IS NULL', array($rrd_fields['local_data_id'])), 'data_source_name', 'data_source_name' ); } else { $unused_data_source_names = array(); } foreach ($rrd_fields['times'] as $update_time => $field_array) { if (empty($update_time)) { /* default the rrdupdate time to now */ $rrd_update_values = 'N:'; } else { $rrd_update_values = $update_time . ':'; } $rrd_update_template = ''; foreach ($field_array as $field_name => $value) { if (cacti_sizeof($unused_data_source_names) && isset($unused_data_source_names[$field_name])) { continue; } if ($rrd_update_template != '') { $rrd_update_template .= ':'; $rrd_update_values .= ':'; } $rrd_update_template .= $field_name; /* if we have "invalid data", give rrdtool an Unknown (U) */ if (!isset($value) || !is_numeric($value)) { $value = 'U'; } $rrd_update_values .= $value; } if (cacti_version_compare(get_rrdtool_version(), '1.5', '>=')) { $update_options = '--skip-past-updates'; } else { $update_options = ''; } if (isset($rrd_fields['template']) && $rrd_update_template == '') { $rrd_update_template = $rrd_fields['template']; } rrdtool_execute("update $rrd_path $update_options --template $rrd_update_template $rrd_update_values", true, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'POLLER'); $rrds_processed++; } } } return $rrds_processed; } function rrdtool_function_tune($rrd_tune_array) { global $config, $data_source_types; include($config['include_path'] . '/global_arrays.php'); $data_source_name = get_data_source_item_name($rrd_tune_array['data_source_id']); $data_source_type = $data_source_types[$rrd_tune_array['data-source-type']]; $data_source_path = get_data_source_path($rrd_tune_array['data_source_id'], true); $rrd_tune = ''; if ($rrd_tune_array['heartbeat'] != '') { $rrd_tune .= " --heartbeat $data_source_name:" . $rrd_tune_array['heartbeat']; } if ($rrd_tune_array['minimum'] != '') { $rrd_tune .= " --minimum $data_source_name:" . $rrd_tune_array['minimum']; } if ($rrd_tune_array['maximum'] != '') { $rrd_tune .= " --maximum $data_source_name:" . $rrd_tune_array['maximum']; } if ($rrd_tune_array['data-source-type'] != '') { $rrd_tune .= " --data-source-type $data_source_name:" . $data_source_type; } if ($rrd_tune_array['data-source-rename'] != '') { $rrd_tune .= " --data-source-rename $data_source_name:" . $rrd_tune_array['data-source-rename']; } if ($rrd_tune != '') { if (file_exists($data_source_path) == true) { if (is_file(read_config_option('path_rrdtool')) && is_executable(read_config_option('path_rrdtool'))) { $fp = popen(read_config_option('path_rrdtool') . " tune $data_source_path $rrd_tune", 'r'); pclose($fp); cacti_log('CACTI2RRD: ' . read_config_option('path_rrdtool') . " tune $data_source_path $rrd_tune", false, 'WEBLOG', POLLER_VERBOSITY_DEBUG); } else { cacti_log("ERROR: RRDtool executable not found, not executable or error in path '" . read_config_option('path_rrdtool') . "'. No output written to RRDfile."); } } } } /* rrdtool_function_fetch - given a data source, return all of its data in an array @arg $local_data_id - the data source to fetch data for @arg $start_time - the start time to use for the data calculation. this value can either be absolute (unix timestamp) or relative (to now) @arg $end_time - the end time to use for the data calculation. this value can either be absolute (unix timestamp) or relative (to now) @arg $resolution - the accuracy of the data measured in seconds @arg $show_unknown - Show unknown 'NAN' values in the output as 'U' @arg $rrdtool_file - Don't force Cacti to calculate the file @arg $cf - Specify the consolidation function to use @arg $rrdtool_pipe - a pipe to an rrdtool command @returns - (array) an array containing all data in this data source broken down by each data source item. the maximum of all data source items is included in an item called 'nth_percentile_maximum'. The array will look as follows: $fetch_array['data_source_names'][0] = 'ds1' $fetch_array['data_source_names'][1] = 'ds2' $fetch_array['data_source_names'][2] = 'nth_percentile_maximum' $fetch_array['start_time'] = $timestamp; $fetch_array['end_time'] = $timestamp; $fetch_array['values'][$dsindex1][...] = $value; $fetch_array['values'][$dsindex2][...] = $value; $fetch_array['values'][$nth_index][...] = $value; Again, the 'nth_percentile_maximum' will have the maximum value amongst all the data sources for each set of data. So, if you have traffic_in and traffic_out, each member element in the array will have the maximum of traffic_in and traffic_out in it. */ function rrdtool_function_fetch($local_data_id, $start_time, $end_time, $resolution = 0, $show_unknown = false, $rrdtool_file = null, $cf = 'AVERAGE', $rrdtool_pipe = false) { global $config; include_once($config['library_path'] . '/boost.php'); /* validate local data id */ if (empty($local_data_id) && is_null($rrdtool_file)) { return array(); } $time = time(); /* initialize fetch array */ $fetch_array = array(); /* check if we have been passed a file instead of local data source to look up */ if (is_null($rrdtool_file)) { $data_source_path = get_data_source_path($local_data_id, true); } else { $data_source_path = $rrdtool_file; } // Find the correct resolution if ($resolution == 0) { $resolution = rrdtool_function_get_resstep($local_data_id, $start_time, $end_time, 'res'); } /* update the rrdfile if performing a fetch */ boost_fetch_cache_check($local_data_id, $rrdtool_pipe); /* build and run the rrdtool fetch command with all of our data */ $cmd_line = "fetch $data_source_path $cf -s $start_time -e $end_time"; if ($resolution > 0) { $cmd_line .= " -r $resolution"; } $output = rrdtool_execute($cmd_line, false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe); $output = explode("\n", $output); $first = true; $count = 0; if (cacti_sizeof($output)) { $timestamp = 0; foreach($output as $line) { $line = trim($line); $max_array = array(); if ($first) { /* get the data source names */ $fetch_array['data_source_names'] = preg_split('/\s+/', $line); $first = false; } elseif ($line != '') { /* process the data sources into an array */ $parts = explode(':', $line); $timestamp = $parts[0]; $data = explode(' ', trim($parts[1])); if (!isset($fetch_array['timestamp']['start_time'])) { $fetch_array['timestamp']['start_time'] = $timestamp; } /* process out bad data */ foreach($data as $index => $number) { if (strtolower($number) == 'nan' || strtolower($number) == '-nan') { if ($show_unknown) { $fetch_array['values'][$index][$timestamp] = 'U'; } } elseif (is_numeric($number)) { $fetch_array['values'][$index][$timestamp] = $number; } elseif ($show_unknown) { $fetch_array['values'][$index][$timestamp] = 'U'; } } } } $fetch_array['timestamp']['end_time'] = $timestamp; } return $fetch_array; } function rrd_function_process_graph_options($graph_start, $graph_end, &$graph, &$graph_data_array) { global $config, $image_types; include($config['include_path'] . '/global_arrays.php'); /* define some variables */ $scale = ''; $rigid = ''; $unit_value = ''; $version = get_rrdtool_version(); $unit_exponent_value = ''; if ($graph['auto_scale'] == 'on') { switch ($graph['auto_scale_opts']) { case '1': /* autoscale ignores lower, upper limit */ $scale = '--alt-autoscale' . RRD_NL; break; case '2': /* autoscale-max, accepts a given lower limit */ $scale = '--alt-autoscale-max' . RRD_NL; if (is_numeric($graph['lower_limit'])) { $scale .= '--lower-limit=' . cacti_escapeshellarg($graph['lower_limit']) . RRD_NL; } break; case '3': /* autoscale-min, accepts a given upper limit */ $scale = '--alt-autoscale-min' . RRD_NL; if ( is_numeric($graph['upper_limit'])) { $scale .= '--upper-limit=' . cacti_escapeshellarg($graph['upper_limit']) . RRD_NL; } break; case '4': /* auto_scale with limits */ $scale = '--alt-autoscale' . RRD_NL; if ( is_numeric($graph['upper_limit'])) { $scale .= '--upper-limit=' . cacti_escapeshellarg($graph['upper_limit']) . RRD_NL; } if ( is_numeric($graph['lower_limit'])) { $scale .= '--lower-limit=' . cacti_escapeshellarg($graph['lower_limit']) . RRD_NL; } break; } } else { if ($graph['upper_limit'] != '') { $scale = '--upper-limit=' . cacti_escapeshellarg($graph['upper_limit']) . RRD_NL; } if ($graph['lower_limit'] != '') { $scale .= '--lower-limit=' . cacti_escapeshellarg($graph['lower_limit']) . RRD_NL; } } if ($graph['auto_scale_log'] == 'on') { $scale .= '--logarithmic' . RRD_NL; } /* --units=si only defined for logarithmic y-axis scaling, even if it doesn't hurt on linear graphs */ if ($graph['scale_log_units'] == 'on' && $graph['auto_scale_log'] == 'on') { $scale .= '--units=si' . RRD_NL; } if ($graph['auto_scale_rigid'] == 'on') { $rigid = '--rigid' . RRD_NL; } if ($graph['unit_value'] != '') { $unit_value = '--y-grid=' . cacti_escapeshellarg($graph['unit_value']) . RRD_NL; } if (preg_match('/^[0-9]+$/', $graph['unit_exponent_value'])) { $unit_exponent_value = '--units-exponent=' . cacti_escapeshellarg($graph['unit_exponent_value']) . RRD_NL; } /* * optionally you can specify and array that overrides some of the db's values, lets set * that all up here */ /* override: graph height (in pixels) */ if (isset($graph_data_array['graph_height'])) { $graph_height = $graph_data_array['graph_height']; } else { $graph_height = $graph['height']; } /* override: graph width (in pixels) */ if (isset($graph_data_array['graph_width'])) { $graph_width = $graph_data_array['graph_width']; } else { $graph_width = $graph['width']; } /* override: skip drawing the legend? */ if (isset($graph_data_array['graph_nolegend'])) { $graph_legend = '--no-legend' . RRD_NL; } else { $graph_legend = ''; } /* export options */ if (isset($graph_data_array['export'])) { $graph_opts = $graph_data_array['export_filename'] . RRD_NL; } else { if (empty($graph_data_array['output_filename'])) { $graph_opts = '-' . RRD_NL; } else { $graph_opts = $graph_data_array['output_filename'] . RRD_NL; } } if (isset($graph_data_array['image_format']) && $graph_data_array['image_format'] == 'png') { $graph['image_format_id'] = 1; } /* basic graph options */ $graph_opts .= '--imgformat=' . $image_types[$graph['image_format_id']] . RRD_NL . '--start=' . cacti_escapeshellarg($graph_start) . RRD_NL . '--end=' . cacti_escapeshellarg($graph_end) . RRD_NL; $graph_opts .= '--pango-markup ' . RRD_NL; if (read_config_option('rrdtool_watermark') == 'on') { $graph_opts .= '--disable-rrdtool-tag ' . RRD_NL; } foreach($graph as $key => $value) { switch($key) { case 'title_cache': if (!empty($value)) { $graph_opts .= '--title=' . cacti_escapeshellarg(html_escape($value)) . RRD_NL; } break; case 'alt_y_grid': if ($value == CHECKED) { $graph_opts .= '--alt-y-grid' . RRD_NL; } break; case 'unit_value': if (!empty($value)) { $graph_opts .= '--y-grid=' . cacti_escapeshellarg($value) . RRD_NL; } break; case 'unit_exponent_value': if (preg_match('/^[0-9]+$/', $value)) { $graph_opts .= '--units-exponent=' . $value . RRD_NL; } break; case 'height': if (isset($graph_data_array['graph_height']) && preg_match('/^[0-9]+$/', $graph_data_array['graph_height'])) { $graph_opts .= '--height=' . $graph_data_array['graph_height'] . RRD_NL; } else { $graph_opts .= '--height=' . $value . RRD_NL; } break; case 'width': if (isset($graph_data_array['graph_width']) && preg_match('/^[0-9]+$/', $graph_data_array['graph_width'])) { $graph_opts .= '--width=' . $graph_data_array['graph_width'] . RRD_NL; } else { $graph_opts .= '--width=' . $value . RRD_NL; } break; case 'graph_nolegend': if (isset($graph_data_array['graph_nolegend'])) { $graph_opts .= '--no-legend' . RRD_NL; } else { $graph_opts .= ''; } break; case 'base_value': if ($value == 1000 || $value == 1024) { $graph_opts .= '--base=' . $value . RRD_NL; } break; case 'vertical_label': if (!empty($value)) { $graph_opts .= '--vertical-label=' . cacti_escapeshellarg(html_escape($value)) . RRD_NL; } break; case 'slope_mode': if ($value == CHECKED) { $graph_opts .= '--slope-mode' . RRD_NL; } break; case 'right_axis': if (!empty($value)) { $graph_opts .= '--right-axis ' . cacti_escapeshellarg($value) . RRD_NL; } break; case 'right_axis_label': if (!empty($value)) { $graph_opts .= '--right-axis-label ' . cacti_escapeshellarg($value) . RRD_NL; } break; case 'right_axis_format': if (!empty($value)) { $format = db_fetch_cell_prepared('SELECT gprint_text from graph_templates_gprint WHERE id = ?', array($value)); $graph_opts .= '--right-axis-format ' . cacti_escapeshellarg(trim(str_replace('%s', '', $format))) . RRD_NL; } break; case 'no_gridfit': if ($value == CHECKED) { $graph_opts .= '--no-gridfit' . RRD_NL; } break; case 'unit_length': if (!empty($value)) { $graph_opts .= '--units-length ' . cacti_escapeshellarg($value) . RRD_NL; } break; case 'tab_width': if (!empty($value)) { $graph_opts .= '--tabwidth ' . cacti_escapeshellarg($value) . RRD_NL; } break; case 'dynamic_labels': if ($value == CHECKED) { $graph_opts .= '--dynamic-labels' . RRD_NL; } break; case 'force_rules_legend': if ($value == CHECKED) { $graph_opts .= '--force-rules-legend' . RRD_NL; } break; case 'legend_position': if (cacti_version_compare($version, '1.4', '>=')) { if (!empty($value)) { $graph_opts .= '--legend-position ' . cacti_escapeshellarg($value) . RRD_NL; } } break; case 'legend_direction': if (cacti_version_compare($version, '1.4', '>=')) { if (!empty($value)) { $graph_opts .= '--legend-direction ' . cacti_escapeshellarg($value) . RRD_NL; } } break; case 'left_axis_formatter': if (cacti_version_compare($version, '1.4', '>=')) { if (!empty($value)) { $graph_opts .= '--left-axis-formatter ' . cacti_escapeshellarg($value) . RRD_NL; } } break; case 'right_axis_formatter': if (cacti_version_compare($version, '1.4', '>=')) { if (!empty($value)) { $graph_opts .= '--right-axis-formatter ' . cacti_escapeshellarg($value) . RRD_NL; } } break; } } $graph_opts .= "$rigid" . trim("$scale$unit_value$unit_exponent_value$graph_legend", "\n\r " . RRD_NL) . RRD_NL; /* add a date to the graph legend */ $graph_opts .= rrdtool_function_format_graph_date($graph_data_array); /* process theme and font styling options */ $graph_opts .= rrdtool_function_theme_font_options($graph_data_array); /* Replace "|query_*|" in the graph command to replace e.g. vertical_label. */ $graph_opts = rrd_substitute_host_query_data($graph_opts, $graph, array()); /* provide smooth lines */ if ($graph['slope_mode'] == 'on') { $graph_opts .= '--slope-mode' . RRD_NL; } /* if the user desires a watermark set it */ $watermark = str_replace("'", '"', read_config_option('graph_watermark')); if ($watermark != '') { $graph_opts .= '--watermark ' . cacti_escapeshellarg($watermark) . RRD_NL; } return $graph_opts; } function rrdtool_function_graph($local_graph_id, $rra_id, $graph_data_array, $rrdtool_pipe = false, &$xport_meta = array(), $user = 0) { global $config, $consolidation_functions, $graph_item_types, $encryption; include_once($config['library_path'] . '/cdef.php'); include_once($config['library_path'] . '/vdef.php'); include_once($config['library_path'] . '/graph_variables.php'); include_once($config['library_path'] . '/boost.php'); include_once($config['library_path'] . '/xml.php'); include($config['include_path'] . '/global_arrays.php'); /* prevent command injection * This function prepares an rrdtool graph statement to be executed by the web server. * We have to take care, that the attacker does not insert shell code. * As some rrdtool parameters accept "Cacti variables", we have to perform the * variable substitution prior to vulnerability checks. * We will enclose all parameters in quotes and substitute quotation marks within * those parameters. */ /* before we do anything; make sure the user has permission to view this graph, if not then get out */ if ($user > 0) { if (!is_graph_allowed($local_graph_id, $user)) { return 'GRAPH ACCESS DENIED'; } } if (getenv('LANG') == '') { putenv('LANG=' . str_replace('-', '_', CACTI_LOCALE) . '.UTF-8'); } /* check the purge the boost poller output cache, and check for a live image file if caching is enabled */ $graph_data = boost_graph_cache_check($local_graph_id, $rra_id, $rrdtool_pipe, $graph_data_array, false); if ($graph_data !== false) { return $graph_data; } if (empty($graph_data_array['graph_start'])) { $graph_data_array['graph_start'] = -86400; } if (empty($graph_data_array['graph_end'])) { $graph_data_array['graph_end'] = -300; } $local_data_ids = array_rekey( db_fetch_assoc_prepared('SELECT dtr.local_data_id FROM graph_templates_item AS gti INNER JOIN data_template_rrd AS dtr ON gti.task_item_id = dtr.id WHERE dtr.local_data_id > 0 AND gti.local_graph_id = ?', array($local_graph_id)), 'local_data_id', 'local_data_id' ); $ds_step = rrdtool_function_get_resstep($local_data_ids, $graph_data_array['graph_start'], $graph_data_array['graph_end'], 'step'); /* if no rra was specified, we need to figure out which one RRDtool will choose using * "best-fit" resolution fit algorithm */ if (empty($rra_id)) { if (empty($graph_data_array['graph_start']) || empty($graph_data_array['graph_end'])) { $rra['rows'] = 600; $rra['steps'] = 1; $rra['timespan'] = 86400; } else { /* get a list of RRAs related to this graph */ $rras = get_associated_rras($local_graph_id); if (cacti_sizeof($rras)) { foreach ($rras as $unchosen_rra) { /* the timespan specified in the RRA "timespan" field may not be accurate */ $real_timespan = ($ds_step * $unchosen_rra['steps'] * $unchosen_rra['rows']); /* make sure the current start/end times fit within each RRA's timespan */ if ($graph_data_array['graph_end'] - $graph_data_array['graph_start'] <= $real_timespan && time() - $graph_data_array['graph_start'] <= $real_timespan) { /* is this RRA better than the already chosen one? */ if (isset($rra) && $unchosen_rra['steps'] < $rra['steps']) { $rra = $unchosen_rra; } elseif (!isset($rra)) { $rra = $unchosen_rra; } } } } if (!isset($rra)) { $rra['rows'] = 600; $rra['steps'] = 1; $rra['timespan'] = 86400; } } } else { $rra = db_fetch_row_prepared('SELECT dspr.rows, dsp.step, dspr.steps FROM data_source_profiles_rra AS dspr INNER JOIN data_source_profiles AS dsp ON dspr.data_source_profile_id=dsp.id WHERE dspr.id = ?', array($rra_id) ); if (isset($rra['steps'])) { $rra['timespan'] = $rra['rows'] * $rra['step'] * $rra['steps']; } else { $rra['timespan'] = 86400; $rra['steps'] = 1; $rra['rows'] = 600; } } if (!isset($graph_data_array['export_realtime']) && isset($rra['steps'])) { $rra_seconds = ($ds_step * $rra['steps']); } else { $rra_seconds = 5; } $graph = db_fetch_row_prepared('SELECT gl.id AS local_graph_id, gl.host_id, gl.snmp_query_id, gl.snmp_index, gtg.title_cache, gtg.vertical_label, gtg.slope_mode, gtg.auto_scale, gtg.auto_scale_opts, gtg.auto_scale_log, gtg.scale_log_units, gtg.auto_scale_rigid, gtg.auto_padding, gtg.base_value, gtg.upper_limit, gtg.lower_limit, gtg.height, gtg.width, gtg.image_format_id, gtg.unit_value, gtg.unit_exponent_value, gtg.alt_y_grid, gtg.right_axis, gtg.right_axis_label, gtg.right_axis_format, gtg.no_gridfit, gtg.unit_length, gtg.tab_width, gtg.dynamic_labels, gtg.force_rules_legend, gtg.legend_position, gtg.legend_direction, gtg.right_axis_formatter, gtg.left_axis_formatter FROM graph_templates_graph AS gtg INNER JOIN graph_local AS gl ON gl.id=gtg.local_graph_id WHERE gtg.local_graph_id = ?', array($local_graph_id) ); /* handle the case where the graph has been deleted */ if (!cacti_sizeof($graph)) { return false; } /* lets make that sql query... */ $graph_items = db_fetch_assoc_prepared('SELECT gti.id AS graph_templates_item_id, gti.cdef_id, gti.vdef_id, gti.text_format, gti.value, gti.hard_return, gti.consolidation_function_id, gti.graph_type_id, gtgp.gprint_text, colors.hex, gti.alpha, gti.line_width, gti.dashes, gti.shift, gti.dash_offset, gti.textalign, dl.snmp_query_id, dl.snmp_index, dtr.id AS data_template_rrd_id, dtr.local_data_id, dtr.rrd_minimum, dtr.rrd_maximum, dtr.data_source_name, dtr.local_data_template_rrd_id FROM graph_templates_item AS gti LEFT JOIN data_template_rrd AS dtr ON gti.task_item_id=dtr.id LEFT JOIN data_local AS dl ON dl.id = dtr.local_data_id LEFT JOIN colors ON gti.color_id=colors.id LEFT JOIN graph_templates_gprint AS gtgp ON gti.gprint_id=gtgp.id WHERE gti.local_graph_id = ? ORDER BY gti.sequence', array($local_graph_id) ); /* variables for use below */ $graph_defs = ''; $txt_graph_items = ''; $pad_number = 0; /* override: graph start time */ if (!isset($graph_data_array['graph_start']) || $graph_data_array['graph_start'] == '0') { $graph_start = -($rra['timespan']); } else { $graph_start = $graph_data_array['graph_start']; } /* override: graph end time */ if (!isset($graph_data_array['graph_end']) || $graph_data_array['graph_end'] == '0') { $graph_end = -($rra_seconds); } else { $graph_end = $graph_data_array['graph_end']; } /* +++++++++++++++++++++++ GRAPH OPTIONS +++++++++++++++++++++++ */ if (!isset($graph_data_array['export_csv'])) { $graph_opts = rrd_function_process_graph_options($graph_start, $graph_end, $graph, $graph_data_array); } else { /* basic export options */ $graph_opts = '--start=' . cacti_escapeshellarg($graph_start) . RRD_NL . '--end=' . cacti_escapeshellarg($graph_end) . RRD_NL . '--maxrows=10000' . RRD_NL; } /* +++++++++++++++++++++++ LEGEND: MAGIC +++++++++++++++++++++++ */ $realtimeCachePath = read_config_option('realtime_cache_path'); $dateTimeFormat = read_config_option('graph_dateformat'); $cactiLastDate = read_config_option('date'); if (empty($cactiLastDate)) { $cactiLastDate = date('Y-m-d H:i:s'); } $dateTime = date($dateTimeFormat, strtotime($cactiLastDate)); /* the following fields will be searched for graph variables */ $variable_fields = array( 'text_format' => array( 'process_no_legend' => false ), 'value' => array( 'process_no_legend' => true ), 'cdef_cache' => array( 'process_no_legend' => true ), 'vdef_cache' => array( 'process_no_legend' => true ) ); $i = 0; $j = 0; $nth = 0; $sum = 0; $last_graph_cf = array(); if (cacti_sizeof($graph_items)) { /* we need to add a new column 'cf_reference', so unless PHP 5 is used, this foreach syntax is required */ foreach ($graph_items as $key => $graph_item) { /* mimic the old behavior: LINE[123], AREA and STACK items use the CF specified in the graph item */ switch ($graph_item['graph_type_id']) { case GRAPH_ITEM_TYPE_LINE1: case GRAPH_ITEM_TYPE_LINE2: case GRAPH_ITEM_TYPE_LINE3: case GRAPH_ITEM_TYPE_LINESTACK: case GRAPH_ITEM_TYPE_TIC: case GRAPH_ITEM_TYPE_AREA: case GRAPH_ITEM_TYPE_STACK: $graph_cf = generate_graph_best_cf($graph_item['local_data_id'], $graph_item['consolidation_function_id'], $rra_seconds); /* remember the last CF for this data source for use with GPRINT * if e.g. an AREA/AVERAGE and a LINE/MAX is used * we will have AVERAGE first and then MAX, depending on GPRINT sequence */ $last_graph_cf['data_source_name']['local_data_template_rrd_id'] = $graph_cf; /* remember this for second foreach loop */ $graph_items[$key]['cf_reference'] = $graph_cf; break; case GRAPH_ITEM_TYPE_GPRINT: /* ATTENTION! * the 'CF' given on graph_item edit screen for GPRINT is indeed NOT a real 'CF', * but an aggregation function * see 'man rrdgraph_data' for the correct VDEF based notation * so our task now is to 'guess' the very graph_item, this GPRINT is related to * and to use that graph_item's CF */ if (isset($last_graph_cf['data_source_name']['local_data_template_rrd_id'])) { $graph_cf = $last_graph_cf['data_source_name']['local_data_template_rrd_id']; /* remember this for second foreach loop */ $graph_items[$key]['cf_reference'] = $graph_cf; } else { $graph_cf = generate_graph_best_cf($graph_item['local_data_id'], $graph_item['consolidation_function_id'], $rra_seconds); /* remember this for second foreach loop */ $graph_items[$key]['cf_reference'] = $graph_cf; } break; case GRAPH_ITEM_TYPE_GPRINT_AVERAGE: $graph_cf = $graph_item['consolidation_function_id']; $graph_items[$key]['cf_reference'] = $graph_cf; break; case GRAPH_ITEM_TYPE_GPRINT_LAST: $graph_cf = $graph_item['consolidation_function_id']; $graph_items[$key]['cf_reference'] = $graph_cf; break; case GRAPH_ITEM_TYPE_GPRINT_MAX: $graph_cf = $graph_item['consolidation_function_id']; $graph_items[$key]['cf_reference'] = $graph_cf; break; case GRAPH_ITEM_TYPE_GPRINT_MIN: $graph_cf = $graph_item['consolidation_function_id']; $graph_items[$key]['cf_reference'] = $graph_cf; break; default: /* all other types are based on the best matching CF */ $graph_cf = generate_graph_best_cf($graph_item['local_data_id'], $graph_item['consolidation_function_id'], $rra_seconds); /* remember this for second foreach loop */ $graph_items[$key]['cf_reference'] = $graph_cf; break; } if (!empty($graph_item['local_data_id']) && !isset($cf_ds_cache[$graph_item['data_template_rrd_id']][$graph_cf])) { /* use a user-specified ds path if one is entered */ if (isset($graph_data_array['export_realtime'])) { $data_source_path = $realtimeCachePath . '/user_' . hash('sha256',session_id()) . '_' . $graph_item['local_data_id'] . '.rrd'; } else { $data_source_path = get_data_source_path($graph_item['local_data_id'], true); } /* FOR WIN32: Escape all colon for drive letters (ex. D\:/path/to/rra) */ $data_source_path = rrdtool_escape_string($data_source_path); if (!empty($data_source_path)) { /* NOTE: (Update) Data source DEF names are created using the graph_item_id; then passed to a function that matches the digits with letters. rrdtool likes letters instead of numbers in DEF names; especially with CDEFs. CDEFs are created the same way, except a 'cdef' is put on the beginning of the hash */ $graph_defs .= 'DEF:' . generate_graph_def_name(strval($i)) . '=' . cacti_escapeshellarg($data_source_path) . ':' . cacti_escapeshellarg($graph_item['data_source_name'], true) . ':' . $consolidation_functions[$graph_cf] . RRD_NL; $cf_ds_cache[$graph_item['data_template_rrd_id']][$graph_cf] = "$i"; $i++; } } /* cache cdef value here to support data query variables in the cdef string */ if (empty($graph_item['cdef_id'])) { $graph_item['cdef_cache'] = ''; $graph_items[$j]['cdef_cache'] = ''; } else { $cdef = get_cdef($graph_item['cdef_id']); $graph_item['cdef_cache'] = $cdef; $graph_items[$j]['cdef_cache'] = $cdef; } /* cache vdef value here */ if (empty($graph_item['vdef_id'])) { $graph_item['vdef_cache'] = ''; $graph_items[$j]['vdef_cache'] = ''; } else { $vdef = get_vdef($graph_item['vdef_id']); $graph_item['vdef_cache'] = $vdef; $graph_items[$j]['vdef_cache'] = $vdef; } /* +++++++++++++++++++++++ LEGEND: TEXT SUBSTITUTION (<>) +++++++++++++++++++++++ */ /* note the current item_id for easy access */ $graph_item_id = $graph_item['graph_templates_item_id']; /* loop through each field that we want to substitute values for: currently: text format and value */ foreach ($variable_fields as $field_name => $field_array) { /* certain fields do not require values when the legend is not to be shown */ if ($field_array['process_no_legend'] == false && isset($graph_data_array['graph_nolegend'])) { continue; } $graph_variables[$field_name][$graph_item_id] = $graph_item[$field_name]; $search = array(); $replace = array(); /* date/time substitution */ if (strstr($graph_variables[$field_name][$graph_item_id], '|date_time|')) { $search[] = '|date_time|'; $replace[] = $dateTime; } /* data source title substitution */ if (strstr($graph_variables[$field_name][$graph_item_id], '|data_source_title|')) { $search[] = '|data_source_title|'; $replace[] = get_data_source_title($graph_item['local_data_id']); } /* data query variables */ $graph_variables[$field_name][$graph_item_id] = rrd_substitute_host_query_data($graph_variables[$field_name][$graph_item_id], $graph, $graph_item); /* Nth percentile */ if (preg_match_all('/\|([0-9]{1,2}):(bits|bytes):(\d):(current|total|max|total_peak|all_max_current|all_max_peak|aggregate_max|aggregate_sum|aggregate_current|aggregate_peak|aggregate):(\d)?\|/', $graph_variables[$field_name][$graph_item_id], $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $search[] = $match[0]; $value = variable_nth_percentile($match, $graph, $graph_item, $graph_items, $graph_start, $graph_end); $replace[] = $value; if ($field_name == 'value') { $xport_meta['NthPercentile'][$nth]['format'] = $match[0]; $xport_meta['NthPercentile'][$nth]['value'] = str_replace($match[0], $value, $graph_variables[$field_name][$graph_item_id]); $nth++; } } } /* bandwidth summation */ if (preg_match_all('/\|sum:(\d|auto):(current|total|atomic):(\d):(\d+|auto)\|/', $graph_variables[$field_name][$graph_item_id], $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $search[] = $match[0]; $value = variable_bandwidth_summation($match, $graph, $graph_item, $graph_items, $graph_start, $graph_end, $rra['steps'], $ds_step); $replace[] = $value; if ($field_name == 'text_format') { $xport_meta['Summation'][$sum]['format'] = $match[0]; $xport_meta['Summation'][$sum]['value'] = str_replace($match[0], $value, $graph_variables[$field_name][$graph_item_id]); $sum++; } } } if (cacti_count($search)) { $graph_variables[$field_name][$graph_item_id] = str_replace($search, $replace, $graph_variables[$field_name][$graph_item_id]); } } /* if we are not displaying a legend there is no point in us even processing the auto padding, text format stuff. */ if (!isset($graph_data_array['graph_nolegend'])) { /* set hard return variable if selected (\n) */ if ($graph_item['hard_return'] == 'on') { $hardreturn[$graph_item_id] = "\\n"; } else { $hardreturn[$graph_item_id] = ''; } } $j++; } } /* +++++++++++++++++++++++ LEGEND: AUTO PADDING (<>) +++++++++++++++++++++++ */ if (cacti_sizeof($graph_items)) { /* we need to add a new column 'cf_reference', so unless PHP 5 is used, this foreach * syntax is required */ foreach ($graph_items as $key => $graph_item) { /* note the current item_id for easy access */ $graph_item_id = $graph_item['graph_templates_item_id']; /* if we are not displaying a legend there is no point in us even processing the * auto padding, text format stuff. */ if (!isset($graph_data_array['graph_nolegend'])) { /* PADDING: remember this is not perfect! its main use is for the basic graph setup of: * AREA - GPRINT-CURRENT - GPRINT-AVERAGE - GPRINT-MAXIMUM \n * of course it can be used in other situations, however may not work as intended. * If you have any additions to this small piece of code, feel free to send them to me. */ if ($graph['auto_padding'] == 'on') { /* only applies to AREA, STACK and LINEs */ if (preg_match('/(AREA|STACK|LINE[123])/', $graph_item_types[$graph_item['graph_type_id']])) { $text_format_length = mb_strlen(trim($graph_variables['text_format'][$graph_item_id]), 'UTF-8'); if ($text_format_length > $pad_number) { $pad_number = $text_format_length; } } } } } } /* +++++++++++++++++++++++ LEGEND: AUTO PADDING (<>) +++++++++++++++++++++++ */ /* +++++++++++++++++++++++ GRAPH ITEMS: CDEF +++++++++++++++++++++++ */ $i = 0; /* hack for rrdtool 1.2.x support */ $graph_item_stack_type = ''; if (cacti_sizeof($graph_items)) { foreach ($graph_items as $graph_item) { /* hack around RRDtool behavior in first RRA */ $graph_cf = generate_graph_best_cf($graph_item['local_data_id'], $graph_item['consolidation_function_id'], $rra_seconds); /* first we need to check if there is a DEF for the current data source/cf combination. if so, we will use that */ if (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][$graph_cf])) { $cf_id = $graph_item['consolidation_function_id']; } else { /* if there is not a DEF defined for the current data source/cf combination, then we will have to improvise. choose the first available cf in the following order: AVERAGE, MAX, MIN, LAST */ if (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][1])) { $cf_id = 1; /* CF: AVERAGE */ } elseif (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][3])) { $cf_id = 3; /* CF: MAX */ } elseif (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][2])) { $cf_id = 2; /* CF: MIN */ } elseif (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][4])) { $cf_id = 4; /* CF: LAST */ } else { $cf_id = 1; /* CF: AVERAGE */ } } /* now remember the correct CF reference */ $cf_id = $graph_item['cf_reference']; /* +++++++++++++++++++++++ GRAPH ITEMS: CDEF START +++++++++++++++++++++++ */ /* make cdef string here; a note about CDEFs in cacti. A CDEF is neither unique to a data source of global cdef, but is unique when those two variables combine. */ $cdef_graph_defs = ''; if ((!empty($graph_item['cdef_id'])) && (!isset($cdef_cache[$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id]))) { $cdef_string = $graph_variables['cdef_cache'][$graph_item['graph_templates_item_id']]; $magic_item = array(); $already_seen = array(); $sources_seen = array(); $count_all_ds_dups = 0; $count_all_ds_nodups = 0; $count_similar_ds_dups = 0; $count_similar_ds_nodups = 0; /* if any of those magic variables are requested ... */ if (preg_match('/(ALL_DATA_SOURCES_(NO)?DUPS|SIMILAR_DATA_SOURCES_(NO)?DUPS)/', $cdef_string) || preg_match('/(COUNT_ALL_DS_(NO)?DUPS|COUNT_SIMILAR_DS_(NO)?DUPS)/', $cdef_string)) { /* now walk through each case to initialize array*/ if (preg_match('/ALL_DATA_SOURCES_DUPS/', $cdef_string)) { $magic_item['ALL_DATA_SOURCES_DUPS'] = ''; } if (preg_match('/ALL_DATA_SOURCES_NODUPS/', $cdef_string)) { $magic_item['ALL_DATA_SOURCES_NODUPS'] = ''; } if (preg_match('/SIMILAR_DATA_SOURCES_DUPS/', $cdef_string)) { $magic_item['SIMILAR_DATA_SOURCES_DUPS'] = ''; } if (preg_match('/SIMILAR_DATA_SOURCES_NODUPS/', $cdef_string)) { $magic_item['SIMILAR_DATA_SOURCES_NODUPS'] = ''; } if (preg_match('/COUNT_ALL_DS_DUPS/', $cdef_string)) { $magic_item['COUNT_ALL_DS_DUPS'] = ''; } if (preg_match('/COUNT_ALL_DS_NODUPS/', $cdef_string)) { $magic_item['COUNT_ALL_DS_NODUPS'] = ''; } if (preg_match('/COUNT_SIMILAR_DS_DUPS/', $cdef_string)) { $magic_item['COUNT_SIMILAR_DS_DUPS'] = ''; } if (preg_match('/COUNT_SIMILAR_DS_NODUPS/', $cdef_string)) { $magic_item['COUNT_SIMILAR_DS_NODUPS'] = ''; } /* loop over all graph items */ foreach($graph_items as $gi_check) { /* only work on graph items, omit GRPINTs, COMMENTs and stuff */ if ((preg_match('/(AREA|STACK|LINE[123])/', $graph_item_types[$gi_check['graph_type_id']])) && (!empty($gi_check['data_template_rrd_id']))) { /* if the user screws up CF settings, PHP will generate warnings if left unchecked */ /* matching consolidation function? */ if (isset($cf_ds_cache[$gi_check['data_template_rrd_id']][$cf_id])) { $def_name = generate_graph_def_name(strval($cf_ds_cache[$gi_check['data_template_rrd_id']][$cf_id])); /* do we need ALL_DATA_SOURCES_DUPS? */ if (isset($magic_item['ALL_DATA_SOURCES_DUPS'])) { $magic_item['ALL_DATA_SOURCES_DUPS'] .= ($count_all_ds_dups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,$def_name,$def_name,UN,0,$def_name,IF,IF"; /* convert unknowns to '0' first */ } /* do we need COUNT_ALL_DS_DUPS? */ if (isset($magic_item['COUNT_ALL_DS_DUPS'])) { $magic_item['COUNT_ALL_DS_DUPS'] .= ($count_all_ds_dups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,1,$def_name,UN,0,1,IF,IF"; /* convert unknowns to '0' first */ } $count_all_ds_dups++; /* check if this item also qualifies for NODUPS */ if (!isset($already_seen[$def_name])) { if (isset($magic_item['ALL_DATA_SOURCES_NODUPS'])) { $magic_item['ALL_DATA_SOURCES_NODUPS'] .= ($count_all_ds_nodups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,$def_name,$def_name,UN,0,$def_name,IF,IF"; /* convert unknowns to '0' first */ } if (isset($magic_item['COUNT_ALL_DS_NODUPS'])) { $magic_item['COUNT_ALL_DS_NODUPS'] .= ($count_all_ds_nodups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,1,$def_name,UN,0,1,IF,IF"; /* convert unknowns to '0' first */ } $count_all_ds_nodups++; $already_seen[$def_name] = true; } /* check for SIMILAR data sources */ if ($graph_item['data_source_name'] == $gi_check['data_source_name']) { /* do we need SIMILAR_DATA_SOURCES_DUPS? */ if (isset($magic_item['SIMILAR_DATA_SOURCES_DUPS']) && ($graph_item['data_source_name'] == $gi_check['data_source_name'])) { $magic_item['SIMILAR_DATA_SOURCES_DUPS'] .= ($count_similar_ds_dups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,$def_name,$def_name,UN,0,$def_name,IF,IF"; /* convert unknowns to '0' first */ } /* do we need COUNT_SIMILAR_DS_DUPS? */ if (isset($magic_item['COUNT_SIMILAR_DS_DUPS']) && ($graph_item['data_source_name'] == $gi_check['data_source_name'])) { $magic_item['COUNT_SIMILAR_DS_DUPS'] .= ($count_similar_ds_dups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,1,$def_name,UN,0,1,IF,IF"; /* convert unknowns to '0' first */ } $count_similar_ds_dups++; /* check if this item also qualifies for NODUPS */ if (!isset($sources_seen[$gi_check['data_template_rrd_id']])) { if (isset($magic_item['SIMILAR_DATA_SOURCES_NODUPS'])) { $magic_item['SIMILAR_DATA_SOURCES_NODUPS'] .= ($count_similar_ds_nodups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,$def_name,$def_name,UN,0,$def_name,IF,IF"; /* convert unknowns to '0' first */ } if (isset($magic_item['COUNT_SIMILAR_DS_NODUPS']) && ($graph_item['data_source_name'] == $gi_check['data_source_name'])) { $magic_item['COUNT_SIMILAR_DS_NODUPS'] .= ($count_similar_ds_nodups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,1,$def_name,UN,0,1,IF,IF"; /* convert unknowns to '0' first */ } $count_similar_ds_nodups++; $sources_seen[$gi_check['data_template_rrd_id']] = true; } } # SIMILAR data sources } # matching consolidation function? } # only work on graph items, omit GRPINTs, COMMENTs and stuff } # loop over all graph items /* if there is only one item to total, don't even bother with the summation. * Otherwise cdef=a,b,c,+,+ is fine. */ if ($count_all_ds_dups > 1 && isset($magic_item['ALL_DATA_SOURCES_DUPS'])) { $magic_item['ALL_DATA_SOURCES_DUPS'] .= str_repeat(',+', ($count_all_ds_dups - 2)) . ',+'; } if ($count_all_ds_nodups > 1 && isset($magic_item['ALL_DATA_SOURCES_NODUPS'])) { $magic_item['ALL_DATA_SOURCES_NODUPS'] .= str_repeat(',+', ($count_all_ds_nodups - 2)) . ',+'; } if ($count_similar_ds_dups > 1 && isset($magic_item['SIMILAR_DATA_SOURCES_DUPS'])) { $magic_item['SIMILAR_DATA_SOURCES_DUPS'] .= str_repeat(',+', ($count_similar_ds_dups - 2)) . ',+'; } if ($count_similar_ds_nodups > 1 && isset($magic_item['SIMILAR_DATA_SOURCES_NODUPS'])) { $magic_item['SIMILAR_DATA_SOURCES_NODUPS'] .= str_repeat(',+', ($count_similar_ds_nodups - 2)) . ',+'; } if ($count_all_ds_dups > 1 && isset($magic_item['COUNT_ALL_DS_DUPS'])) { $magic_item['COUNT_ALL_DS_DUPS'] .= str_repeat(',+', ($count_all_ds_dups - 2)) . ',+'; } if ($count_all_ds_nodups > 1 && isset($magic_item['COUNT_ALL_DS_NODUPS'])) { $magic_item['COUNT_ALL_DS_NODUPS'] .= str_repeat(',+', ($count_all_ds_nodups - 2)) . ',+'; } if ($count_similar_ds_dups > 1 && isset($magic_item['COUNT_SIMILAR_DS_DUPS'])) { $magic_item['COUNT_SIMILAR_DS_DUPS'] .= str_repeat(',+', ($count_similar_ds_dups - 2)) . ',+'; } if ($count_similar_ds_nodups > 1 && isset($magic_item['COUNT_SIMILAR_DS_NODUPS'])) { $magic_item['COUNT_SIMILAR_DS_NODUPS'] .= str_repeat(',+', ($count_similar_ds_nodups - 2)) . ',+'; } } /* allow automatic rate calculations on raw gauge data */ if (isset($graph_item['local_data_id'])) { $cdef_string = str_replace('CURRENT_DATA_SOURCE_PI', db_fetch_cell_prepared('SELECT rrd_step FROM data_template_data WHERE local_data_id = ?', array($graph_item['local_data_id'])), $cdef_string); } else { $cdef_string = str_replace('CURRENT_DATA_SOURCE_PI', read_config_option('poller_interval'), $cdef_string); } $cdef_string = str_replace('CURRENT_DATA_SOURCE', generate_graph_def_name(strval((isset($cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id]) ? $cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id] : '0'))), $cdef_string); /* allow automatic rate calculations on raw gauge data */ if (isset($graph_item['local_data_id'])) { $cdef_string = str_replace('ALL_DATA_SOURCES_DUPS_PI', db_fetch_cell_prepared('SELECT rrd_step FROM data_template_data WHERE local_data_id = ?', array($graph_item['local_data_id'])), $cdef_string); } else { $cdef_string = str_replace('ALL_DATA_SOURCES_DUPS_PI', read_config_option('poller_interval'), $cdef_string); } /* ALL|SIMILAR_DATA_SOURCES(NO)?DUPS are to be replaced here */ if (isset($magic_item['ALL_DATA_SOURCES_DUPS'])) { $cdef_string = str_replace('ALL_DATA_SOURCES_DUPS', $magic_item['ALL_DATA_SOURCES_DUPS'], $cdef_string); } /* allow automatic rate calculations on raw gauge data */ if (isset($graph_item['local_data_id'])) { $cdef_string = str_replace('ALL_DATA_SOURCES_NODUPS_PI', db_fetch_cell_prepared('SELECT rrd_step FROM data_template_data WHERE local_data_id = ?', array($graph_item['local_data_id'])), $cdef_string); } else { $cdef_string = str_replace('ALL_DATA_SOURCES_NODUPS_PI', read_config_option('poller_interval'), $cdef_string); } if (isset($magic_item['ALL_DATA_SOURCES_NODUPS'])) { $cdef_string = str_replace('ALL_DATA_SOURCES_NODUPS', $magic_item['ALL_DATA_SOURCES_NODUPS'], $cdef_string); } /* allow automatic rate calculations on raw gauge data */ if (isset($graph_item['local_data_id'])) { $cdef_string = str_replace('SIMILAR_DATA_SOURCES_DUPS_PI', db_fetch_cell_prepared('SELECT rrd_step FROM data_template_data WHERE local_data_id = ?', array($graph_item['local_data_id'])), $cdef_string); } else { $cdef_string = str_replace('SIMILAR_DATA_SOURCES_DUPS_PI', read_config_option('poller_interval'), $cdef_string); } if (isset($magic_item['SIMILAR_DATA_SOURCES_DUPS'])) { $cdef_string = str_replace('SIMILAR_DATA_SOURCES_DUPS', $magic_item['SIMILAR_DATA_SOURCES_DUPS'], $cdef_string); } if (isset($graph_item['local_data_id'])) { $cdef_string = str_replace('SIMILAR_DATA_SOURCES_NODUPS_PI', db_fetch_cell_prepared('SELECT rrd_step FROM data_template_data WHERE local_data_id = ?', array($graph_item['local_data_id'])), $cdef_string); } else { $cdef_string = str_replace('SIMILAR_DATA_SOURCES_NODUPS_PI', read_config_option('poller_interval'), $cdef_string); } if (isset($magic_item['SIMILAR_DATA_SOURCES_NODUPS'])) { $cdef_string = str_replace('SIMILAR_DATA_SOURCES_NODUPS', $magic_item['SIMILAR_DATA_SOURCES_NODUPS'], $cdef_string); } /* COUNT_ALL|SIMILAR_DATA_SOURCES(NO)?DUPS are to be replaced here */ if (isset($magic_item['COUNT_ALL_DS_DUPS'])) { $cdef_string = str_replace('COUNT_ALL_DS_DUPS', $magic_item['COUNT_ALL_DS_DUPS'], $cdef_string); } if (isset($magic_item['COUNT_ALL_DS_NODUPS'])) { $cdef_string = str_replace('COUNT_ALL_DS_NODUPS', $magic_item['COUNT_ALL_DS_NODUPS'], $cdef_string); } if (isset($magic_item['COUNT_SIMILAR_DS_DUPS'])) { $cdef_string = str_replace('COUNT_SIMILAR_DS_DUPS', $magic_item['COUNT_SIMILAR_DS_DUPS'], $cdef_string); } if (isset($magic_item['COUNT_SIMILAR_DS_NODUPS'])) { $cdef_string = str_replace('COUNT_SIMILAR_DS_NODUPS', $magic_item['COUNT_SIMILAR_DS_NODUPS'], $cdef_string); } /* data source item variables */ $cdef_string = str_replace('CURRENT_DS_MINIMUM_VALUE', (empty($graph_item['rrd_minimum']) ? '0' : $graph_item['rrd_minimum']), $cdef_string); $cdef_string = str_replace('CURRENT_DS_MAXIMUM_VALUE', (empty($graph_item['rrd_maximum']) ? '0' : $graph_item['rrd_maximum']), $cdef_string); $cdef_string = str_replace('CURRENT_GRAPH_MINIMUM_VALUE', (empty($graph['lower_limit']) ? '0' : $graph['lower_limit']), $cdef_string); $cdef_string = str_replace('CURRENT_GRAPH_MAXIMUM_VALUE', (empty($graph['upper_limit']) ? '0' : $graph['upper_limit']), $cdef_string); if ((strpos($cdef_string, '|query_ifHighSpeed|') !== false) || (strpos($cdef_string, '|query_ifSpeed|') !== false)) { $local_data = db_fetch_row_prepared('SELECT * FROM data_local WHERE id = ?', array($graph_item['local_data_id'])); $speed = rrdtool_function_interface_speed($local_data); $cdef_string = str_replace(array('|query_ifHighSpeed|','|query_ifSpeed|'), array($speed, $speed), $cdef_string); } /* replace query variables in cdefs */ $cdef_string = rrd_substitute_host_query_data($cdef_string, $graph, $graph_item); /* make the initial 'virtual' cdef name: 'cdef' + [a,b,c,d...] */ $cdef_graph_defs .= 'CDEF:cdef' . generate_graph_def_name(strval($i)) . '='; /* prohibit command injection and provide platform specific quoting */ $cdef_graph_defs .= cacti_escapeshellarg(sanitize_cdef($cdef_string), true); $cdef_graph_defs .= " \\\n"; /* the CDEF cache is so we do not create duplicate CDEF's on a graph */ $cdef_cache[$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id] = $i; } /* add the cdef string to the end of the def string */ $graph_defs .= $cdef_graph_defs; /* +++++++++++++++++++++++ GRAPH ITEMS: CDEFs END +++++++++++++++++++++++ */ /* +++++++++++++++++++++++ GRAPH ITEMS: VDEFs START +++++++++++++++++++++++ */ /* make vdef string here, copied from cdef stuff */ $vdef_graph_defs = ''; if ((!empty($graph_item['vdef_id'])) && (!isset($vdef_cache[$graph_item['vdef_id']][$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id]))) { $vdef_string = $graph_variables['vdef_cache'][$graph_item['graph_templates_item_id']]; /* do we refer to a CDEF within this VDEF? */ if ($graph_item['cdef_id'] != '0') { /* 'calculated' VDEF: use (cached) CDEF as base, only way to get calculations into VDEFs */ $vdef_string = 'cdef' . str_replace('CURRENT_DATA_SOURCE', generate_graph_def_name(strval(isset($cdef_cache[$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id]) ? $cdef_cache[$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id] : '0')), $vdef_string); } else { /* 'pure' VDEF: use DEF as base */ $vdef_string = str_replace('CURRENT_DATA_SOURCE', generate_graph_def_name(strval(isset($cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id]) ? $cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id] : '0')), $vdef_string); } # TODO: It would be possible to refer to a CDEF, but that's all. So ALL_DATA_SOURCES_NODUPS and stuff can't be used directly! # $vdef_string = str_replace('ALL_DATA_SOURCES_NODUPS', $magic_item['ALL_DATA_SOURCES_NODUPS'], $vdef_string); # $vdef_string = str_replace('ALL_DATA_SOURCES_DUPS', $magic_item['ALL_DATA_SOURCES_DUPS'], $vdef_string); # $vdef_string = str_replace('SIMILAR_DATA_SOURCES_NODUPS', $magic_item['SIMILAR_DATA_SOURCES_NODUPS'], $vdef_string); # $vdef_string = str_replace('SIMILAR_DATA_SOURCES_DUPS', $magic_item['SIMILAR_DATA_SOURCES_DUPS'], $vdef_string); /* make the initial 'virtual' vdef name */ $vdef_graph_defs .= 'VDEF:vdef' . generate_graph_def_name(strval($i)) . '='; $vdef_graph_defs .= cacti_escapeshellarg(sanitize_cdef($vdef_string)); $vdef_graph_defs .= " \\\n"; /* the VDEF cache is so we do not create duplicate VDEFs on a graph, * but take info account, that same VDEF may use different CDEFs * so index over VDEF_ID, CDEF_ID per DATA_TEMPLATE_RRD_ID, lvm */ $vdef_cache[$graph_item['vdef_id']][$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id] = $i; } /* add the cdef string to the end of the def string */ $graph_defs .= $vdef_graph_defs; /* +++++++++++++++++++++++ GRAPH ITEMS: VDEFs END +++++++++++++++++++++++ */ /* note the current item_id for easy access */ $graph_item_id = $graph_item['graph_templates_item_id']; /* we put this in a variable so it can be manipulated before mainly used if we want to skip it, like below */ $current_graph_item_type = $graph_item_types[$graph_item['graph_type_id']]; /* IF this graph item has a data source... get a DEF name for it, or the cdef if that applies to this graph item */ if ($graph_item['cdef_id'] == '0') { if (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id])) { $data_source_name = generate_graph_def_name(strval($cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id])); } else { $data_source_name = ''; } } else { $data_source_name = 'cdef' . generate_graph_def_name(strval($cdef_cache[$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id])); } /* IF this graph item has a data source... get a DEF name for it, or the vdef if that applies to this graph item */ if ($graph_item['vdef_id'] == '0') { /* do not overwrite $data_source_name that stems from cdef above */ } else { $data_source_name = 'vdef' . generate_graph_def_name(strval($vdef_cache[$graph_item['vdef_id']][$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id])); } /* to make things easier... if there is no text format set; set blank text */ if (!isset($graph_variables['text_format'][$graph_item_id])) { $graph_variables['text_format'][$graph_item_id] = ''; } if (!isset($hardreturn[$graph_item_id])) { $hardreturn[$graph_item_id] = ''; } /* +++++++++++++++++++++++ GRAPH ITEMS +++++++++++++++++++++++ */ /* most of the calculations have been done above. now we have for print everything out in an RRDtool-friendly fashion */ $need_rrd_nl = true; /* initialize color support */ $graph_item_color_code = ''; if (!empty($graph_item['hex'])) { $graph_item_color_code = '#' . $graph_item['hex']; $graph_item_color_code .= $graph_item['alpha']; } /* initialize dash support */ $dash = ''; if ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE1 || $graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE2 || $graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE3 || $graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINESTACK || $graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_HRULE || $graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_VRULE) { if (!empty($graph_item['dashes'])) { $dash .= ':dashes=' . $graph_item['dashes']; } if (!empty($graph_item['dash_offset'])) { $dash .= ':dash-offset=' . $graph_item['dash_offset']; } } if (!isset($graph_data_array['export_csv'])) { switch($graph_item['graph_type_id']) { case GRAPH_ITEM_TYPE_COMMENT: if (!isset($graph_data_array['graph_nolegend'])) { $comments = array(); $comment_arg = rrd_substitute_host_query_data($graph_variables['text_format'][$graph_item_id], $graph, $graph_item); // Check for a wrapping comment $max = read_config_option('max_title_length') - 20; if (strlen($comment_arg) > $max) { $comments = explode("\n", wordwrap($comment_arg, $max)); } else { $comments[] = $comment_arg; } foreach($comments as $comment) { # next, compute the argument of the COMMENT statement and perform injection counter measures if (trim($comment) == '') { # an empty COMMENT must be treated with care $comment = cacti_escapeshellarg(' ' . $hardreturn[$graph_item_id]); } else { $comment = cacti_escapeshellarg(rrdtool_escape_string(html_escape($comment)) . $hardreturn[$graph_item_id]); } # create rrdtool specific command line $txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $comment . ' '; } } break; case GRAPH_ITEM_TYPE_TEXTALIGN: if (!isset($graph_data_array['graph_nolegend'])) { if (!empty($graph_item['textalign'])) { $txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $graph_item['textalign']; } } break; case GRAPH_ITEM_TYPE_GPRINT: $text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id]), false); if ($graph_item['vdef_id'] == '0') { $txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $data_source_name . ':' . $consolidation_functions[$graph_item['consolidation_function_id']] . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' '; } else { $txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $data_source_name . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' '; } break; case GRAPH_ITEM_TYPE_GPRINT_AVERAGE: if (!isset($graph_data_array['graph_nolegend'])) { $text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id])); if ($graph_item['vdef_id'] == '0') { $txt_graph_items .= 'GPRINT:' . $data_source_name . ':AVERAGE:' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' '; } else { $txt_graph_items .= 'GPRINT:' . $data_source_name . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' '; } } break; case GRAPH_ITEM_TYPE_GPRINT_LAST: if (!isset($graph_data_array['graph_nolegend'])) { $text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id])); if ($graph_item['vdef_id'] == '0') { $txt_graph_items .= 'GPRINT:' . $data_source_name . ':LAST:' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' '; } else { $txt_graph_items .= 'GPRINT:' . $data_source_name . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' '; } } break; case GRAPH_ITEM_TYPE_GPRINT_MAX: if (!isset($graph_data_array['graph_nolegend'])) { $text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id])); if ($graph_item['vdef_id'] == '0') { $txt_graph_items .= 'GPRINT:' . $data_source_name . ':MAX:' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' '; } else { $txt_graph_items .= 'GPRINT:' . $data_source_name . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' '; } } break; case GRAPH_ITEM_TYPE_GPRINT_MIN: if (!isset($graph_data_array['graph_nolegend'])) { $text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id])); if ($graph_item['vdef_id'] == '0') { $txt_graph_items .= 'GPRINT:' . $data_source_name . ':MIN:' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' '; } else { $txt_graph_items .= 'GPRINT:' . $data_source_name . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' '; } } break; case GRAPH_ITEM_TYPE_AREA: $text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id] != '' ? str_pad($graph_variables['text_format'][$graph_item_id], $pad_number):'')); if (read_config_option('enable_rrdtool_gradient_support') == 'on') { /* End color is a 40% (0.4) darkened (negative number) version of the original color */ $end_color = colourBrightness('#' . $graph_item['hex'], -0.4); $txt_graph_items .= gradient($data_source_name, $graph_item_color_code, $end_color . $graph_item['alpha'], $text_format, 20, false, $graph_item['alpha']); } else { $txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $data_source_name . $graph_item_color_code . ':' . cacti_escapeshellarg($text_format . $hardreturn[$graph_item_id]) . ' '; } if ($graph_item['shift'] == CHECKED && abs($graph_item['value']) > 0) { /* create a SHIFT statement */ $txt_graph_items .= RRD_NL . 'SHIFT:' . $data_source_name . ':' . $graph_item['value']; } break; case GRAPH_ITEM_TYPE_STACK: $text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id] != '' ? str_pad($graph_variables['text_format'][$graph_item_id], $pad_number):'')); $txt_graph_items .= 'AREA:' . $data_source_name . $graph_item_color_code . ':' . cacti_escapeshellarg($text_format . $hardreturn[$graph_item_id]) . ':STACK'; if ($graph_item['shift'] == CHECKED && $graph_item['value'] > 0) { # create a SHIFT statement $txt_graph_items .= RRD_NL . 'SHIFT:' . $data_source_name . ':' . $graph_item['value']; } break; case GRAPH_ITEM_TYPE_LINE1: case GRAPH_ITEM_TYPE_LINE2: case GRAPH_ITEM_TYPE_LINE3: $text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id] != '' ? str_pad($graph_variables['text_format'][$graph_item_id], $pad_number):'')); $txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $data_source_name . $graph_item_color_code . ':' . cacti_escapeshellarg($text_format . $hardreturn[$graph_item_id]) . $dash; if ($graph_item['shift'] == CHECKED && $graph_item['value'] > 0) { # create a SHIFT statement $txt_graph_items .= RRD_NL . 'SHIFT:' . $data_source_name . ':' . $graph_item['value']; } break; case GRAPH_ITEM_TYPE_LINESTACK: $text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id] != '' ? str_pad($graph_variables['text_format'][$graph_item_id], $pad_number):'')); $txt_graph_items .= 'LINE' . $graph_item['line_width'] . ':' . $data_source_name . $graph_item_color_code . ':' . cacti_escapeshellarg($text_format . $hardreturn[$graph_item_id]) . ':STACK' . $dash; if ($graph_item['shift'] == CHECKED && $graph_item['value'] > 0) { # create a SHIFT statement $txt_graph_items .= RRD_NL . 'SHIFT:' . $data_source_name . ':' . $graph_item['value']; } break; case GRAPH_ITEM_TYPE_TIC: $_fraction = (empty($graph_item['graph_type_id']) ? '' : (':' . $graph_item['value'])); $_legend = ':' . cacti_escapeshellarg(rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id])) . $hardreturn[$graph_item_id]); $txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $data_source_name . $graph_item_color_code . $_fraction . $_legend; break; case GRAPH_ITEM_TYPE_HRULE: /* perform variable substitution; if this does not return a number, rrdtool will FAIL! */ $substitute = strip_alpha(rrd_substitute_host_query_data($graph_variables['value'][$graph_item_id], $graph, $graph_item)); $text_format = rrdtool_escape_string(html_escape(rrd_substitute_host_query_data($graph_variables['text_format'][$graph_item_id], $graph, $graph_item))); /* don't break rrdtool if the strip_alpha() returns false */ if ($substitute !== false) { $graph_variables['value'][$graph_item_id] = $substitute; } else { $graph_variables['value'][$graph_item_id] = '0'; } $txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $graph_variables['value'][$graph_item_id] . $graph_item_color_code . ':' . cacti_escapeshellarg($text_format . $hardreturn[$graph_item_id]) . '' . $dash; break; case GRAPH_ITEM_TYPE_VRULE: if (substr_count($graph_item['value'], ':')) { $value_array = explode(':', $graph_item['value']); if ($value_array[0] < 0) { $value = date('U') - (-3600 * $value_array[0]) - 60 * $value_array[1]; } else { $value = date('U', mktime($value_array[0],$value_array[1],0)); } $txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $value . $graph_item_color_code . ':' . cacti_escapeshellarg(rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id])) . $hardreturn[$graph_item_id]) . $dash; } elseif (is_numeric($graph_item['value'])) { $value = $graph_item['value']; $txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $value . $graph_item_color_code . ':' . cacti_escapeshellarg(rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id])) . $hardreturn[$graph_item_id]) . $dash; } break; default: $need_rrd_nl = false; } } else { if (preg_match('/^(AREA|AREA:STACK|LINE[123]|STACK)$/', $graph_item_types[$graph_item['graph_type_id']])) { /* give all export items a name */ if (trim($graph_variables['text_format'][$graph_item_id]) == '') { $legend_name = 'col' . $j . '-' . $data_source_name; } else { $legend_name = $graph_variables['text_format'][$graph_item_id]; } $stacked_columns['col' . $j] = ($graph_item_types[$graph_item['graph_type_id']] == 'STACK') ? 1 : 0; $j++; $txt_graph_items .= 'XPORT:' . cacti_escapeshellarg($data_source_name) . ':' . str_replace(':', '', cacti_escapeshellarg($legend_name)) ; } else { $need_rrd_nl = false; } } $i++; if (($i < cacti_sizeof($graph_items)) && ($need_rrd_nl)) { $txt_graph_items .= RRD_NL; } } } if (!isset($graph_data_array['export_csv']) || $graph_data_array['export_csv'] != true) { $graph_array = api_plugin_hook_function('rrd_graph_graph_options', array('graph_opts' => $graph_opts, 'graph_defs' => $graph_defs, 'txt_graph_items' => $txt_graph_items, 'graph_id' => $local_graph_id, 'start' => $graph_start, 'end' => $graph_end)); $graph_array = add_business_hours($graph_array); if (!empty($graph_array)) { $graph_defs = $graph_array['graph_defs']; $txt_graph_items = $graph_array['txt_graph_items']; $graph_opts = $graph_array['graph_opts']; } /* either print out the source or pass the source onto rrdtool to get us a nice PNG */ if (isset($graph_data_array['print_source'])) { $source_command_line = read_config_option('path_rrdtool') . ' graph ' . $graph_opts . $graph_defs . $txt_graph_items; $source_command_line_lengths = strlen(str_replace("\\\n", ' ', $source_command_line)); print '
' . html_escape($source_command_line) . ''; print '' . 'RRDtool Command lengths = ' . $source_command_line_lengths . ' characters.
' . 'Warning: The Cacti OS is Windows system, RRDtool Command lengths should not exceed 8191 characters.' . ''; } } else { if (isset($graph_data_array['graphv'])) { $graph = 'graphv'; } else { $graph = 'graph'; } if (isset($graph_data_array['get_error'])) { return rrdtool_execute("graph $graph_opts$graph_defs$txt_graph_items", false, RRDTOOL_OUTPUT_STDERR); } elseif (isset($graph_data_array['export'])) { rrdtool_execute("graph $graph_opts$graph_defs$txt_graph_items", false, RRDTOOL_OUTPUT_NULL, $rrdtool_pipe); return 0; } elseif (isset($graph_data_array['export_realtime'])) { $output_flag = RRDTOOL_OUTPUT_GRAPH_DATA; $output = rrdtool_execute("graph $graph_opts$graph_defs$txt_graph_items", false, $output_flag, $rrdtool_pipe); if ($fp = fopen($graph_data_array['export_realtime'], 'w')) { fwrite($fp, $output, strlen($output)); fclose($fp); chmod($graph_data_array['export_realtime'], 0644); } return $output; } else { $graph_data_array = boost_prep_graph_array($graph_data_array); if (!isset($graph_data_array['output_flag'])) { $output_flag = RRDTOOL_OUTPUT_GRAPH_DATA; } else { $output_flag = $graph_data_array['output_flag']; } $output = rrdtool_execute("$graph $graph_opts$graph_defs$txt_graph_items", false, $output_flag, $rrdtool_pipe); boost_graph_set_file($output, $local_graph_id, $rra_id); return $output; } } } else { $output_flag = RRDTOOL_OUTPUT_STDOUT; $xport_array = rrdxport2array(rrdtool_execute("xport $graph_opts$graph_defs$txt_graph_items", false, $output_flag, $rrdtool_pipe)); /* add host and graph information */ $xport_array['meta']['stacked_columns']= $stacked_columns; $xport_array['meta']['title_cache'] = $graph['title_cache']; $xport_array['meta']['vertical_label'] = $graph['vertical_label']; $xport_array['meta']['local_graph_id'] = $local_graph_id; $xport_array['meta']['host_id'] = $graph['host_id']; return $xport_array; } } function rrdtool_escape_string($text, $ignore_percent = true) { if ($ignore_percent) { return str_replace(array('"', ':'), array('\"', '\:'), $text); } else { return str_replace(array('"', ':', '%'), array('\"', '\:', '%%'), $text); } } function rrdtool_function_xport($local_graph_id, $rra_id, $xport_data_array, &$xport_meta, $user = 0) { return rrdtool_function_graph($local_graph_id, $rra_id, $xport_data_array, null, $xport_meta, $user); } function rrdtool_function_format_graph_date(&$graph_data_array) { global $datechar; $graph_legend = ''; /* setup date format */ $date_fmt = read_user_setting('default_date_format',read_config_option('default_date_format')); $dateCharSetting = read_user_setting('default_datechar',read_config_option('default_datechar')); $datecharacter = $datechar[$dateCharSetting]; switch ($date_fmt) { case GD_MO_D_Y: $graph_date = 'm' . $datecharacter . 'd' . $datecharacter . 'Y H:i:s'; break; case GD_MN_D_Y: $graph_date = 'M' . $datecharacter . 'd' . $datecharacter . 'Y H:i:s'; break; case GD_D_MO_Y: $graph_date = 'd' . $datecharacter . 'm' . $datecharacter . 'Y H:i:s'; break; case GD_D_MN_Y: $graph_date = 'd' . $datecharacter . 'M' . $datecharacter . 'Y H:i:s'; break; case GD_Y_MO_D: $graph_date = 'Y' . $datecharacter . 'm' . $datecharacter . 'd H:i:s'; break; case GD_Y_MN_D: $graph_date = 'Y' . $datecharacter . 'M' . $datecharacter . 'd H:i:s'; break; } /* display the timespan for zoomed graphs */ if ((isset($graph_data_array['graph_start'])) && (isset($graph_data_array['graph_end']))) { if (($graph_data_array['graph_start'] < 0) && ($graph_data_array['graph_end'] < 0)) { $graph_legend = "COMMENT:\"From " . str_replace(':', '\:', date($graph_date, time()+$graph_data_array['graph_start'])) . ' To ' . str_replace(':', '\:', date($graph_date, time()+$graph_data_array['graph_end'])) . "\\c\"" . RRD_NL . "COMMENT:\" \\n\"" . RRD_NL; } elseif (($graph_data_array['graph_start'] >= 0) && ($graph_data_array['graph_end'] >= 0)) { $graph_legend = "COMMENT:\"From " . str_replace(':', '\:', date($graph_date, $graph_data_array['graph_start'])) . ' To ' . str_replace(':', '\:', date($graph_date, $graph_data_array['graph_end'])) . "\\c\"" . RRD_NL . "COMMENT:\" \\n\"" . RRD_NL; } } return $graph_legend; } function rrdtool_function_theme_font_options(&$graph_data_array) { global $config; /* implement theme colors */ $graph_opts = ''; $themefonts = array(); $themecolors = 'rrdcolors'; $themeborder = 'rrdborder'; if (isset($graph_data_array['graph_theme'])) { $rrdtheme = $config['base_path'] . '/include/themes/' . $graph_data_array['graph_theme'] . '/rrdtheme.php'; } else { $rrdtheme = $config['base_path'] . '/include/themes/' . get_selected_theme() . '/rrdtheme.php'; } if (file_exists($rrdtheme) && is_readable($rrdtheme)) { $rrdversion = get_rrdtool_version(); include($rrdtheme); if (isset($_COOKIE['CactiColorMode']) && in_array($_COOKIE['CactiColorMode'], array('dark', 'light', 'dark-dimmed'))) { $themecolors = 'rrdcolors_' . $_COOKIE['CactiColorMode']; $themeborder = 'rrdborder_' . $_COOKIE['CactiColorMode']; if (!isset($$themecolors) || !is_array($$themecolors)) { $themecolors = 'rrdcolors'; $themeborder = 'rrdborder'; } } if (isset($$themecolors) && is_array($$themecolors)) { foreach($$themecolors as $colortag => $color) { $graph_opts .= '--color ' . strtoupper($colortag) . '#' . strtoupper($color) . RRD_NL; } } if (isset($$themeborder) && cacti_version_compare($rrdversion,'1.4','>=')) { $graph_opts .= "--border " . $$themeborder . RRD_NL; } if (isset($rrdfonts)) { $themefonts = $rrdfonts; } } /* title fonts */ $graph_opts .= rrdtool_function_set_font('title', ((!empty($graph_data_array['graph_nolegend'])) ? $graph_data_array['graph_nolegend'] : ''), $themefonts); /* axis fonts */ $graph_opts .= rrdtool_function_set_font('axis', '', $themefonts); /* legend fonts */ $graph_opts .= rrdtool_function_set_font('legend', '', $themefonts); /* unit fonts */ $graph_opts .= rrdtool_function_set_font('unit', '', $themefonts); /* watermark fonts */ if (isset($rrdversion) && cacti_version_compare($rrdversion,'1.3','>')) { $graph_opts .= rrdtool_function_set_font('watermark', '', $themefonts); } return $graph_opts; } function rrdtool_set_font($type, $no_legend = '', $themefonts = array()) { return rrdtool_function_set_font($type, $no_legend, $themefonts); } function rrdtool_function_set_font($type, $no_legend, $themefonts) { global $config; if (read_config_option('font_method') == 0) { if (read_user_setting('custom_fonts') == 'on') { $font = read_user_setting($type . '_font'); $size = read_user_setting($type . '_size'); } else { $font = read_config_option($type . '_font'); $size = read_config_option($type . '_size'); } } elseif (isset($themefonts[$type]['font']) && isset($themefonts[$type]['size'])) { $font = $themefonts[$type]['font']; $size = $themefonts[$type]['size']; } else { return; } if ($font != '') { /* verifying all possible pango font params is too complex to be tested here * so we only escape the font */ $font = cacti_escapeshellarg($font); } if ($type == 'title') { if (!empty($no_legend)) { $size = $size * .70; } elseif (($size <= 4) || !is_numeric($size)) { $size = 12; } } elseif (($size <= 4) || !is_numeric($size)) { $size = 8; } return '--font ' . strtoupper($type) . ':' . floatval($size) . ':' . $font . RRD_NL; } function rrd_substitute_host_query_data($txt_graph_item, $graph, $graph_item) { /* replace host variables in graph elements */ $host_id = 0; if (!preg_match('/(\|query_|\|host_|\|input_)/', $txt_graph_item)) { return $txt_graph_item; } if (empty($graph['host_id'])) { /* if graph has no associated host determine host_id from graph item data source */ if (isset($graph_item['local_data_id']) && !empty($graph_item['local_data_id'])) { $host_id = db_fetch_cell_prepared('SELECT host_id FROM data_local WHERE id = ?', array($graph_item['local_data_id'])); } } else { $host_id = $graph['host_id']; } $txt_graph_item = substitute_host_data($txt_graph_item, '|', '|', $host_id); /* replace query variables in graph elements */ if (strpos($txt_graph_item, '|query_') !== false) { if (isset($graph_item['snmp_query_id'])) { $txt_graph_item = substitute_snmp_query_data($txt_graph_item, $host_id, $graph_item['snmp_query_id'], $graph_item['snmp_index']); } else if (isset($graph['snmp_query_id'])) { $txt_graph_item = substitute_snmp_query_data($txt_graph_item, $host_id, $graph['snmp_query_id'], $graph['snmp_index']); } } /* replace query variables in graph elements */ if (strpos($txt_graph_item, '|input_') !== false && isset($graph_item['local_data_id'])) { return substitute_data_input_data($txt_graph_item, $graph, $graph_item['local_data_id']); } else { return $txt_graph_item; } } function rrdtool_function_get_resstep($local_data_ids, $graph_start, $graph_end, $type = 'res') { if (!is_array($local_data_ids)) { $local_data_ids = array($local_data_ids); } $time = time(); if ($graph_start < 0) { $graph_start = $time + $graph_start; } if ($graph_end < 0) { $graph_end = $time + $graph_end; } if (cacti_sizeof($local_data_ids)) { foreach($local_data_ids as $local_data_id) { $data_source_info = db_fetch_assoc_prepared('SELECT dsp.step, dspr.steps, dspr.rows, dspr.timespan FROM data_source_profiles AS dsp INNER JOIN data_source_profiles_rra AS dspr ON dspr.data_source_profile_id=dsp.id INNER JOIN data_template_data AS dtd ON dtd.data_source_profile_id=dsp.id WHERE dtd.local_data_id = ? ORDER BY step, steps ASC', array($local_data_id)); if (cacti_sizeof($data_source_info)) { foreach($data_source_info as $resolution) { if ($graph_start > ($time - ($resolution['step'] * $resolution['steps'] * $resolution['rows']))) { if ($type == 'res') { return $resolution['step'] * $resolution['steps']; } else { return $resolution['step']; } } } } } } return 0; } /** * rrdtool_function_info - given a data source id, return rrdtool info array * * @param (int) $local_data_id - data source id * * @return (array) an array containing all data from rrdtool info command */ function rrdtool_function_info($local_data_id) { /* Get the path to rrdtool file */ $data_source_path = get_data_source_path($local_data_id, true); /* Execute rrdtool info command */ $cmd_line = ' info ' . $data_source_path; $output = rrdtool_execute($cmd_line, RRDTOOL_OUTPUT_NULL, RRDTOOL_OUTPUT_STDOUT); if ($output == '') { return false; } /* Hack for i18n */ if (strpos($output, ',') !== false) { $output = str_replace(',', '.', $output); } /* Parse the output */ $matches = array(); $rrd_info = array('rra' => array(), 'ds' => array()); $output = explode("\n", $output); foreach ($output as $line) { $line = trim($line); if (preg_match('/^ds\[(\S+)\]\.(\S+) = (\S+)$/', $line, $matches)) { $rrd_info['ds'][$matches[1]][$matches[2]] = trim($matches[3], '"'); } elseif (preg_match('/^rra\[(\S+)\]\.(\S+)\[(\S+)\]\.(\S+) = (\S+)$/', $line, $matches)) { $rrd_info['rra'][$matches[1]][$matches[2]][$matches[3]][$matches[4]] = trim($matches[5], '"'); } elseif (preg_match('/^rra\[(\S+)\]\.(\S+) = (\S+)$/', $line, $matches)) { $rrd_info['rra'][$matches[1]][$matches[2]] = trim($matches[3], '"'); } elseif (preg_match("/^(\S+) = \"(\S+)\"$/", $line, $matches)) { $rrd_info[$matches[1]] = trim($matches[2], '"'); } elseif (preg_match('/^(\S+) = (\S+)$/', $line, $matches)) { $rrd_info[$matches[1]] = trim($matches[2], '"'); } } $output = ''; $matches = array(); /* Return parsed values */ return $rrd_info; } function rrdtool_function_info_from_ds($data_source_id) { global $data_source_types, $consolidation_functions; /* construct the skeleton of the info array */ $info_array['rra'] = array(); $info_array['ds'] = array(); $info_array['step'] = 0; $info_array['filename'] = clean_up_path(get_data_source_path($data_source_id, true)); /* get cacti header information for given data source id */ $cacti_header_array = db_fetch_row_prepared('SELECT local_data_template_data_id, rrd_step, data_source_profile_id FROM data_template_data WHERE local_data_id = ?', array($data_source_id)); $data_template_id = db_fetch_cell_prepared('SELECT data_template_id FROM data_local WHERE id = ?', array($data_source_id)); /* get cacti DS information */ if ($data_template_id > 0) { $cacti_ds_array = db_fetch_assoc_prepared('SELECT DISTINCT dtr.id, dtr.data_source_name, dtr.data_source_type_id, dtr.rrd_heartbeat, dtr.rrd_maximum, dtr.rrd_minimum FROM data_template_rrd AS dtr INNER JOIN graph_templates_item AS gti ON dtr.id = gti.task_item_id WHERE local_data_id = ?', array($data_source_id)); } else { $cacti_ds_array = db_fetch_assoc_prepared('SELECT DISTINCT dtr.id, dtr.data_source_name, dtr.data_source_type_id, dtr.rrd_heartbeat, dtr.rrd_maximum, dtr.rrd_minimum FROM data_template_rrd AS dtr WHERE local_data_id = ?', array($data_source_id)); } if (cacti_sizeof($cacti_header_array) && cacti_sizeof($cacti_ds_array)) { $info_array['step'] = $cacti_header_array['rrd_step']; /* get cacti RRA information */ $cacti_rra_array = db_fetch_assoc_prepared('SELECT dspc.consolidation_function_id AS cf, dsp.x_files_factor AS xff, dsp.heartbeat, dspr.steps AS steps, dspr.rows AS `rows` FROM data_source_profiles AS dsp INNER JOIN data_source_profiles_cf AS dspc ON dsp.id = dspc.data_source_profile_id INNER JOIN data_source_profiles_rra AS dspr ON dsp.id = dspr.data_source_profile_id WHERE dsp.id = ? ORDER BY dspc.consolidation_function_id, dspr.steps', array($cacti_header_array['data_source_profile_id'])); /* get the data source information */ $data_local = db_fetch_row_prepared('SELECT host_id, snmp_query_id, snmp_index FROM data_local WHERE id = ?', array($data_source_id)); /* get the speed just in case we have replacement data */ $speed = rrdtool_function_interface_speed($data_local); /* complete the 'ds' component of the info array */ foreach($cacti_ds_array as $index => $ds) { $info_array['ds'][$ds['data_source_name']]['index'] = $index; $info_array['ds'][$ds['data_source_name']]['type'] = $data_source_types[$ds['data_source_type_id']]; $info_array['ds'][$ds['data_source_name']]['min'] = $ds['rrd_minimum']; if ($ds['rrd_maximum'] == '|query_ifSpeed|' || $ds['rrd_maximum'] == '|query_ifHighSpeed|') { $info_array['ds'][$ds['data_source_name']]['max'] = $speed; } else { $info_array['ds'][$ds['data_source_name']]['max'] = $ds['rrd_maximum']; } $info_array['ds'][$ds['data_source_name']]['minimal_heartbeat'] = $ds['rrd_heartbeat']; } /* complete the 'rra' component of the info array */ if (cacti_sizeof($cacti_rra_array)) { foreach($cacti_rra_array as $index => $rra) { foreach($cacti_ds_array as $ds) { $info_array['rra'][$index]['cf'] = $consolidation_functions[$rra['cf']]; $info_array['rra'][$index]['rows'] = $rra['rows']; $info_array['rra'][$index]['xff'] = $rra['xff']; $info_array['rra'][$index]['steps'] = $rra['steps']; } } return $info_array; } else { return array(); } } else { return array(); } } /** * rrdtool_function_contains_cf verifies if the RRDfile contains the 'MAX' consolidation function * * @param (int) $local_data_id - the id of the data source * @param (int) $cf - the consolidation function to search for * * @return (bool) true or false depending on the result */ function rrdtool_function_contains_cf($local_data_id, $cf) { $info = rrdtool_function_info($local_data_id); if (cacti_sizeof($info)) { if (isset($info['rra'])) { foreach($info['rra'] as $ds) { if ($ds['cf'] == $cf) { return true; } } } } return false; } /** * rrdtool_cacti_compare - compares cacti information to rrd file information * * @param $data_source_idi the id of the data source * @param $info rrdtool info as an array * * @return array build like $info defining html class in case of error */ function rrdtool_cacti_compare($data_source_id, &$info) { global $data_source_types, $consolidation_functions; /** * This function will be passed the RRDtool info rrdfile output * formatted as an array with multiple indexes. They include: * * 'ds' => An array of data source items including name, min/max, heartbeat, and type * 'rra' => An array of consolidation functions, rows, xff and other misc data from * the RRDfile. * * The the $cacti_info array includes the same structure, but from the current database * settings, which may differ from the original rrdfile that is on disk. * * Once we have these two pieces of information, we can do a comparison and provide some * guidance as to how to correct the format of the RRDfile. Some of the possible * differences can result in what would effectively be missing data, so care must be taken * when providing these recommendations. */ $cacti_info = rrdtool_function_info_from_ds($data_source_id); /* create an empty diff array to hold the differences */ $diff = array(); if ($cacti_info['step'] != $info['step']) { $diff['step'] = __("The required RRDfile step size is '%s' but observed step is '%s'", $cacti_info['step'], $info['step']); } /* ----------------------------------------------------------------------------------- * data source information -----------------------------------------------------------------------------------*/ if (cacti_sizeof($cacti_info) && cacti_sizeof($info)) { /* get the heartbeat from the profile */ $profile_heartbeat = db_fetch_cell_prepared('SELECT heartbeat FROM data_source_profiles AS dsp INNER JOIN data_template_data AS dtd ON dsp.id = dtd.data_source_profile_id WHERE dtd.local_data_id = ?', array($data_source_id)); /* cursory checks on total data sources */ if (isset($cacti_info['ds']) && isset($info['ds'])) { if (cacti_sizeof($cacti_info['ds']) != cacti_sizeof($info['ds'])) { // Need to warn here about the total number of data sources not matchhing } foreach($cacti_info['ds'] as $data_source_name => $data_source) { /** * This should be a rare case today where the RRDfile does not * have the data source, but Cacti does. */ if (!isset($info['ds'][$data_source_name])) { /* cacti knows this ds, but the rrd file does not */ $info['ds'][$data_source_name]['type'] = $data_source['type']; $info['ds'][$data_source_name]['minimal_heartbeat'] = $data_source['minimal_heartbeat']; $info['ds'][$ds_name]['min'] = $data_source['min']; $info['ds'][$ds_name]['max'] = $data_source['max']; $info['ds'][$ds_name]['seen'] = true; continue; } /** * Accomodate a Cacti bug where the heartbeat was not * propagated. */ if ($data_source['minimal_heartbeat'] != $profile_heartbeat) { cacti_log(sprintf('NOTE: Incorrect Data Source heartbeat found and corrected for Local Data ID %s and Data Source \'%s\'', $data_source_id, $data_source_name), false, 'DSDEBUG'); db_execute_prepared('UPDATE data_template_rrd SET rrd_heartbeat = ? WHERE local_data_id = ? AND data_source_name = ?', array($profile_heartbeat, $data_source_id, $data_source_name)); $cacti_info['ds'][$data_source_name]['minimal_heartbeat'] = $profile_heartbeat; } /* verity that all data sources are seen */ if (isset($info['ds'][$data_source_name])) { $info['ds'][$data_source_name]['seen'] = true; } /* check the data source type */ if ($data_source['type'] != $info['ds'][$data_source_name]['type']) { $diff['ds'][$data_source_name]['type'] = __("Type for Data Source '%s' should be '%s'", $data_source_name, $data_source['type']); $diff['tune'][] = $info['filename'] . ' ' . '--data-source-type ' . $data_source_name . ':' . $data_source['type']; } /* check the mimimal heartbeat */ if ($data_source['minimal_heartbeat'] != $info['ds'][$data_source_name]['minimal_heartbeat']) { $diff['ds'][$data_source_name]['minimal_heartbeat'] = __("Heartbeat for Data Source '%s' should be '%s'", $data_source_name, $data_source['minimal_heartbeat']); $diff['tune'][] = $info['filename'] . ' ' . '--heartbeat ' . $data_source_name . ':' . $data_source['minimal_heartbeat']; } /* check the minimums and maximums and correct for legacy behavior */ if ($data_source['min'] != $info['ds'][$data_source_name]['min']) { if (($data_source['min'] == '0' || $data_source['max'] == 'U') && $info['ds'][$data_source_name]['min'] == 'NaN') { $info['ds'][$data_source_name]['min'] = 'U'; } } if ($data_source['min'] != $info['ds'][$data_source_name]['min']) { $diff['ds'][$data_source_name]['min'] = __("RRD minimum for Data Source '%s' should be '%s'", $data_source_name, $data_source['min']); $diff['tune'][] = $info['filename'] . ' ' . '--minimum ' . $data_source_name . ':' . $data_source['min']; } } } /* if there are data sources missing log that now */ foreach ($info['ds'] as $data_source_name => $data_source) { if (!isset($data_source['seen'])) { $diff['ds'][$data_source_name]['error'] = __("DS '%s' missing in Cacti definition", $data_source_name); } } if (isset($cacti_info['rra']) && isset($info['rra'])) { $resize = true; // assume a resize operation as long as no rra duplicates are found /* scan cacti rra information for duplicates of (CF, STEPS) */ foreach($cacti_info['rra'] as $index => $rra) { $cf = $rra['cf']; $steps = $rra['steps']; foreach($cacti_info['rra'] as $cacti_rra_id => $cacti_rra) { if ($cf == $cacti_rra['cf'] && $steps == $cacti_rra['steps'] && ($index != $cacti_rra_id)) { $diff['rra'][$index]['error'] = __("Cacti RRA '%s' has same CF/steps (%s, %s) as '%s'", $index, $cf, $steps, $cacti_rra_id); $diff['rra'][$cacti_rra_id]['error'] = __("Cacti RRA '%s' has same CF/steps (%s, %s) as '%s'", $cacti_rra_id, $cf, $steps, $index); $resize = false; } } } /* scan file rra information for duplicates of (CF, PDP_PER_ROWS) */ foreach($info['rra'] as $index => $rra) { $cf = $rra['cf']; $steps = $rra['pdp_per_row']; foreach($info['rra'] as $file_rra_id => $file_rra) { if (($cf == $file_rra['cf']) && ($steps == $file_rra['pdp_per_row']) && ($index != $file_rra_id)) { $diff['rra'][$i]['error'] = __("File RRA '%s' has same CF/steps (%s, %s) as '%s'", $index, $cf, $steps, $file_rra_id); $diff['rra'][$file_rra_id]['error'] = __("File RRA '%s' has same CF/steps (%s, %s) as '%s'", $file_rra_id, $cf, $steps, $index); $resize = false; } } } foreach($cacti_info['rra'] as $cacti_rra_id => $cacti_rra) { /** * find matching rra info from rrd file * do NOT assume, that rra sequence is kept ($cacti_rra_id != $file_rra_id may happen)! * Match is assumed, if CF and STEPS/PDP_PER_ROW match; so go for it */ foreach ($info['rra'] as $file_rra_id => $file_rra) { /* in case of mismatch, $file_rra['pdp_per_row'] might not be defined */ if (!isset($file_rra['pdp_per_row'])) { $file_rra['pdp_per_row'] = 0; } /* corrrect issue with older rrdtools */ $file_rra['cf'] = trim($file_rra['cf'], '"'); if ($cacti_rra['cf'] == $file_rra['cf'] && $cacti_rra_id == $file_rra_id) { if (isset($info['rra'][$file_rra_id]['seen'])) { continue; } /* mark both rra id's as seen to avoid printing them as non-matching */ $info['rra'][$file_rra_id]['seen'] = true; $cacti_info['rra'][$cacti_rra_id]['seen'] = true; if ($cacti_rra['steps'] != $file_rra['pdp_per_row']) { $diff['rra'][$file_rra_id]['pdp_per_row_error'] = __("The pdp_per_row of '%s' is invalid for RRA '%s' should be '%s'. Consider deleting and allowing Cacti to re-create RRDfile.", $file_rra['pdp_per_row'], $cacti_rra_id, $cacti_rra['steps']); } if ($cacti_rra['xff'] != $file_rra['xff']) { $diff['rra'][$file_rra_id]['xff'] = __("The XFF for Cacti RRA id is '%s' but should be '%s'", $cacti_rra_id, $cacti_rra['xff']); } if ($cacti_rra['rows'] != $file_rra['rows'] && $resize) { $diff['rra'][$file_rra_id]['rows'] = __("The number of rows for Cacti RRA id '%s' is incorrect. The number of rows are '%s' but should be '%s'", $cacti_rra_id, $file_rra['rows'], $cacti_rra['rows']); if ($cacti_rra['rows'] > $file_rra['rows']) { $diff['resize'][] = $info['filename'] . ' ' . $cacti_rra_id . ' GROW ' . ($cacti_rra['rows'] - $file_rra['rows']); } else { $diff['resize'][] = $info['filename'] . ' ' . $cacti_rra_id . ' SHRINK ' . ($file_rra['rows'] - $cacti_rra['rows']); } } } } } /* if cacti knows an rra that has no match, consider this as an error */ if (!isset($cacti_info['rra'][$cacti_rra_id]['seen'])) { /* add to info array for printing, the index $cacti_rra_id has no real meaning */ $info['rra']['cacti_' . $cacti_rra_id]['cf'] = $cacti_rra['cf']; $info['rra']['cacti_' . $cacti_rra_id]['steps'] = $cacti_rra['steps']; $info['rra']['cacti_' . $cacti_rra_id]['xff'] = $cacti_rra['xff']; $info['rra']['cacti_' . $cacti_rra_id]['rows'] = $cacti_rra['rows']; $diff['rra']['cacti_' . $cacti_rra_id]['error'] = __("The RRA '%s' missing in the existing Cacti RRDfile", $cacti_rra_id); } } /* if the rrd file has an rra that has no cacti match, consider this as an error */ foreach ($info['rra'] as $file_rra_id => $file_rra) { if (!isset($info['rra'][$file_rra_id]['seen'])) { $diff['rra'][$file_rra_id]['error'] = __("RRA '%s' missing in Cacti definition", $file_rra_id); } } } return $diff; } /** * rrdtool_info2html - take output from rrdtool info array and build html table * * @param (array) $info_array - array of rrdtool info data * @param (array) $diff - array of differences between definition and current rrd file settings * * @return (string) - html code */ function rrdtool_info2html($info_array, $diff=array()) { global $config; include_once($config['library_path'] . '/time.php'); html_start_box(__('RRD File Information'), '100%', '', '3', 'center', ''); # header data $header_items = array( array('display' =>__('Header'), 'align' => 'left'), array('display' => '', 'align' => 'left') ); html_header($header_items, 1); # add human readable timestamp if (isset($info_array['last_update'])) { $info_array['last_update'] .= ' [' . date(CACTI_DATE_TIME_FORMAT, $info_array['last_update']) . ']'; } $loop = array( 'filename' => $info_array['filename'], 'rrd_version' => $info_array['rrd_version'], 'step' => $info_array['step'], 'last_update' => $info_array['last_update']); foreach ($loop as $key => $value) { form_alternate_row($key, true); form_selectable_cell($key, 'key'); form_selectable_cell($value, 'value', '', ((isset($diff[$key]) ? 'color:red' : ''))); form_end_row(); } html_end_box(); # data sources $header_items = array( array('display' => __('Data Source Items'), 'align' => 'left'), array('display' => __('Type'), 'align' => 'left'), array('display' => __('Minimal Heartbeat'), 'align' => 'right'), array('display' => __('Min'), 'align' => 'right'), array('display' => __('Max'), 'align' => 'right'), array('display' => __('Last DS'), 'align' => 'right'), array('display' => __('Value'), 'align' => 'right'), array('display' => __('Unknown Sec'), 'align' => 'right') ); html_start_box('', '100%', '', '3', 'center', ''); html_header($header_items, 1); if (cacti_sizeof($info_array['ds'])) { foreach ($info_array['ds'] as $key => $value) { form_alternate_row('line' . $key, true); form_selectable_cell($key, 'name', '', (isset($diff['ds'][$key]['error']) ? 'color:red' : '')); form_selectable_cell((isset($value['type']) ? $value['type'] : ''), 'type', '', (isset($diff['ds'][$key]['type']) ? 'color:red' : '')); form_selectable_cell((isset($value['minimal_heartbeat']) ? $value['minimal_heartbeat'] : ''), 'minimal_heartbeat', '', (isset($diff['ds'][$key]['minimal_heartbeat']) ? 'color:red, text-align:right' : 'text-align:right')); if (isset($value['min'])) { if ($value['min'] == 'U') { form_selectable_cell($value['min'], 'min', '', 'right'); } elseif (is_numeric($value['min'])) { form_selectable_cell(number_format_i18n($value['min']), 'min', '', 'right'); } else { form_selectable_cell($value['min'], 'min', '', 'color:red;text-align:right'); } } else { form_selectable_cell(__('Unknown'), 'min', '', 'color:red;text-align:right'); } if (isset($value['max'])) { if ($value['max'] == 'U' || $value['max'] == 'NaN') { form_selectable_cell($value['max'], 'max', '', 'right'); } elseif (is_numeric($value['max'])) { form_selectable_cell(number_format_i18n($value['max']), 'max', '', 'right'); } else { form_selectable_cell($value['max'], 'max', '', 'color:red;text-align:right'); } } else { form_selectable_cell(__('Unknown'), 'max', '', 'color:red;text-align:right'); } form_selectable_cell((isset($value['last_ds']) && is_numeric($value['last_ds']) ? number_format_i18n($value['last_ds']) : (isset($value['last_ds']) ? $value['last_ds']:'')), 'last_ds', '', 'text-align:right'); form_selectable_cell((isset($value['value']) ? is_numeric($value['value']) ? number_format_i18n($value['value']) : $value['value'] : ''), 'value', '', 'text-align:right'); form_selectable_cell((isset($value['unknown_sec']) && is_numeric($value['unknown_sec']) ? number_format_i18n($value['unknown_sec']) : (isset($value['unknown_sec']) ? $value['unknown_sec']:'')), 'unknown_sec', '', 'text-align:right'); form_end_row(); } } html_end_box(); # round robin archive $header_items = array( array('display' => __('Round Robin Archive'), 'align' => 'left'), array('display' => __('Consolidation Function'), 'align' => 'left'), array('display' => __('Rows'), 'align' => 'right'), array('display' => __('Cur Row'), 'align' => 'right'), array('display' => __('PDP per Row'), 'align' => 'right'), array('display' => __('X-Files Factor'), 'align' => 'right'), array('display' => __('CDP Prep Value (0)'), 'align' => 'right'), array('display' => __('CDP Unknown Data points (0)'), 'align' => 'right') ); html_start_box('', '100%', '', '3', 'center', ''); html_header($header_items, 1); if (cacti_sizeof($info_array['rra'])) { foreach ($info_array['rra'] as $key => $value) { form_alternate_row('line_' . $key, true); form_selectable_cell($key, 'name', '', (isset($diff['rra'][$key]['error']) ? 'color:red' : '')); form_selectable_cell((isset($value['cf']) ? $value['cf'] : ''), 'cf'); form_selectable_cell((isset($value['rows']) ? $value['rows'] : ''), 'rows', '', (isset($diff['rra'][$key]['rows']) ? 'color:red;text-align:right' : 'text-align:right')); form_selectable_cell((isset($value['cur_row']) ? $value['cur_row'] : ''), 'cur_row', '', 'text-align:right'); form_selectable_cell((isset($value['pdp_per_row']) ? $value['pdp_per_row'] : ''), 'pdp_per_row', '', 'text-align:right'); form_selectable_cell((isset($value['xff']) ? floatval($value['xff']) : ''), 'xff', '', (isset($diff['rra'][$key]['xff']) ? 'color:red;text-align:right' : 'text-align:right')); form_selectable_cell((isset($value['cdp_prep'][0]['value']) ? (strtolower($value['cdp_prep'][0]['value']) == 'nan') ? $value['cdp_prep'][0]['value'] : floatval($value['cdp_prep'][0]['value']) : ''), 'value', '', 'text-align:right'); form_selectable_cell((isset($value['cdp_prep'][0]['unknown_datapoints'])? $value['cdp_prep'][0]['unknown_datapoints'] : ''), 'unknown_datapoints', '', 'text-align:right'); form_end_row(); } } html_end_box(); } /** * rrdtool_tune - create rrdtool tune/resize commands html+cli enabled * * @param (string) $rrd_file - rrd file name * @param (array) $diff - array of discrepancies between cacti settings and rrd file info * @param (bool) $show_source - only show text+commands or execute all commands, execute is for cli mode only! */ function rrdtool_tune($rrd_file, $diff, $show_source = true) { $rrd_path = read_config_option('path_rrdtool'); function print_leaves($array) { foreach ($array as $key => $line) { if (!is_array($line)) { if (CACTI_CLI) { print $line . PHP_EOL; } else { print '