diff --git a/data_sources.php b/data_sources.php index 9d22aa8c7c..753555eb44 100644 --- a/data_sources.php +++ b/data_sources.php @@ -713,6 +713,15 @@ function ds_edit() { } } + /* handle debug mode */ + if (isset_request_var('info')) { + if (get_nfilter_request_var('info') == '0') { + kill_session_var('ds_info_mode'); + }elseif (get_nfilter_request_var('info') == '1') { + $_SESSION['ds_info_mode'] = true; + } + } + top_header(); if (!isempty_request_var('id')) { @@ -724,6 +733,7 @@ function ds_edit() { *'>Turn Data Source Debug Mode.
+ *'>Turn Data Source Info Mode.
*'>Edit Data Template.
+ + + + +
+ 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; +} + +/** rrdtool_cacti_compare compares cacti information to rrd file information + * @param $data_source_id 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; + + /* 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)); + + $cacti_file = get_data_source_path($data_source_id, true); + + /* get cacti DS information */ + $cacti_ds_array = db_fetch_assoc_prepared("SELECT data_source_name, data_source_type_id, + rrd_heartbeat, rrd_maximum, rrd_minimum + FROM data_template_rrd + WHERE local_data_id = ?", array($data_source_id)); + + /* get cacti RRA information */ + $cacti_rra_array = db_fetch_assoc_prepared('SELECT + dspc.consolidation_function_id AS cf, + dsp.x_files_factor AS xff, + 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'])); + + $diff = array(); + /* ----------------------------------------------------------------------------------- + * header information + -----------------------------------------------------------------------------------*/ + if ($cacti_header_array['rrd_step'] != $info['step']) { + $diff['step'] = __("required rrd step size is '%s'", $cacti_header_array['rrd_step']); + } + + /* ----------------------------------------------------------------------------------- + * data source information + -----------------------------------------------------------------------------------*/ + if (sizeof($cacti_ds_array) > 0) { + foreach ($cacti_ds_array as $key => $data_source) { + $ds_name = $data_source['data_source_name']; + + /* try to print matching rrd file's ds information */ + if (isset($info['ds'][$ds_name]) ) { + if (!isset($info['ds'][$ds_name]['seen'])) { + $info['ds'][$ds_name]['seen'] = TRUE; + } else { + continue; + } + + $ds_type = trim($info['ds'][$ds_name]['type'], '"'); + if ($data_source_types[$data_source['data_source_type_id']] != $ds_type) { + $diff['ds'][$ds_name]['type'] = __("type for data source '%s' should be '%s'", $ds_name, $data_source_types[$data_source['data_source_type_id']]); + $diff['tune'][] = $info['filename'] . ' ' . '--data-source-type ' . $ds_name . ':' . $data_source_types[$data_source['data_source_type_id']]; + } + + if ($data_source['rrd_heartbeat'] != $info['ds'][$ds_name]['minimal_heartbeat']) { + $diff['ds'][$ds_name]['minimal_heartbeat'] = __("heartbeat for data source '%s' should be '%s'", $ds_name, $data_source['rrd_heartbeat']); + $diff['tune'][] = $info['filename'] . ' ' . '--heartbeat ' . $ds_name . ':' . $data_source['rrd_heartbeat']; + } + + if ($data_source['rrd_minimum'] != $info['ds'][$ds_name]['min']) { + $diff['ds'][$ds_name]['min'] = __("rrd minimum for data source '%s' should be '%s'", $ds_name, $data_source['rrd_minimum']); + $diff['tune'][] = $info['filename'] . ' ' . '--maximum ' . $ds_name . ':' . $data_source['rrd_minimum']; + } + + if ($data_source['rrd_maximum'] != $info['ds'][$ds_name]['max']) { + $data_local = db_fetch_row('SELECT * FROM data_local WHERE id=' . $data_source_id); + if ($data_source['rrd_maximum'] == '|query_ifSpeed|' || $data_source['rrd_maximum'] == '|query_ifHighSpeed|') { + $highSpeed = db_fetch_cell("SELECT field_value + FROM host_snmp_cache + WHERE host_id=" . $data_local['host_id'] . " + AND snmp_query_id=" . $data_local['snmp_query_id'] . " + AND snmp_index='" . $data_local['snmp_index'] . "' + AND field_name='ifHighSpeed'"); + + if (!empty($highSpeed)) { + $data_source['rrd_maximum'] = $highSpeed * 1000000; + }else{ + $data_source['rrd_maximum'] = substitute_snmp_query_data('|query_ifSpeed|',$data_local['host_id'], $data_local['snmp_query_id'], $data_local['snmp_index']); + } + }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']); + } + + if (empty($data_source['rrd_maximum']) || $data_source['rrd_maximum'] == '|query_ifSpeed|') { + $data_source['rrd_maximum'] = '10000000000000'; + } + } + + if ($data_source['rrd_maximum'] != $info['ds'][$ds_name]['max']) { + $diff['ds'][$ds_name]['max'] = __("rrd maximum for data source '%s' should be '%s'", $ds_name, $data_source['rrd_maximum']); + $diff['tune'][] = $info['filename'] . ' ' . '--minimum ' . $ds_name . ':' . $data_source['rrd_maximum']; + } + } else { + # cacti knows this ds, but the rrd file does not + $info['ds'][$ds_name]['type'] = $data_source_types[$data_source['data_source_type_id']]; + $info['ds'][$ds_name]['minimal_heartbeat'] = $data_source['rrd_heartbeat']; + $info['ds'][$ds_name]['min'] = $data_source['rrd_minimum']; + $info['ds'][$ds_name]['max'] = $data_source['rrd_maximum']; + $info['ds'][$ds_name]['seen'] = TRUE; + $diff['ds'][$ds_name]['error'] = __("DS '%s' missing in rrd file", $ds_name); + } + } + } + + /* print all data sources still known to the rrd file (no match to cacti ds will happen here) */ + if (sizeof($info['ds']) > 0) { + foreach ($info['ds'] as $ds_name => $data_source) { + if (!isset($data_source['seen'])) { + $diff['ds'][$ds_name]['error'] = __("DS '%s' missing in cacti definition", $ds_name); + } + } + } + + + /* ----------------------------------------------------------------------------------- + * RRA information + -----------------------------------------------------------------------------------*/ + $resize = TRUE; # assume a resize operation as long as no rra duplicates are found + # scan cacti rra information for duplicates of (CF, STEPS) + if (sizeof($cacti_rra_array) > 0) { + for ($i=0; $i<= sizeof($cacti_rra_array)-1; $i++) { + $cf = $cacti_rra_array{$i}['cf']; + $steps = $cacti_rra_array{$i}['steps']; + foreach($cacti_rra_array as $cacti_rra_id => $cacti_rra) { + if ($cf == $cacti_rra['cf'] && $steps == $cacti_rra['steps'] && ($i != $cacti_rra_id)) { + $diff['rra'][$i]['error'] = __("Cacti RRA '%s' has same cf/steps (%s, %s) as '%s'", $i, $consolidation_functions{$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, $consolidation_functions{$cf}, $steps, $i); + $resize = FALSE; + } + } + } + } + # scan file rra information for duplicates of (CF, PDP_PER_ROWS) + if (sizeof($info['rra']) > 0) { + for ($i=0; $i<= sizeof($info['rra'])-1; $i++) { + $cf = $info['rra']{$i}['cf']; + $steps = $info['rra']{$i}['pdp_per_row']; + foreach($info['rra'] as $file_rra_id => $file_rra) { + if (($cf == $file_rra['cf']) && ($steps == $file_rra['pdp_per_row']) && ($i != $file_rra_id)) { + $diff['rra'][$i]['error'] = __("File RRA '%s' has same cf/steps (%s, %s) as '%s'", $i, $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, $i); + $resize = FALSE; + } + } + } + } + + /* print all RRAs known to cacti and add those from matching rrd file */ + if (sizeof($cacti_rra_array) > 0) { + foreach($cacti_rra_array 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; + + if ($consolidation_functions{$cacti_rra['cf']} == trim($file_rra['cf'], '"') && + $cacti_rra['steps'] == $file_rra['pdp_per_row']) { + + if (!isset($info['rra'][$file_rra_id]['seen'])) { + # mark both rra id's as seen to avoid printing them as non-matching + $info['rra'][$file_rra_id]['seen'] = TRUE; + $cacti_rra_array[$cacti_rra_id]['seen'] = TRUE; + } else { + continue; + } + + if ($cacti_rra['xff'] != $file_rra['xff']) { + $diff['rra'][$file_rra_id]['xff'] = __("xff for cacti rra id '%s' should be '%s'", $cacti_rra_id, $cacti_rra['xff']); + } + + if ($cacti_rra['rows'] != $file_rra['rows'] && $resize) { + $diff['rra'][$file_rra_id]['rows'] = __("number of rows for cacti rra id '%s' should be '%s'", $cacti_rra_id, $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_rra_array[$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'] = $consolidation_functions{$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'] = __("RRA '%s' missing in rrd file", $cacti_rra_id); + } + } + } + + # if the rrd file has an rra that has no cacti match, consider this as an error + if (sizeof($info['rra']) > 0) { + 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; + +} + +/** 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, false, 'info_header'); + + # add human readable timestamp + if (isset($info_array['last_update'])) { + $info_array['last_update'] .= ' [' . date(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' => __('Unkown Sec'), 'align' => 'right') + ); + + html_start_box('', '100%', '', '3', 'center', ''); + + html_header($header_items, 1, false, 'info_ds'); + + if (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')); + form_selectable_cell((isset($value['min']) ? number_format($value['min']) : ''), 'min', '', (isset($diff['ds'][$key]['min']) ? 'color:red;text-align:right' : 'text-align:right')); + form_selectable_cell((isset($value['max']) ? number_format($value['max']) : ''), 'max', '', (isset($diff['ds'][$key]['max']) ? 'color:red;text-align:right' : 'text-align:right')); + form_selectable_cell((isset($value['last_ds']) ? number_format($value['last_ds']) : ''), 'last_ds', '', 'text-align:right'); + form_selectable_cell((isset($value['value']) ? number_format($value['value']) : ''), 'value', '', 'text-align:right'); + form_selectable_cell((isset($value['unknown_sec']) ? number_format($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 Datapoints (0)'), 'align' => 'right') + ); + + html_start_box('', '100%', '', '3', 'center', ''); + + html_header($header_items, 1, false, 'info_rra'); + + if (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 $rrd_file - rrd file name + * @param $diff - array of discrepancies between cacti setttings and rrd file info + * @param $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) { + function print_leaves($array, $nl) { + foreach ($array as $key => $line) { + if (!is_array($line)) { + print $line . $nl; + } else { + if ($key === 'tune') continue; + if ($key === 'resize') continue; + print_leaves($line, $nl); + } + } + + } + + + $cmd = array(); + # for html/cli mode + if (!isset($_SERVER['argv'][0]) || isset($_SERVER['REQUEST_METHOD']) || isset($_SERVER['REMOTE_ADDR'])) { + $nl = '
'; + } else { + $nl = "\n"; + } + + if ($show_source && sizeof($diff)) { + # print error descriptions + print_leaves($diff, $nl); + } + + if (isset($diff['tune']) && sizeof($diff['tune'])) { + # create tune commands + foreach ($diff['tune'] as $line) { + if ($show_source == true) { + print read_config_option('path_rrdtool') . ' tune ' . $line . $nl; + }else{ + rrdtool_execute("tune $line", true, RRDTOOL_OUTPUT_STDOUT); + } + } + } + + if (isset($diff['resize']) && sizeof($diff['resize'])) { + # each resize goes into an extra line + foreach ($diff['resize'] as $line) { + if ($show_source == true) { + print read_config_option('path_rrdtool') . ' resize ' . $line . $nl; + print __('rename %s to %s', dirname($rrd_file) . '/resize.rrd', $rrd_file) . $nl; + }else{ + rrdtool_execute("resize $line", true, RRDTOOL_OUTPUT_STDOUT); + rename(dirname($rrd_file) . '/resize.rrd', $rrd_file); + } + } + } +} + +/** Given a data source id, check the rrdtool file to the data source definition + * @param $data_source_id - data source id + * @return - (array) an array containing issues with the rrdtool file definition vs data source + */ +function rrd_check($data_source_id) { + global $rrd_tune_array, $data_source_types; + + $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); +} + +/** Given a data source id, update the rrdtool file to match the data source definition + * @param $data_source_id - data source id + * @return - 1 success, 2 false + */ +function rrd_repair($data_source_id) { + global $rrd_tune_array, $data_source_types; + + $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); +} + +/** add a (list of) datasource(s) to an (array of) rrd file(s) + * @param array $file_array - array of rrd files + * @param array $ds_array - array of datasouce parameters + * @param bool $debug - debug mode + * @return mixed - success (bool) or error message (array) + */ +function rrd_datasource_add($file_array, $ds_array, $debug) { + global $data_source_types, $consolidation_functions; + + $rrdtool_pipe = rrd_init(); + + /* iterate all given rrd files */ + foreach ($file_array as $file) { + /* create a DOM object from an rrdtool dump */ + $dom = new domDocument; + $dom->loadXML(rrdtool_execute("dump $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL')); + if (!$dom) { + $check['err_msg'] = __('Error while parsing the XML of rrdtool dump'); + return $check; + } + + /* rrdtool dump depends on rrd file version: + * version 0001 => RRDTool 1.0.x + * version 0003 => RRDTool 1.2.x, 1.3.x, 1.4.x + */ + $version = trim($dom->getElementsByTagName('version')->item(0)->nodeValue); + + /* now start XML processing */ + foreach ($ds_array as $ds) { + /* first, append the strcuture in the rrd header */ + if ($ds['type'] === $data_source_types[DATA_SOURCE_TYPE_COMPUTE]) { + rrd_append_compute_ds($dom, $version, $ds['name'], $ds['type'], $ds['cdef']); + } else { + rrd_append_ds($dom, $version, $ds['name'], $ds['type'], $ds['heartbeat'], $ds['min'], $ds['max']); + } + /* now work on the structure as part of the tree */ + rrd_append_cdp_prep_ds($dom, $version); + /* add alues to the tree */ + rrd_append_value($dom); + } + + if ($debug) { + echo $dom->saveXML(); + } else { + /* for rrdtool restore, we need a file, so write the XML to disk */ + $xml_file = $file . '.xml'; + $rc = $dom->save($xml_file); + /* verify, if write was successful */ + if ($rc === false) { + $check['err_msg'] = __('ERROR while writing XML file: %s', $xml_file); + return $check; + } else { + /* are we allowed to write the rrd file? */ + if (is_writable($file)) { + /* restore the modified XML to rrd */ + rrdtool_execute("restore -f $xml_file $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL'); + /* scratch that XML file to avoid filling up the disk */ + unlink($xml_file); + cacti_log(__('Added datasource(s) to rrd file: %s', $file), false, 'UTIL'); + } else { + $check['err_msg'] = __('ERROR: RRD file %s not writeable', $file); + return $check; + } + } + } + } + + rrd_close($rrdtool_pipe); + + return true; +} + +/** delete a (list of) rra(s) from an (array of) rrd file(s) + * @param array $file_array - array of rrd files + * @param array $rra_array - array of rra parameters + * @param bool $debug - debug mode + * @return mixed - success (bool) or error message (array) + */ +function rrd_rra_delete($file_array, $rra_array, $debug) { + $rrdtool_pipe = ''; + + /* iterate all given rrd files */ + foreach ($file_array as $file) { + /* create a DOM document from an rrdtool dump */ + $dom = new domDocument; + $dom->loadXML(rrdtool_execute("dump $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL')); + if (!$dom) { + $check['err_msg'] = __('Error while parsing the XML of rrdtool dump'); + return $check; + } + + /* now start XML processing */ + foreach ($rra_array as $rra) { + rrd_delete_rra($dom, $rra, $debug); + } + + if ($debug) { + echo $dom->saveXML(); + } else { + /* for rrdtool restore, we need a file, so write the XML to disk */ + $xml_file = $file . '.xml'; + $rc = $dom->save($xml_file); + /* verify, if write was successful */ + if ($rc === false) { + $check['err_msg'] = __('ERROR while writing XML file: %s', $xml_file); + return $check; + } else { + /* are we allowed to write the rrd file? */ + if (is_writable($file)) { + /* restore the modified XML to rrd */ + rrdtool_execute("restore -f $xml_file $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL'); + /* scratch that XML file to avoid filling up the disk */ + unlink($xml_file); + cacti_log(__('Deleted rra(s) from rrd file: %s', $file), false, 'UTIL'); + } else { + $check['err_msg'] = __('ERROR: RRD file %s not writeable', $file); + return $check; + } + } + } + } + + rrd_close($rrdtool_pipe); + + return true; +} + +/** clone a (list of) rra(s) from an (array of) rrd file(s) + * @param array $file_array - array of rrd files + * @param string $cf - new consolidation function + * @param array $rra_array - array of rra parameters + * @param bool $debug - debug mode + * @return mixed - success (bool) or error message (array) + */ +function rrd_rra_clone($file_array, $cf, $rra_array, $debug) { + $rrdtool_pipe = ''; + + /* iterate all given rrd files */ + foreach ($file_array as $file) { + /* create a DOM document from an rrdtool dump */ + $dom = new domDocument; + $dom->loadXML(rrdtool_execute("dump $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL')); + if (!$dom) { + $check['err_msg'] = __('Error while parsing the XML of rrdtool dump'); + return $check; + } + + /* now start XML processing */ + foreach ($rra_array as $rra) { + rrd_copy_rra($dom, $cf, $rra, $debug); + } + + if ($debug) { + echo $dom->saveXML(); + } else { + /* for rrdtool restore, we need a file, so write the XML to disk */ + $xml_file = $file . '.xml'; + $rc = $dom->save($xml_file); + /* verify, if write was successful */ + if ($rc === false) { + $check['err_msg'] = __('ERROR while writing XML file: %s', $xml_file); + return $check; + } else { + /* are we allowed to write the rrd file? */ + if (is_writable($file)) { + /* restore the modified XML to rrd */ + rrdtool_execute("restore -f $xml_file $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL'); + /* scratch that XML file to avoid filling up the disk */ + unlink($xml_file); + cacti_log(__('Deleted rra(s) from rrd file: %s', $file), false, 'UTIL'); + } else { + $check['err_msg'] = __('ERROR: RRD file %s not writeable', $file); + return $check; + } + } + } + } + + rrd_close($rrdtool_pipe); + + return true; +} + +/** appends a subtree to an RRD XML structure + * @param object $dom - the DOM object, where the RRD XML is stored + * @param string $version- rrd file version + * @param string $name - name of the new ds + * @param string $type - type of the new ds + * @param int $min_hb - heartbeat of the new ds + * @param string $min - min value of the new ds or [NaN|U] + * @param string $max - max value of the new ds or [NaN|U] + * @return object - modified DOM + */ +function rrd_append_ds($dom, $version, $name, $type, $min_hb, $min, $max) { + /* rrdtool version dependencies */ + if ($version === RRD_FILE_VERSION1) { + $last_ds = 'U'; + } + elseif ($version === RRD_FILE_VERSION3) { + $last_ds = 'UNKN'; + } + + /* create subtree */ + $new_dom = new DOMDocument; + /* pretty print */ + $new_dom->formatOutput = true; + /* this defines the new node structure */ + $new_dom->loadXML(" + + $name + $type + $min_hb + $min + $max + + + $last_ds + 0.0000000000e+00 + 0 + "); + + /* create a node element from new document */ + $new_node = $new_dom->getElementsByTagName('ds')->item(0); + #echo $new_dom->saveXML(); # print new node + + /* get XPATH notation required for positioning */ + #$xpath = new DOMXPath($dom); + /* get XPATH for entry where new node will be inserted + * which is the entry */ + #$insert = $xpath->query('/rrd/rra')->item(0); + $insert = $dom->getElementsByTagName('rra')->item(0); + + /* import the new node */ + $new_node = $dom->importNode($new_node, true); + /* and insert it at the correct place */ + $insert->parentNode->insertBefore($new_node, $insert); +} + +/** COMPUTE DS: appends a subtree to an RRD XML structure + * @param object $dom - the DOM object, where the RRD XML is stored + * @param string $version- rrd file version + * @param string $name - name of the new ds + * @param string $type - type of the new ds + * @param int $cdef - the cdef rpn used for COMPUTE + * @return object - modified DOM + */ +function rrd_append_compute_ds($dom, $version, $name, $type, $cdef) { + /* rrdtool version dependencies */ + if ($version === RRD_FILE_VERSION1) { + $last_ds = 'U'; + } + elseif ($version === RRD_FILE_VERSION3) { + $last_ds = 'UNKN'; + } + + /* create subtree */ + $new_dom = new DOMDocument; + /* pretty print */ + $new_dom->formatOutput = true; + /* this defines the new node structure */ + $new_dom->loadXML(" + + $name + $type + $cdef + + + $last_ds + 0.0000000000e+00 + 0 + "); + + /* create a node element from new document */ + $new_node = $new_dom->getElementsByTagName('ds')->item(0); + + /* get XPATH notation required for positioning */ + #$xpath = new DOMXPath($dom); + /* get XPATH for entry where new node will be inserted + * which is the entry */ + #$insert = $xpath->query('/rrd/rra')->item(0); + $insert = $dom->getElementsByTagName('rra')->item(0); + + /* import the new node */ + $new_node = $dom->importNode($new_node, true); + /* and insert it at the correct place */ + $insert->parentNode->insertBefore($new_node, $insert); +} + +/** append a subtree to the subtrees of a RRD XML structure + * @param object $dom - the DOM object, where the RRD XML is stored + * @param string $version - rrd file version + * @return object - the modified DOM object + */ +function rrd_append_cdp_prep_ds($dom, $version) { + /* get all entries */ + #$cdp_prep_list = $xpath->query('/rrd/rra/cdp_prep'); + $cdp_prep_list = $dom->getElementsByTagName('rra')->item(0)->getElementsByTagName('cdp_prep'); + + /* get XPATH notation required for positioning */ + #$xpath = new DOMXPath($dom); + + /* get XPATH for source entry */ + #$src_ds = $xpath->query('/rrd/rra/cdp_prep/ds')->item(0); + $src_ds = $dom->getElementsByTagName('rra')->item(0)->getElementsByTagName('cdp_prep')->item(0)->getElementsByTagName('ds')->item(0); + /* clone the source ds entry to preserve RRDTool notation */ + $new_ds = $src_ds->cloneNode(true); + + /* rrdtool version dependencies */ + if ($version === RRD_FILE_VERSION3) { + $new_ds->getElementsByTagName('primary_value')->item(0)->nodeValue = ' NaN '; + $new_ds->getElementsByTagName('secondary_value')->item(0)->nodeValue = ' NaN '; + } + + /* the new node always has default entries */ + $new_ds->getElementsByTagName('value')->item(0)->nodeValue = ' NaN '; + $new_ds->getElementsByTagName('unknown_datapoints')->item(0)->nodeValue = ' 0 '; + + + /* iterate all entries found, equals 'number of ' times 'number of ' */ + if ($cdp_prep_list->length) { + foreach ($cdp_prep_list as $cdp_prep) { + /* $cdp_prep now points to the next XML Element + * and append new ds entry at end of child list */ + $cdp_prep->appendChild($new_ds); + } + } +} + +/** append a alue element to the subtrees of a RRD XML structure + * @param object $dom - the DOM object, where the RRD XML is stored + * @return object - the modified DOM object + */ +function rrd_append_value($dom) { + /* get XPATH notation required for positioning */ + #$xpath = new DOMXPath($dom); + + /* get all entries */ + #$itemList = $xpath->query('/rrd/rra/database/row'); + $itemList = $dom->getElementsByTagName('row'); + + /* create entry to preserve RRDTool notation */ + $new_v = $dom->createElement('v', ' NaN '); + + /* iterate all entries found, equals 'number of ' times 'number of ' */ + if ($itemList->length) { + foreach ($itemList as $item) { + /* $item now points to the next XML Element + * and append new ds entry at end of child list */ + $item->appendChild($new_v); + } + } +} + +/** delete an subtree from the XML structure + * @param object $dom - the DOM document, where the RRD XML is stored + * @param array $rra_parm - a single rra parameter set, given by the user + * @return object - the modified DOM object + */ +function rrd_delete_rra($dom, $rra_parm) { + /* find all RRA DOMNodes */ + $rras = $dom->getElementsByTagName('rra'); + + /* iterate all entries found */ + $nb = $rras->length; + for ($pos = 0; $pos < $nb; $pos++) { + /* retrieve all RRA DOMNodes one by one */ + $rra = $rras->item($pos); + $cf = $rra->getElementsByTagName('cf')->item(0)->nodeValue; + $pdp_per_row = $rra->getElementsByTagName('pdp_per_row')->item(0)->nodeValue; + $xff = $rra->getElementsByTagName('xff')->item(0)->nodeValue; + $rows = $rra->getElementsByTagName('row')->length; + + if ($cf == $rra_parm['cf'] && + $pdp_per_row == $rra_parm['pdp_per_row'] && + $xff == $rra_parm['xff'] && + $rows == $rra_parm['rows']) { + print(__("RRA (CF=%s, ROWS=%d, PDP_PER_ROW=%d, XFF=%1.2f) removed from RRD file\n", $cf, $rows, $pdp_per_row, $xff)); + /* we need the parentNode for removal operation */ + $parent = $rra->parentNode; + $parent->removeChild($rra); + break; /* do NOT accidentally remove more than one element, else loop back to forth */ + } + } + return $dom; +} + +/** clone an subtree of the XML structure, replacing cf + * @param object $dom - the DOM document, where the RRD XML is stored + * @param string $cf - new consolidation function + * @param array $rra_parm - a single rra parameter set, given by the user + * @return object - the modified DOM object + */ +function rrd_copy_rra($dom, $cf, $rra_parm) { + /* find all RRA DOMNodes */ + $rras = $dom->getElementsByTagName('rra'); + + /* iterate all entries found */ + $nb = $rras->length; + for ($pos = 0; $pos < $nb; $pos++) { + /* retrieve all RRA DOMNodes one by one */ + $rra = $rras->item($pos); + $_cf = $rra->getElementsByTagName('cf')->item(0)->nodeValue; + $_pdp_per_row = $rra->getElementsByTagName('pdp_per_row')->item(0)->nodeValue; + $_xff = $rra->getElementsByTagName('xff')->item(0)->nodeValue; + $_rows = $rra->getElementsByTagName('row')->length; + + if ($_cf == $rra_parm['cf'] && + $_pdp_per_row == $rra_parm['pdp_per_row'] && + $_xff == $rra_parm['xff'] && + $_rows == $rra_parm['rows']) { + print(__("RRA (CF=%s, ROWS=%d, PDP_PER_ROW=%d, XFF=%1.2f) adding to RRD file\n", $cf, $_rows, $_pdp_per_row, $_xff)); + /* we need the parentNode for append operation */ + $parent = $rra->parentNode; + + /* get a clone of the matching RRA */ + $new_rra = $rra->cloneNode(true); + /* and find the 'old' cf */ + #$old_cf = $new_rra->getElementsByTagName('cf')->item(0); + /* now replace old cf with new one */ + #$old_cf->childNodes->item(0)->replaceData(0,20,$cf); + $new_rra->getElementsByTagName('cf')->item(0)->nodeValue = $cf; + + /* append new rra entry at end of the list */ + $parent->appendChild($new_rra); + break; /* do NOT accidentally clone more than one element, else loop back to forth */ + } + } + + return $dom; +}