1<?php 2 3/** 4 * Observium 5 * 6 * This file is part of Observium. 7 * 8 * @package observium 9 * @subpackage rrdtool 10 * @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2019 Observium Limited 11 * 12 */ 13 14/** 15 * Get full path for rrd file. 16 * 17 * @param array $device Device array 18 * @param string $filename Base filename for rrd file 19 * @return string Full rrd file path 20 */ 21// TESTME needs unit testing 22function get_rrd_path($device, $filename) 23{ 24 global $config; 25 26 $rrd_dir = trim($config['rrd_dir']) . '/'; 27 $filename = trim($filename); 28 if (str_starts($filename, $rrd_dir)) 29 { 30 // Already full path 31 return $filename; 32 } 33 34 $filename = safename($filename); 35 36 // If filename empty, return base rrd dirname for device (for example in delete_device()) 37 $rrd_file = $rrd_dir; 38 if (strlen($device['hostname'])) 39 { 40 $rrd_file .= $device['hostname'] . '/'; 41 } 42 43 if (strlen($filename) > 0) 44 { 45 $ext = pathinfo($filename, PATHINFO_EXTENSION); 46 if ($ext != 'rrd') { $filename .= '.rrd'; } // Add rrd extension if not already set 47 $rrd_file .= safename($filename); 48 49 // Add rrd filename to global array $graph_return 50 $GLOBALS['graph_return']['rrds'][] = $rrd_file; 51 } 52 53 return $rrd_file; 54} 55 56/** 57 * Rename rrd file for device is some schema changes. 58 * 59 * @param array $device 60 * @param string $old_rrd Base filename for old rrd file 61 * @param string $new_rrd Base filename for new rrd file 62 * @param boolean $overwrite Force overwrite new rrd file if already exist 63 * @return bool TRUE if renamed 64 */ 65function rename_rrd($device, $old_rrd, $new_rrd, $overwrite = FALSE) 66{ 67 $old_rrd = get_rrd_path($device, $old_rrd); 68 $new_rrd = get_rrd_path($device, $new_rrd); 69 print_debug_vars($old_rrd); 70 print_debug_vars($new_rrd); 71 if (is_file($old_rrd)) 72 { 73 if (!$overwrite && is_file($new_rrd)) 74 { 75 // If not forced overwrite file, return false 76 print_debug("RRD already exist new file: '$new_rrd'"); 77 $renamed = FALSE; 78 } else { 79 $renamed = rename($old_rrd, $new_rrd); 80 } 81 } else { 82 print_debug("RRD old file not found: '$old_rrd'"); 83 $renamed = FALSE; 84 } 85 if ($renamed) 86 { 87 print_debug("RRD moved: '$old_rrd' -> '$new_rrd'"); 88 } 89 90 return $renamed; 91} 92 93/** 94 * Rename rrd file for device (same as in rename_rrd()), 95 * but rrd filename detected by common entity params 96 * 97 * @param array $device 98 * @param string $entity Entity type (sensor, status, etc..) 99 * @param array $old Old entity params, based on discovery entity 100 * @param array $new New entity params, based on discovery entity 101 * @param boolean $overwrite Force overwrite new rrd file if already exist 102 * @return bool TRUE if renamed 103 */ 104function rename_rrd_entity($device, $entity, $old, $new, $overwrite = FALSE) 105{ 106 switch ($entity) 107 { 108 case 'sensor': 109 $old_sensor = array('poller_type' => $old['poller_type'], 110 'sensor_descr' => $old['descr'], 111 'sensor_class' => $old['class'], 112 'sensor_type' => $old['type'], 113 'sensor_index' => $old['index']); 114 $new_sensor = array('poller_type' => $new['poller_type'], 115 'sensor_descr' => $new['descr'], 116 'sensor_class' => $new['class'], 117 'sensor_type' => $new['type'], 118 'sensor_index' => $new['index']); 119 120 $old_rrd = get_sensor_rrd($device, $old_sensor); 121 $new_rrd = get_sensor_rrd($device, $new_sensor); 122 break; 123 case 'status': 124 $old_status = array('poller_type' => $old['poller_type'], 125 'status_descr' => $old['descr'], 126 'status_type' => $old['type'], 127 'status_index' => $old['index']); 128 $new_status = array('poller_type' => $new['poller_type'], 129 'status_descr' => $new['descr'], 130 'status_type' => $new['type'], 131 'status_index' => $new['index']); 132 133 $old_rrd = get_status_rrd($device, $old_status); 134 $new_rrd = get_status_rrd($device, $new_status); 135 break; 136 default: 137 print_debug("skipped unknown entity for rename rrd"); 138 return FALSE; 139 } 140 141 $old_rrd = safename($old_rrd); 142 143 return rename_rrd($device, $old_rrd, $new_rrd, $overwrite); 144} 145 146/** 147 * Opens up a pipe to RRDTool using handles provided 148 * 149 * @return boolean 150 * @global array $config 151 * @param &rrd_process 152 * @param &rrd_pipes 153 */ 154// TESTME needs unit testing 155function rrdtool_pipe_open(&$rrd_process, &$rrd_pipes) 156{ 157 global $config; 158 159 $command = $config['rrdtool'] . ' -'; // Waits for input via standard input (STDIN) 160 161 $descriptorspec = array( 162 0 => array('pipe', 'r'), // stdin 163 1 => array('pipe', 'w'), // stdout 164 2 => array('pipe', 'w') // stderr 165 ); 166 167 $cwd = $config['rrd_dir']; 168 $env = array(); 169 170 $rrd_process = proc_open($command, $descriptorspec, $rrd_pipes, $cwd, $env); 171 172 stream_set_blocking($rrd_pipes[1], 0); 173 stream_set_blocking($rrd_pipes[2], 0); 174 175 if (is_resource($rrd_process)) 176 { 177 // $pipes now looks like this: 178 // 0 => writeable handle connected to child stdin 179 // 1 => readable handle connected to child stdout 180 // 2 => readable handle connected to child stderr 181 if (OBS_DEBUG > 1) 182 { 183 print_message('RRD PIPE OPEN[%gTRUE%n]', 'console'); 184 } 185 186 return TRUE; 187 } else { 188 if (isset($config['rrd']['debug']) && $config['rrd']['debug']) 189 { 190 logfile('rrd.log', "RRD pipe process not opened '$command'."); 191 } 192 if (OBS_DEBUG > 1) 193 { 194 print_message('RRD PIPE OPEN[%rFALSE%n]', 'console'); 195 } 196 return FALSE; 197 } 198} 199 200/** 201 * Closes the pipe to RRDTool 202 * 203 * @return integer 204 * @param resource rrd_process 205 * @param array rrd_pipes 206 */ 207// TESTME needs unit testing 208function rrdtool_pipe_close($rrd_process, &$rrd_pipes) 209{ 210 if (OBS_DEBUG > 1) 211 { 212 $rrd_status['stdout'] = stream_get_contents($rrd_pipes[1]); 213 $rrd_status['stderr'] = stream_get_contents($rrd_pipes[2]); 214 } 215 216 if (is_resource($rrd_pipes[0])) 217 { 218 fclose($rrd_pipes[0]); 219 } 220 fclose($rrd_pipes[1]); 221 fclose($rrd_pipes[2]); 222 223 // It is important that you close any pipes before calling 224 // proc_close in order to avoid a deadlock 225 226 $rrd_status['exitcode'] = proc_close($rrd_process); 227 if (OBS_DEBUG > 1) 228 { 229 print_message('RRD PIPE CLOSE['.($rrd_status['exitcode'] !== 0 ? '%rFALSE' : '%gTRUE').'%n]', 'console'); 230 if ($rrd_status['stdout']) 231 { 232 print_message("RRD PIPE STDOUT[\n".$rrd_status['stdout']."\n]", 'console', FALSE); 233 } 234 if ($rrd_status['exitcode'] && $rrd_status['stderr']) 235 { 236 // Show stderr if exitcode not 0 237 print_message("RRD PIPE STDERR[\n".$rrd_status['stderr']."\n]", 'console', FALSE); 238 } 239 } 240 241 return $rrd_status['exitcode']; 242} 243 244/** 245 * Generates a graph file at $graph_file using $options 246 * Opens its own rrdtool pipe. 247 * 248 * @return integer 249 * @param string graph_file 250 * @param string options 251 */ 252// TESTME needs unit testing 253function rrdtool_graph($graph_file, $options) 254{ 255 global $config; 256 257 // Note, always use pipes, because standard command line has limits! 258 if ($config['rrdcached']) 259 { 260 $options = str_replace($config['rrd_dir'].'/', '', $options); 261 $cmd = 'graph --daemon ' . $config['rrdcached'] . " $graph_file $options"; 262 } else { 263 $cmd = "graph $graph_file $options"; 264 } 265 $GLOBALS['rrd_status'] = FALSE; 266 $GLOBALS['exec_status'] = array('command' => $config['rrdtool'] . ' ' . $cmd, 267 'stdout' => '', 268 'exitcode' => -1); 269 270 $start = microtime(TRUE); 271 rrdtool_pipe_open($rrd_process, $rrd_pipes); 272 if (is_resource($rrd_process)) 273 { 274 // $pipes now looks like this: 275 // 0 => writeable handle connected to child stdin 276 // 1 => readable handle connected to child stdout 277 // Any error output will be appended to /tmp/error-output.txt 278 279 fwrite($rrd_pipes[0], $cmd); 280 fclose($rrd_pipes[0]); 281 282 $iter = 0; 283 while (strlen($line) < 1 && $iter < 1000) 284 { 285 // wait for 10 milliseconds to loosen loop 286 usleep(10000); 287 $line = fgets($rrd_pipes[1], 1024); 288 $stdout .= $line; 289 $iter++; 290 } 291 $stdout = preg_replace('/(?:\n|\r\n|\r)$/D', '', $stdout); // remove last (only) eol 292 unset($iter); 293 294 $runtime = microtime(TRUE) - $start; 295 296 // Check rrdtool's output for the command. 297 if (preg_match('/\d+x\d+/', $stdout)) 298 { 299 $GLOBALS['rrd_status'] = TRUE; 300 } else { 301 $stderr = trim(stream_get_contents($rrd_pipes[2])); 302 if (isset($config['rrd']['debug']) && $config['rrd']['debug']) 303 { 304 logfile('rrd.log', "RRD $stderr, CMD: " . $GLOBALS['exec_status']['command']); 305 } 306 } 307 $exitcode = rrdtool_pipe_close($rrd_process, $rrd_pipes); 308 309 $GLOBALS['exec_status']['exitcode'] = $exitcode; 310 $GLOBALS['exec_status']['stdout'] = $stdout; 311 $GLOBALS['exec_status']['stderr'] = $stderr; 312 } else { 313 $runtime = microtime(TRUE) - $start; 314 $stdout = NULL; 315 } 316 $GLOBALS['exec_status']['runtime'] = $runtime; 317 // Add some data to global array $graph_return 318 $GLOBALS['graph_return']['status'] = $GLOBALS['rrd_status']; 319 $GLOBALS['graph_return']['command'] = $GLOBALS['exec_status']['command']; 320 $GLOBALS['graph_return']['filename'] = $graph_file; 321 $GLOBALS['graph_return']['output'] = $stdout; 322 $GLOBALS['graph_return']['runtime'] = $GLOBALS['exec_status']['runtime']; 323 324 if (OBS_DEBUG) 325 { 326 print_message(PHP_EOL . 'RRD CMD[%y' . $cmd . '%n]', 'console', FALSE); 327 $debug_msg = 'RRD RUNTIME['.($runtime > 0.1 ? '%r' : '%g').round($runtime, 4).'s%n]' . PHP_EOL; 328 $debug_msg .= 'RRD STDOUT['.($GLOBALS['rrd_status'] ? '%g': '%r').$stdout.'%n]' . PHP_EOL; 329 if ($stderr) 330 { 331 $debug_msg .= 'RRD STDERR[%r'.$stderr.'%n]' . PHP_EOL; 332 } 333 $debug_msg .= 'RRD_STATUS['.($GLOBALS['rrd_status'] ? '%gTRUE': '%rFALSE').'%n]'; 334 335 print_message($debug_msg . PHP_EOL, 'console'); 336 } 337 338 return $stdout; 339} 340 341/** 342 * Generates and pipes a command to rrdtool 343 * 344 * @param string command 345 * @param string filename 346 * @param string options 347 * @global array $config 348 * @global mixed $rrd_pipes 349 */ 350// TESTME needs unit testing 351function rrdtool($command, $filename, $options) 352{ 353 global $config, $rrd_pipes; 354 355 // We now require rrdcached 1.5.5 356 if ($config['rrdcached'] && (OBS_RRD_NOLOCAL || !in_array($command, ['create', 'tune']))) 357 { 358 $filename = str_replace($config['rrd_dir'].'/', '', $filename); 359 if (OBS_RRD_NOLOCAL && $command == 'create') 360 { 361 // No overwrite for remote rrdtool, since no way for check if rrdfile exist 362 $options .= ' --no-overwrite'; 363 } 364 $options .= ' --daemon ' . $config['rrdcached']; 365 } 366 367 $cmd = "$command $filename $options"; 368 369 $GLOBALS['rrd_status'] = FALSE; 370 $GLOBALS['exec_status'] = array('command' => $config['rrdtool'] . ' ' . $cmd, 371 'exitcode' => 1); 372 373 if ($config['norrd']) 374 { 375 print_message("[%rRRD Disabled - $cmd%n]", 'color'); 376 return NULL; 377 } 378 379 if (in_array($command, array('fetch', 'last', 'lastupdate', 'tune'))) 380 { 381 // This commands require exact STDOUT, skip use pipes 382 $command = $config['rrdtool'] . ' ' . $cmd; 383 $stdout = external_exec($command, 500); // Limit exec time to 500ms 384 $runtime = $GLOBALS['exec_status']['runtime']; 385 $GLOBALS['rrd_status'] = $GLOBALS['exec_status']['exitcode'] === 0; 386 // Check rrdtool's output for the command. 387 if (!$GLOBALS['rrd_status'] && isset($config['rrd']['debug']) && $config['rrd']['debug']) 388 { 389 logfile('rrd.log', "RRD ".$GLOBALS['exec_status']['stderr'].", CMD: $cmd"); 390 } 391 } else { 392 // FIXME, need add check if pipes exist 393 $start = microtime(TRUE); 394 fwrite($rrd_pipes[0], $cmd."\n"); 395 usleep(1000); 396 397 $stdout = trim(stream_get_contents($rrd_pipes[1])); 398 $stderr = trim(stream_get_contents($rrd_pipes[2])); 399 $runtime = microtime(TRUE) - $start; 400 401 // Check rrdtool's output for the command. 402 if (strpos($stdout, 'ERROR') !== FALSE) 403 { 404 if (isset($config['rrd']['debug']) && $config['rrd']['debug']) 405 { 406 logfile('rrd.log', "RRD $stdout, CMD: $cmd"); 407 } 408 } else { 409 $GLOBALS['rrd_status'] = TRUE; 410 $GLOBALS['exec_status']['exitcode'] = 0; 411 } 412 $GLOBALS['exec_status']['stdout'] = $stdout; 413 $GLOBALS['exec_status']['stdin'] = $stdin; 414 $GLOBALS['exec_status']['runtime'] = $runtime; 415 416 } 417 418 $GLOBALS['rrdtool'][$command]['time'] += $runtime; 419 $GLOBALS['rrdtool'][$command]['count']++; 420 421 if (OBS_DEBUG) 422 { 423 print_message(PHP_EOL . 'RRD CMD[%y' . $cmd . '%n]', 'console', FALSE); 424 $debug_msg = 'RRD RUNTIME['.($runtime > 1 ? '%r' : '%g').round($runtime, 4).'s%n]' . PHP_EOL; 425 $debug_msg .= 'RRD STDOUT['.($GLOBALS['rrd_status'] ? '%g': '%r').$stdout.'%n]' . PHP_EOL; 426 if ($stderr) 427 { 428 $debug_msg .= 'RRD STDERR[%r'.$stderr.'%n]' . PHP_EOL; 429 } 430 $debug_msg .= 'RRD_STATUS['.($GLOBALS['rrd_status'] ? '%gTRUE': '%rFALSE').'%n]'; 431 432 print_message($debug_msg . PHP_EOL, 'console'); 433 } 434 435 return $stdout; 436} 437 438/** 439 * Generates an rrd database at $filename using $options 440 * Creates the file if it does not exist yet. 441 * DEPRECATED: use rrdtool_create_ng(), this will disappear and ng will be renamed when conversion is complete. 442 * 443 * @param array device 444 * @param string filename 445 * @param string ds 446 * @param string options 447 */ 448function rrdtool_create($device, $filename, $ds, $options = '') 449{ 450 global $config; 451 452 if ($filename[0] == '/') 453 { 454 print_debug("You should pass the filename only (not the full path) to this function! Passed filename: ".$filename); 455 $filename = basename($filename); 456 } 457 458 $fsfilename = get_rrd_path($device, $filename); 459 460 if ($config['norrd']) 461 { 462 print_message("[%rRRD Disabled - create $fsfilename%n]", 'color'); 463 return NULL; 464 } 465 else if (OBS_RRD_NOLOCAL) 466 { 467 print_debug("RRD create $fsfilename passed to remote rrdcached with --no-overwrite."); 468 } 469 else if (rrd_exists($device, $filename)) 470 { 471 print_debug("RRD $fsfilename already exists - no need to create."); 472 return FALSE; // Bail out if the file exists already 473 } 474 475 if (!$options) 476 { 477 $options = preg_replace('/\s+/', ' ', $config['rrd']['rra']); 478 } 479 480 $step = "--step ".$config['rrd']['step']; 481 482 //$command = $config['rrdtool'] . " create $fsfilename $ds $step $options"; 483 //return external_exec($command); 484 485 // Clean up old ds strings. This is kinda nasty. 486 $ds = str_replace("\ 487", '', $ds); 488 return rrdtool('create', $fsfilename, $ds . " $step $options"); 489 490} 491 492/** 493 * Generates RRD filename from definition 494 * 495 * @param string/array $def Original filename, using %index% (or %custom% %keys%) as placeholder for indexes 496 * @param string/array $index Index, if RRD type is indexed (or array of multiple indexes) 497 * @return string Filename of RRD 498 */ 499// TESTME needs unit testing 500function rrdtool_generate_filename($def, $index) 501{ 502 if (is_string($def)) 503 { 504 // Compat with old 505 $filename = $def; 506 } 507 elseif (isset($def['file'])) 508 { 509 $filename = $def['file']; 510 } 511 elseif (isset($def['entity_type'])) 512 { 513 // Entity specific filename by ID, ie for sensor/status/counter 514 $entity_id = $index; 515 return get_entity_rrd_by_id($def['entity_type'], $entity_id); 516 } 517 518 // Generate warning for indexed filenames containing %index% - does not help if you use custom field names for indexing 519 if (strstr($filename, '%index%') !== FALSE) 520 { 521 if ($index === NULL) 522 { 523 print_warning("RRD filename generation error: filename contains %index%, but \$index is NULL!"); 524 } 525 } 526 527 // Convert to single element array if not an array. 528 // This will automatically use %index% as the field to replace (see below). 529 if (!is_array($index)) { $index = array('index' => $index); } 530 531 // Replace %index% by $index['index'], %foo% by $index['foo'] etc. 532 $filename = array_tag_replace($index, $filename); 533 534 return safename($filename); 535} 536 537/** 538 * Generates an rrd database based on $type definition, using $options 539 * Only creates the file if it does not exist yet. 540 * Should most likely not be called on its own, as an update call will check for existence. 541 * 542 * @param array $device Device array 543 * @param string/array $type rrd file type from $config['rrd_types'] or actual config array 544 * @param string/array $index Index, if RRD type is indexed (or array of multiple tags) 545 * @param array $options Options for create RRD, like STEP, RRA, MAX or SPEED 546 * 547 * @return string 548 */ 549// TESTME needs unit testing 550function rrdtool_create_ng($device, $type, $index = NULL, $options = []) 551{ 552 global $config; 553 554 if (!is_array($type)) // We were passed a string 555 { 556 if (!is_array($config['rrd_types'][$type])) // Check if definition exists 557 { 558 print_warning("Cannot create RRD for type $type - not found in definitions!"); 559 return FALSE; 560 } 561 562 $definition = $config['rrd_types'][$type]; 563 } else { // We were passed an array, use as-is 564 $definition = $type; 565 } 566 567 $filename = rrdtool_generate_filename($definition, $index); 568 569 $fsfilename = get_rrd_path($device, $filename); 570 571 if ($config['norrd']) 572 { 573 print_message("[%rRRD Disabled - create $fsfilename%n]", 'color'); 574 return NULL; 575 } 576 else if (OBS_RRD_NOLOCAL) 577 { 578 print_debug("RRD create $fsfilename passed to remote rrdcached with --no-overwrite."); 579 } 580 else if (rrd_exists($device, $filename)) 581 { 582 print_debug("RRD $fsfilename already exists - no need to create."); 583 return FALSE; // Bail out if the file exists already 584 } 585 586 // Set RRA option 587 $rra = isset($options['rra']) ? $options['rra'] : $config['rrd']['rra']; 588 $rra = preg_replace('/\s+/', ' ', $rra); 589 590 // Set step 591 $step = isset($options['step']) ? $options['step'] : $config['rrd']['step']; 592 593 // Create tags, for use in replace 594 $tags = []; 595 if (strlen($index)) 596 { 597 $tags['index'] = $index; 598 } 599 if (isset($options['speed'])) 600 { 601 print_debug("Passed speed: ".$options['speed']); 602 $options['speed'] = intval(unit_string_to_numeric($options['speed']) / 8); // Detect passed speed value (converted to bits) 603 $tags['speed'] = max($options['speed'], $config['max_port_speed']); // But result select maximum between passed and default! 604 print_debug(" RRD speed: ".$options['speed'].PHP_EOL. 605 " Default: ".$config['max_port_speed'].PHP_EOL. 606 " Max: ".$tags['speed']); 607 } else { 608 // Default speed 609 $tags['speed'] = $config['max_port_speed']; 610 } 611 612 // Create DS parameter based on the definition 613 $ds = array(); 614 615 foreach ($definition['ds'] as $name => $def) 616 { 617 if (strlen($name) > 19) { print_warning("SEVERE: DS name $name is longer than 19 characters - over RRD limit!"); } 618 619 // Set defaults for missing attributes 620 if (!isset($def['type'])) { $def['type'] = 'COUNTER'; } 621 if (!isset($def['max'])) { $def['max'] = 'U'; } 622 else { $def['max'] = array_tag_replace($tags, $def['max']); } // can use %speed% tag, speed must passed by $options['speed'] 623 if (!isset($def['min'])) { $def['min'] = 'U'; } 624 if (!isset($def['heartbeat'])) { $def['heartbeat'] = 2 * $step; } 625 626 // Create DS string to pass on the command line 627 $ds[] = "DS:$name:" . $def['type'] . ':' . $def['heartbeat'] . ':' . $def['min'] . ':' . $def['max']; 628 } 629 630 631 return rrdtool('create', $fsfilename, implode(' ', $ds) . " --step $step $rra"); 632 633} 634 635/** 636 * Checks if an RRD database at $filename for $device exists 637 * Checks via rrdcached if configured, else via is_exists 638 * 639 * @param array device 640 * @param string filename 641**/ 642function rrd_exists($device, $filename) 643{ 644 645 global $config; 646 647 $fsfilename = get_rrd_path($device, $filename); 648 649 if (OBS_RRD_NOLOCAL) 650 { 651 // NOTE. RRD last on remote daemon reduce polling times 652 rrdtool_last($fsfilename); 653 654 //ERROR: realpath(vds.coosm.net/status.rrd): No such file or directory 655 return strpos($GLOBALS['exec_status']['stderr'], 'No such file or directory') === FALSE; 656 //return $GLOBALS['rrd_status']; 657 } else { 658 if (is_file($fsfilename)) 659 { 660 return TRUE; 661 } else { 662 return FALSE; 663 } 664 } 665 666} 667 668/** 669 * Updates an rrd database at $filename using $options 670 * Where $options is an array, each entry which is not a number is replaced with "U" 671 * 672 * @param array $device Device array 673 * @param string/array $type RRD file type from $config['rrd_types'] or actual config array 674 * @param array $ds DS data (key/value) 675 * @param string/array $index Index, if RRD type is indexed (or array of multiple indexes) 676 * @param bool $create Create RRD file if it does not exist 677 * @param array $options Options to pass to create function if file does not exist 678 * 679 * @return string 680 */ 681// TESTME needs unit testing 682function rrdtool_update_ng($device, $type, $ds, $index = NULL, $create = TRUE, $options = []) 683{ 684 global $config, $graphs; 685 686 if (!is_array($type)) // We were passed a string 687 { 688 if (!is_array($config['rrd_types'][$type])) // Check if definition exists 689 { 690 print_warning("Cannot create RRD for type $type - not found in definitions!"); 691 return FALSE; 692 } 693 694 $definition = $config['rrd_types'][$type]; 695 696 // Append graph if not already passed 697 if (!isset($definition['graphs'])) 698 { 699 $definition['graphs'] = array(str_replace('-', '_', $type)); 700 } 701 } else { // We were passed an array, use as-is 702 $definition = $type; 703 } 704 705 $filename = rrdtool_generate_filename($definition, $index); 706 707 $fsfilename = get_rrd_path($device, $filename); 708 709 // Create the file if missing (if we have permission to create it) 710 if ($create) 711 { 712 rrdtool_create_ng($device, $type, $index, $options); 713 } 714 715 $update = array('N'); 716 717 foreach ($definition['ds'] as $name => $def) 718 { 719 if (isset($ds[$name])) 720 { 721 if (is_numeric($ds[$name])) 722 { 723 // Add data to DS update string 724 $update[] = $ds[$name]; 725 } else { 726 // Data not numeric, mark unknown 727 $update[] = 'U'; 728 } 729 } else { 730 // Data not sent, mark unknown 731 $update[] = 'U'; 732 } 733 } 734 735 /** // This is setting loads of random shit that doesn't exist 736 // ONLY GRAPHS THAT EXIST MAY GO INTO THIS ARRAY 737 // Set global graph variable for store avialable device graphs 738 foreach ($definition['graphs'] as $def) 739 { 740 $graphs[$def] = TRUE; 741 } 742 **/ 743 744 if ($config['influxdb']['enabled']) 745 { 746 influxdb_update($device, $filename, $ds, $definition, $index); 747 } 748 749 return rrdtool('update', $fsfilename, implode(':', $update)); 750} 751 752/** 753 * Updates an rrd database at $filename using $options 754 * Where $options is an array, each entry which is not a number is replaced with "U" 755 * DEPRECATED: use rrdtool_update_ng(), this will disappear and ng will be renamed when conversion is complete. 756 * 757 * @param array $device 758 * @param string $filename 759 * @param array $options 760 * @return string 761 */ 762function rrdtool_update($device, $filename, $options) 763{ 764 // Do some sanitization on the data if passed as an array. 765 if (is_array($options)) 766 { 767 $values[] = "N"; 768 foreach ($options as $value) 769 { 770 if (!is_numeric($value)) { $value = 'U'; } 771 $values[] = $value; 772 } 773 $options = implode(':', $values); 774 } 775 776 if ($filename[0] == '/') 777 { 778 $filename = basename($filename); 779 print_debug("You should pass the filename only (not the full path) to this function!"); 780 } 781 782 $fsfilename = get_rrd_path($device, $filename); 783 784 if ($GLOBALS['config']['influxdb']['enabled']) 785 { 786 influxdb_update( $device, $filename, $options ); 787 } 788 789 return rrdtool("update", $fsfilename, $options); 790} 791 792// DOCME needs phpdoc block 793// TESTME needs unit testing 794function rrdtool_fetch($filename, $options) 795{ 796 return rrdtool('fetch', $filename, $options); 797} 798 799// TESTME needs unit testing 800/** 801 * Returns the UNIX timestamp of the most recent update of $filename 802 * 803 * @param string $filename RRD filename 804 * @param string $options Mostly not required 805 * @return string UNIX timestamp 806 */ 807function rrdtool_last($filename, $options = '') 808{ 809 return rrdtool('last', $filename, $options); 810} 811 812// TESTME needs unit testing 813/** 814 * Returns the UNIX timestamp and the value stored for each datum in the most recent update of $filename 815 * 816 * @param string $filename RRD filename 817 * @param string $options Mostly not required 818 * @return string UNIX timestamp and the value stored for each datum 819 */ 820function rrdtool_lastupdate($filename, $options = '') 821{ 822 return rrdtool('lastupdate', $filename, $options); 823} 824 825// TESTME needs unit testing 826/** 827 * Renames a DS inside an RRD file 828 * 829 * @param array $device Device 830 * @param string $filename Filename 831 * @param string $oldname Current DS name 832 * @param string $newname New DS name 833 */ 834function rrdtool_rename_ds($device, $filename, $oldname, $newname) 835{ 836 global $config; 837 838 $return = FALSE; 839 if ($config['norrd']) 840 { 841 print_message('[%gRRD Disabled%n] '); 842 return $return; 843 } 844 845 // rrdtool tune rename DS supported since v1.4 846 $version = get_versions(); 847 if (version_compare($version['rrdtool_version'], '1.4', '>=')) 848 { 849 $fsfilename = get_rrd_path($device, $filename); 850 print_debug("RRD DS renamed, file $fsfilename: '$oldname' -> '$newname'"); 851 return rrdtool('tune', $filename, "--data-source-rename $oldname:$newname"); 852 } 853 854 // Comparability with old version (but we support only >= v1.5.5, this not required) 855 if (OBS_RRD_NOLOCAL) 856 { 857 print_message('[%gRRD REMOTE UNSUPPORTED%n] '); 858 } else { 859 $fsfilename = get_rrd_path($device, $filename); 860 if (is_file($fsfilename)) 861 { 862 // this function used in discovery, where not exist rrd pipes 863 $command = $config['rrdtool'] . " tune $fsfilename --data-source-rename $oldname:$newname"; 864 $return = external_exec($command); 865 //print_vars($GLOBALS['exec_status']); 866 if ($GLOBALS['exec_status']['exitcode'] === 0) 867 { 868 print_debug("RRD DS renamed, file $fsfilename: '$oldname' -> '$newname'"); 869 } else { 870 $return = FALSE; 871 } 872 } 873 } 874 875 return $return; 876} 877 878// TESTME needs unit testing 879/** 880 * Adds a DS to an RRD file 881 * 882 * @param array Device 883 * @param string Filename 884 * @param string New DS name 885 */ 886function rrdtool_add_ds($device, $filename, $add) 887{ 888 global $config; 889 890 $return = FALSE; 891 if ($config['norrd']) 892 { 893 print_message("[%gRRD Disabled%n] "); 894 return $return; 895 } 896 897 // rrdtool tune add DS supported since v1.4 898 $version = get_versions(); 899 if (version_compare($version['rrdtool_version'], '1.4', '>=')) 900 { 901 $fsfilename = get_rrd_path($device, $filename); 902 print_debug("RRD DS added, file ".$fsfilename.": '".$add."'"); 903 return rrdtool('tune', $filename, "DS:$add"); 904 } 905 906 // Comparability with old version (but we support only >= v1.5.5, this not required) 907 if (OBS_RRD_NOLOCAL) 908 { 909 print_message('[%gRRD REMOTE UNSUPPORTED%n] '); 910 } else { 911 $fsfilename = get_rrd_path($device, $filename); 912 if (is_file($fsfilename)) 913 { 914 // this function used in discovery, where not exist rrd pipes 915 916 $fsfilename = get_rrd_path($device, $filename); 917 918 $return = external_exec($config['install_dir'] . "/scripts/add_ds_to_rrd.pl ".dirname($fsfilename)." ".basename($fsfilename)." $add"); 919 920 //print_vars($GLOBALS['exec_status']); 921 if ($GLOBALS['exec_status']['exitcode'] === 0) 922 { 923 print_debug("RRD DS added, file ".$fsfilename.": '".$add."'"); 924 } else { 925 $return = FALSE; 926 } 927 } 928 } 929 930 return $return; 931} 932 933// TESTME needs unit testing 934/** 935 * Adds one or more RRAs to an RRD file; space-separated if you want to add more than one. 936 * 937 * @param array Device 938 * @param string Filename 939 * @param array RRA(s) to be added to the RRD file 940 */ 941function rrdtool_add_rra($device, $filename, $options) 942{ 943 global $config; 944 945 if ($config['norrd']) 946 { 947 print_message('[%gRRD Disabled%n] '); 948 } 949 else if (OBS_RRD_NOLOCAL) 950 { 951 ///FIXME Currently unsupported on remote rrdcached 952 print_message('[%gRRD REMOTE UNSUPPORTED%n] '); 953 } else { 954 $fsfilename = get_rrd_path($device, $filename); 955 956 external_exec($config['install_dir'] . "/scripts/rrdtoolx.py addrra $fsfilename $fsfilename.new $options"); 957 rename("$fsfilename.new", $fsfilename); 958 } 959} 960 961/** 962 * Escapes strings for RRDtool 963 * 964 * @param string String to escape 965 * @param integer if passed, string will be padded and trimmed to exactly this length (after rrdtool unescapes it) 966 * 967 * @return string Escaped string 968 */ 969// TESTME needs unit testing 970function rrdtool_escape($string, $maxlength = NULL) 971{ 972 if ($maxlength != NULL) 973 { 974 $string = substr(str_pad($string, $maxlength),0,$maxlength); 975 } 976 977 $string = str_replace(array(':', "'", '%'), array('\:', '`', '%%'), $string); 978 979 // FIXME: should maybe also probably escape these? # \ ? [ ^ ] ( $ ) ' 980 981 return $string; 982} 983 984/** 985 * Helper function to strip quotes from RRD output 986 * 987 * @str RRD-Info generated string 988 * @return String with one surrounding pair of quotes stripped 989 */ 990// TESTME needs unit testing 991function rrd_strip_quotes($str) 992{ 993 if ($str[0] == '"' && $str[strlen($str)-1] == '"') 994 { 995 return substr($str, 1, strlen($str)-2); 996 } 997 998 return $str; 999} 1000 1001/** 1002 * Determine useful information about RRD file 1003 * 1004 * Copyright (C) 2009 Bruno Prémont <bonbons AT linux-vserver.org> 1005 * 1006 * @file Name of RRD file to analyse 1007 * 1008 * @return Array describing the RRD file 1009 * 1010 */ 1011// TESTME needs unit testing 1012function rrdtool_file_info($file) 1013{ 1014 global $config; 1015 1016 $info = array('filename'=>$file); 1017 1018 if (OBS_RRD_NOLOCAL) 1019 { 1020 ///FIXME Currently unsupported on remote rrdcached 1021 print_message('[%gRRD REMOTE UNSUPPORTED%n] '); 1022 return $info; 1023 } 1024 1025 $rrd = array_filter(explode(PHP_EOL, external_exec($config['rrdtool'] . ' info ' . $file)), 'strlen'); 1026 if ($rrd) 1027 { 1028 foreach ($rrd as $s) 1029 { 1030 $p = strpos($s, '='); 1031 if ($p === false) 1032 { 1033 continue; 1034 } 1035 1036 $key = trim(substr($s, 0, $p)); 1037 $value = trim(substr($s, $p+1)); 1038 if (strncmp($key,'ds[', 3) == 0) 1039 { 1040 /* DS definition */ 1041 $p = strpos($key, ']'); 1042 $ds = substr($key, 3, $p-3); 1043 if (!isset($info['DS'])) 1044 { 1045 $info['DS'] = array(); 1046 } 1047 1048 $ds_key = substr($key, $p+2); 1049 1050 if (strpos($ds_key, '[') === false) 1051 { 1052 if (!isset($info['DS']["$ds"])) 1053 { 1054 $info['DS']["$ds"] = array(); 1055 } 1056 $info['DS']["$ds"]["$ds_key"] = rrd_strip_quotes($value); 1057 } 1058 } 1059 else if (strncmp($key, 'rra[', 4) == 0) 1060 { 1061 /* RRD definition */ 1062 $p = strpos($key, ']'); 1063 $rra = substr($key, 4, $p-4); 1064 if (!isset($info['RRA'])) 1065 { 1066 $info['RRA'] = array(); 1067 } 1068 $rra_key = substr($key, $p+2); 1069 1070 if (strpos($rra_key, '[') === false) 1071 { 1072 if (!isset($info['RRA']["$rra"])) 1073 { 1074 $info['RRA']["$rra"] = array(); 1075 } 1076 $info['RRA']["$rra"]["$rra_key"] = rrd_strip_quotes($value); 1077 } 1078 } else if (strpos($key, '[') === false) { 1079 $info[$key] = rrd_strip_quotes($value); 1080 } 1081 } 1082 } 1083 1084 return $info; 1085} 1086 1087// Creates a string of X number of ,ADDNAN. Used when aggregating things. 1088function rrd_addnan($count) 1089{ 1090 return str_repeat(',ADDNAN', $count); 1091} 1092 1093// creates an rpn string to add an array of DSes together 1094function rrd_aggregate_dses($ds_list) 1095{ 1096 return implode(',', $ds_list) . rrd_addnan(count($ds_list) - 1); 1097} 1098 1099// EOF 1100