1<?php 2/** 3 * Observium 4 * 5 * This file is part of Observium. 6 * 7 * @package observium 8 * @subpackage entities 9 * @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2019 Observium Limited 10 * 11 * 12 */ 13 14function discover_counter_definition($device, $mib, $entry) 15{ 16 17 echo($entry['oid']. ' ['); 18 19 // Check that types listed in skip_if_valid_exist have already been found 20 if (discovery_check_if_type_exist($GLOBALS['valid'], $entry, 'counter')) { echo '!]'; return; } 21 22 // Check array requirements list 23 if (discovery_check_requires_pre($device, $entry, 'counter')) { echo '!]'; return; } 24 25 // Fetch table or Oids 26 $table_oids = array('oid', 'oid_descr', 'oid_scale', 'oid_unit', 'oid_class', 27 'oid_limit_low', 'oid_limit_low_warn', 'oid_limit_high_warn', 'oid_limit_high', 28 'oid_limit_nominal', 'oid_limit_delta_warn', 'oid_limit_delta', 'oid_limit_scale', 29 'oid_extra', 'oid_entPhysicalIndex'); 30 $counter_array = discover_fetch_oids($device, $mib, $entry, $table_oids); 31 32 if (empty($entry['oid_num'])) 33 { 34 // Use snmptranslate if oid_num not set 35 $entry['oid_num'] = snmp_translate($entry['oid'], $mib); 36 } else { 37 $entry['oid_num'] = rtrim($entry['oid_num'], '.'); 38 } 39 40 if (!isset($entry['scale'])) 41 { 42 $scale = 1; 43 $options['scale'] = 1; 44 } else { 45 $scale = $entry['scale']; 46 $options['scale'] = $entry['scale']; 47 } 48 49 $counters = array(); // Reset per-class counters for each MIB 50 51 $counters_count = count($counter_array); 52 foreach ($counter_array as $index => $counter) 53 { 54 $options = array(); 55 56 if (isset($entry['class'])) 57 { 58 // Hardcoded counter class 59 $class = $entry['class']; 60 } else { 61 // If no 'class' hardcoded, see if we can get class from the map_class via oid_class 62 if (isset($entry['oid_class']) && isset($counter[$entry['oid_class']])) 63 { 64 if (isset($entry['map_class'][$counter[$entry['oid_class']]])) 65 { 66 $class = $entry['map_class'][$counter[$entry['oid_class']]]; 67 } else { 68 print_debug('Value from oid_class (' . $counter[$entry['oid_class']] . ') does not match any configured values in map_class!'); 69 continue; // Break foreach. Next counter! 70 } 71 } else { 72 print_debug('No class hardcoded, but no oid_class (' . $entry['oid_class'] . ') found in table walk!'); 73 continue; // Break foreach. Next counter! 74 } 75 } 76 77 $dot_index = '.' . $index; 78 $oid_num = $entry['oid_num'] . $dot_index; 79 80 // echo PHP_EOL; print_vars($entry); echo PHP_EOL; print_vars($counter); echo PHP_EOL; print_vars($descr); echo PHP_EOL; 81 82 // %i% can be used in description, a counter is kept per counter class 83 $counters[$class]++; 84 85 // Generate specific keys used during rewrites 86 87 $counter['class'] = nicecase($class); // Class in descr 88 $counter['index'] = $index; // Index in descr 89 $counter['i'] = $counters[$class]; // i++ counter in descr (per counter class) 90 91 // Check array requirements list 92 if (discovery_check_requires($device, $entry, $counter, 'counter')) { continue; } 93 94 $value = snmp_fix_numeric($counter[$entry['oid']]); 95 if (!is_numeric($value)) 96 { 97 print_debug("Excluded by current value ($value) is not numeric."); 98 continue; 99 } 100 101 // Check for min/max values, when counters report invalid data as counter does not exist 102 if (isset($entry['min']) && $value <= $entry['min']) 103 { 104 print_debug("Excluded by current value ($value) is equals or below min (".$entry['min'].")."); 105 continue; 106 } 107 else if (isset($entry['max']) && $value >= $entry['max']) 108 { 109 print_debug("Excluded by current value ($value) is equals or above max (".$entry['max'].")."); 110 continue; 111 } 112 else if (isset($entry['invalid']) && in_array($value, (array)$entry['invalid'])) 113 { 114 print_debug("Excluded by current value ($value) in invalid range [".implode(', ', (array)$entry['invalid'])."]."); 115 continue; 116 } 117 118 // Check limits oids if set 119 foreach (array('limit_low', 'limit_low_warn', 'limit_high_warn', 'limit_high') as $limit) 120 { 121 $oid_limit = 'oid_' . $limit; 122 if (isset($entry[$oid_limit])) 123 { 124 if (isset($counter[$entry[$oid_limit]])) { $options[$limit] = $counter[$entry[$oid_limit]]; } // Named oid, exist in table 125 else { $options[$limit] = snmp_get_oid($device, $entry[$oid_limit] . $dot_index, $mib); } // Numeric oid 126 $options[$limit] = snmp_fix_numeric($options[$limit]); 127 // Scale limit 128 if (isset($entry['limit_scale']) && is_numeric($entry['limit_scale']) && $entry['limit_scale'] != 0) 129 { 130 $options[$limit] *= $entry['limit_scale']; 131 } 132 } 133 elseif (isset($entry[$limit]) && is_numeric($entry[$limit])) 134 { 135 $options[$limit] = $entry[$limit]; // Limit from definition 136 } 137 } 138 139 // Limits based on nominal +- delta oids (see TPT-HEALTH-MIB) 140 if (isset($entry['oid_limit_nominal']) && (isset($entry['oid_limit_delta']) || isset($entry['oid_limit_delta_warn']))) 141 { 142 $oid_limit = 'oid_limit_nominal'; 143 if (isset($counter[$entry[$oid_limit]])) { $limit_nominal = $counter[$entry[$oid_limit]]; } // Named oid, exist in table 144 else { $limit_nominal = snmp_get_oid($device, $entry[$oid_limit] . $dot_index, $mib); } // Numeric oid 145 146 if (is_numeric($limit_nominal) && isset($entry['oid_limit_delta_warn'])) 147 { 148 $oid_limit = 'oid_limit_delta_warn'; 149 if (isset($counter[$entry[$oid_limit]])) { $limit_delta_warn = $counter[$entry[$oid_limit]]; } // Named oid, exist in table 150 else { $limit_delta_warn = snmp_get_oid($device, $entry[$oid_limit] . $dot_index, $mib); } // Numeric oid 151 $options['limit_low_warn'] = $limit_nominal - $limit_delta_warn; //$entry['limit_scale']; 152 $options['limit_high_warn'] = $limit_nominal + $limit_delta_warn; //$entry['limit_scale']; 153 if (isset($entry['limit_scale']) && is_numeric($entry['limit_scale']) && $entry['limit_scale'] != 0) 154 { 155 $options['limit_low_warn'] *= $entry['limit_scale']; 156 $options['limit_high_warn'] *= $entry['limit_scale']; 157 } 158 } 159 if (is_numeric($limit_nominal) && isset($entry['oid_limit_delta'])) 160 { 161 $oid_limit = 'oid_limit_delta'; 162 if (isset($counter[$entry[$oid_limit]])) { $limit_delta = $counter[$entry[$oid_limit]]; } // Named oid, exist in table 163 else { $limit_delta = snmp_get_oid($device, $entry[$oid_limit] . $dot_index, $mib); } // Numeric oid 164 $options['limit_low'] = $limit_nominal - $limit_delta; 165 $options['limit_high'] = $limit_nominal + $limit_delta; 166 if (isset($entry['limit_scale']) && is_numeric($entry['limit_scale']) && $entry['limit_scale'] != 0) 167 { 168 $options['limit_low'] *= $entry['limit_scale']; 169 $options['limit_high'] *= $entry['limit_scale']; 170 } 171 } 172 } 173 // Limit by 174 if (isset($entry['limit_by'])) 175 { 176 $options['limit_by'] = $entry['limit_by']; 177 } 178 179 // Unit 180 if (isset($entry['unit'])) { $options['counter_unit'] = $entry['unit']; } 181 if (isset($entry['oid_unit']) && isset($counter[$entry['oid_unit']])) 182 { 183 // Translate unit from specific Oid 184 $unit = $counter[$entry['oid_unit']]; 185 if (isset($entry['map_unit'][$unit])) 186 { 187 $options['counter_unit'] = $entry['map_unit'][$unit]; 188 } 189 } 190 191 // Rule-based entity linking. 192 if ($measured = entity_measured_match_definition($device, $entry, $counter, 'counter')) 193 { 194 $options = array_merge($options, $measured); 195 $counter = array_merge($counter, $measured); // append to $counter for %descr% tags, ie %port_label% 196 } 197 // End rule-based entity linking 198 elseif (isset($entry['entPhysicalIndex'])) 199 { 200 // Just set physical index 201 $options['entPhysicalIndex'] = array_tag_replace($counter, $entry['entPhysicalIndex']); 202 } 203 204 // Generate Description 205 $descr = entity_descr_definition('counter', $entry, $counter, $counters_count); 206 207 // Rename old (converted) RRDs to definition format 208 if (isset($entry['rename_rrd'])) 209 { 210 $options['rename_rrd'] = $entry['rename_rrd']; 211 } 212 elseif (isset($entry['rename_rrd_full'])) 213 { 214 $options['rename_rrd_full'] = $entry['rename_rrd_full']; 215 } 216 217 discover_counter($device, $class, $mib, $entry['oid'], $oid_num, $index, $descr, $scale, $value, $options); 218 } 219 220 echo '] '; 221 222} 223 224// TESTME needs unit testing 225/** 226 * Discover a new counter on a device 227 * 228 * This function adds a status counter to a device, if it does not already exist. 229 * Data on the counter is updated if it has changed, and an event is logged with regards to the changes. 230 * 231 * Status counters are handed off to discover_status(). 232 * Current counter values are rectified in case they are broken (added spaces, etc). 233 * 234 * @param array $device Device array counter is being discovered on 235 * @param string $class Class of counter (voltage, temperature, etc.) 236 * @param string $mib SNMP MIB name 237 * @param string $object SNMP Named Oid of counter (without index) 238 * @param string $oid SNMP Numeric Oid of counter (without index) 239 * @param string $index SNMP index of counter 240 * @param string $counter_descr Description of counter 241 * @param int $scale Scale of counter (0.1 for 1:10 scale, 10 for 10:1 scale, etc) 242 * @param string $value Current counter value 243 * @param array $options Options (counter_unit, limit_auto, limit*, poller_type, scale, measured_*) 244 * @return bool 245 */ 246function discover_counter($device, $class, $mib, $object, $oid, $index, $counter_descr, $scale = 1, $value = NULL, $options = array()) 247{ 248 global $config; 249 250 //echo 'MIB:'; print_vars($mib); 251 252 $poller_type = (isset($options['poller_type']) ? $options['poller_type'] : 'snmp'); 253 // Class for counter is free text string (not limited by known classes) 254 $class = strlen($class) ? strtolower($class) : 'counter'; 255 // Use MIB & Object or Numeric Oid? 256 $use_mib_object = $mib && $object; 257 258 $counter_deleted = 0; 259 260 // Init main 261 $param_main = array('oid' => 'counter_oid', 'counter_descr' => 'counter_descr', 'scale' => 'counter_multiplier', 262 'counter_deleted' => 'counter_deleted', 'mib' => 'counter_mib', 'object' => 'counter_object'); 263 264 // Init numeric values 265 if (!is_numeric($scale) || $scale == 0) { $scale = 1; } 266 267 268 // Skip discovery counter if value not numeric or null (default) 269 if (strlen($value)) 270 { 271 // Some retarded devices report data with spaces and commas 272 // STRING: " 20,4" 273 $value = snmp_fix_numeric($value); 274 } 275 276 if (is_numeric($value)) 277 { 278 $value = scale_value($value, $scale); 279 // $value *= $scale; // Scale before unit conversion 280 $value = value_to_si($value, $options['counter_unit'], $class); // Convert if not SI unit 281 } else { 282 print_debug("Counter skipped by not numeric value: '$value', '$counter_descr'"); 283 if (strlen($value)) 284 { 285 print_debug("Perhaps this is named status, use discover_status() instead."); 286 } 287 return FALSE; 288 } 289 290 $param_limits = array('limit_high' => 'counter_limit', 'limit_high_warn' => 'counter_limit_warn', 291 'limit_low' => 'counter_limit_low', 'limit_low_warn' => 'counter_limit_low_warn'); 292 foreach ($param_limits as $key => $column) 293 { 294 // Set limits vars and unit convert if required 295 $$key = (is_numeric($options[$key]) ? value_to_si($options[$key], $options['counter_unit'], $class) : NULL); 296 } 297 // Set by which param use limits 298 switch (strtolower($options['limit_by'])) 299 { 300 case 's': 301 case 'sec': 302 case 'second': 303 $limit_by = 'sec'; 304 break; 305 case 'm': 306 case 'min': 307 case 'minute': 308 $limit_by = 'min'; 309 break; 310 case 'h': 311 case 'hour': 312 $limit_by = 'hour'; 313 break; 314 case 'val': 315 case 'value': 316 $limit_by = 'value'; 317 break; 318 default: 319 //case 'poll': 320 //case '5min': 321 $limit_by = '5min'; 322 break; 323 } 324 // Auto calculate high/low limits if not passed (for counter must be explicit passed) 325 //$limit_auto = !isset($options['limit_auto']) || (bool)$options['limit_auto']; 326 $limit_auto = isset($options['limit_auto']) && (bool)$options['limit_auto']; 327 328 // Init optional 329 $param_opt = array('entPhysicalIndex', 'entPhysicalClass', 'entPhysicalIndex_measured', 'measured_class', 'measured_entity', 'counter_unit'); 330 foreach ($param_opt as $key) 331 { 332 $$key = ($options[$key] ? $options[$key] : NULL); 333 } 334 335 print_debug("Discover counter: [class: $class, device: ".$device['hostname'].", oid: $oid, index: $index, descr: $counter_descr, scale: $scale, limits: ($limit_low, $limit_low_warn, $limit_high_warn, $limit_high), CURRENT: $value, $entPhysicalIndex, $entPhysicalClass"); 336 337 // Check counter ignore filters 338 foreach ($config['ignore_counter'] as $bi) { if (strcasecmp($bi, $counter_descr) == 0) { print_debug("Skipped by equals: $bi, $counter_descr "); return FALSE; } } 339 foreach ($config['ignore_counter_string'] as $bi) { if (stripos($counter_descr, $bi) !== FALSE) { print_debug("Skipped by strpos: $bi, $counter_descr "); return FALSE; } } 340 foreach ($config['ignore_counter_regexp'] as $bi) { if (preg_match($bi, $counter_descr) > 0) { print_debug("Skipped by regexp: $bi, $counter_descr "); return FALSE; } } 341 342 if (!is_null($limit_low_warn) && !is_null($limit_high_warn) && ($limit_low_warn > $limit_high_warn)) 343 { 344 // Fix high/low thresholds (i.e. on negative numbers) 345 list($limit_high_warn, $limit_low_warn) = array($limit_low_warn, $limit_high_warn); 346 } 347 print_debug_vars($limit_high); 348 print_debug_vars($limit_high_warn); 349 print_debug_vars($limit_low_warn); 350 print_debug_vars($limit_low); 351 352 if ($use_mib_object) 353 { 354 $where = '`device_id` = ? AND `counter_class` = ? AND `counter_mib` = ? AND `counter_object` = ? AND `counter_index` = ? AND `poller_type`= ?'; 355 $params = [$device['device_id'], $class, $mib, $object, $index, $poller_type]; 356 } else { 357 // Rare case, when MIB and Object unknown 358 $where = '`device_id` = ? AND `counter_class` = ? AND `counter_oid` = ? AND `counter_index` = ? AND `poller_type`= ?'; 359 $params = [$device['device_id'], $class, $oid, $index, $poller_type]; 360 } 361 362 if (!dbExist('counters', $where, $params)) 363 { 364 if (!$limit_high) { $limit_high = sensor_limit_high($class, $value, $limit_auto); } 365 if (!$limit_low) { $limit_low = sensor_limit_low($class, $value, $limit_auto); } 366 367 if (!is_null($limit_low) && !is_null($limit_high) && ($limit_low > $limit_high)) 368 { 369 // Fix high/low thresholds (i.e. on negative numbers) 370 list($limit_high, $limit_low) = array($limit_low, $limit_high); 371 print_debug("High/low limits swapped."); 372 } 373 374 $counter_insert = array('poller_type' => $poller_type, 'counter_class' => $class, 'device_id' => $device['device_id'], 375 'counter_index' => $index); 376 377 foreach ($param_main as $key => $column) 378 { 379 $counter_insert[$column] = $$key; 380 } 381 382 foreach ($param_limits as $key => $column) 383 { 384 // Convert strings/numbers to (float) or to array('NULL') 385 $$key = is_numeric($$key) ? (float)$$key : array('NULL'); 386 $counter_insert[$column] = $$key; 387 } 388 $counter_insert['counter_limit_by'] = $limit_by; 389 390 foreach ($param_opt as $key) 391 { 392 if (is_null($$key)) { $$key = array('NULL'); } 393 $counter_insert[$key] = $$key; 394 } 395 396 $counter_insert['counter_value'] = $value; 397 $counter_insert['counter_polled'] = time(); 398 399 $counter_id = dbInsert($counter_insert, 'counters'); 400 401 print_debug("( $counter_id inserted )"); 402 echo('+'); 403 404 log_event("Counter added: $class $mib::$object.$index $counter_descr", $device, 'counter', $counter_id); 405 } else { 406 $counter_entry = dbFetchRow("SELECT * FROM `counters` WHERE " . $where, $params); 407 $counter_id = $counter_entry['counter_id']; 408 409 // Limits 410 if (!$counter_entry['counter_custom_limit']) 411 { 412 if (!is_numeric($limit_high)) 413 { 414 if ($counter_entry['counter_limit'] !== '') 415 { 416 // Calculate a reasonable limit 417 $limit_high = sensor_limit_high($class, $value, $limit_auto); 418 } else { 419 // Use existing limit. (this is wrong! --mike) 420 $limit_high = $counter_entry['counter_limit']; 421 } 422 } 423 424 if (!is_numeric($limit_low)) 425 { 426 if ($counter_entry['counter_limit_low'] !== '') 427 { 428 // Calculate a reasonable limit 429 $limit_low = sensor_limit_low($class, $value, $limit_auto); 430 } else { 431 // Use existing limit. (this is wrong! --mike) 432 $limit_low = $counter_entry['counter_limit_low']; 433 } 434 } 435 436 // Fix high/low thresholds (i.e. on negative numbers) 437 if (!is_null($limit_low) && !is_null($limit_high) && ($limit_low > $limit_high)) 438 { 439 list($limit_high, $limit_low) = array($limit_low, $limit_high); 440 print_debug("High/low limits swapped."); 441 } 442 443 // Update limits 444 $update = array(); 445 $update_msg = array(); 446 $debug_msg = 'Current counter value: "'.$value.'", scale: "'.$scale.'"'.PHP_EOL; 447 foreach ($param_limits as $key => $column) 448 { 449 // $key - param name, $$key - param value, $column - column name in DB for $key 450 $debug_msg .= ' '.$key.': "'.$counter_entry[$column].'" -> "'.$$key.'"'.PHP_EOL; 451 //convert strings/numbers to identical type (float) or to array('NULL') for correct comparison 452 $$key = is_numeric($$key) ? (float)$$key : array('NULL'); 453 $counter_entry[$column] = is_numeric($counter_entry[$column]) ? (float)$counter_entry[$column] : array('NULL'); 454 if (float_cmp($$key, $counter_entry[$column], 0.01) !== 0) // FIXME, need compare autogenerated and hard passed limits by different ways 455 { 456 $update[$column] = $$key; 457 $update_msg[] = $key.' -> "'.(is_array($$key) ? 'NULL' : $$key).'"'; 458 } 459 } 460 if ($counter_entry['counter_limit_by'] != $limit_by) 461 { 462 $update['counter_limit_by'] = $limit_by; 463 $update_msg[] = 'limit_by -> "'.$limit_by.'"'; 464 } 465 if (count($update)) 466 { 467 echo("L"); 468 print_debug($debug_msg); 469 log_event('Counter updated (limits): '.implode(', ', $update_msg), $device, 'counter', $counter_entry['counter_id']); 470 $updated = dbUpdate($update, 'counters', '`counter_id` = ?', array($counter_entry['counter_id'])); 471 } 472 } 473 474 $update = array(); 475 foreach ($param_main as $key => $column) 476 { 477 if (float_cmp($$key, $counter_entry[$column]) !== 0) 478 { 479 $update[$column] = $$key; 480 } 481 } 482 483 foreach ($param_opt as $key) 484 { 485 if ($$key != $counter_entry[$key]) 486 { 487 $update[$key] = $$key; 488 } 489 } 490 491 if (count($update)) 492 { 493 $updated = dbUpdate($update, 'counters', '`counter_id` = ?', array($counter_entry['counter_id'])); 494 echo('U'); 495 log_event("Counter updated: $class $mib::$object.$index $counter_descr", $device, 'counter', $counter_entry['counter_id']); 496 } else { 497 echo('.'); 498 } 499 } 500 501 // Rename old (converted) RRDs to definition format 502 // Allow with changing class or without 503 if (isset($options['rename_rrd']) || isset($options['rename_rrd_full'])) 504 { 505 $rrd_tags = array('index' => $index, 'mib' => $mib, 'object' => $object, 'oid' => $object); 506 if (isset($options['rename_rrd'])) 507 { 508 $options['rename_rrd'] = array_tag_replace($rrd_tags, $options['rename_rrd']); 509 $old_rrd = 'counter-' . $class . '-' . $options['rename_rrd']; 510 } 511 else if (isset($options['rename_rrd_full'])) 512 { 513 $options['rename_rrd_full'] = array_tag_replace($rrd_tags, $options['rename_rrd_full']); 514 $old_rrd = 'counter-' . $options['rename_rrd_full']; 515 } 516 $new_rrd = 'counter-'.$class.'-'.$type.'-'.$index; 517 rename_rrd($device, $old_rrd, $new_rrd); 518 } 519 520 $GLOBALS['valid']['counter'][$mib][$object][$index] = 1; 521 522 return $counter_id; 523 //return TRUE; 524} 525 526// Poll a counter 527function poll_counter($device, &$oid_cache) 528{ 529 global $config, $agent_sensors, $ipmi_counters, $graphs, $table_rows; 530 531 $sql = "SELECT * FROM `counters`"; 532 $sql .= " WHERE `device_id` = ? AND `counter_deleted` = ?"; 533 $sql .= ' ORDER BY `counter_oid`'; // This fix polling some OIDs (when not ordered) 534 535 //print_vars($GLOBALS['cache']['entity_attribs']); 536 foreach (dbFetchRows($sql, array($device['device_id'], '0')) as $counter_db) 537 { 538 $counter_poll = array(); 539 $class = $counter_db['counter_class']; 540 // Counter not have type attribute, this need for compat with agent or ipmi 541 $type = $counter_db['counter_mib'] . '-' . $counter_db['counter_object']; 542 543 //print_cli_heading("Counter: ".$counter_db['counter_descr'], 3); 544 545 if (OBS_DEBUG) 546 { 547 echo("Checking (" . $counter_db['poller_type'] . ") $class " . $counter_db['counter_descr'] . " "); 548 print_debug_vars($counter_db, 1); 549 } 550 551 if ($counter_db['poller_type'] == "snmp") 552 { 553 $counter_db['counter_oid'] = '.' . ltrim($counter_db['counter_oid'], '.'); // Fix first dot in oid for caching 554 555 // Take value from $oid_cache if we have it, else snmp_get it 556 if (isset($oid_cache[$counter_db['counter_oid']])) 557 { 558 $oid_cache[$counter_db['counter_oid']] = snmp_fix_numeric($oid_cache[$counter_db['counter_oid']]); 559 } 560 if (is_numeric($oid_cache[$counter_db['counter_oid']])) 561 { 562 print_debug("value taken from oid_cache"); 563 $counter_poll['counter_value'] = $oid_cache[$counter_db['counter_oid']]; 564 } else { 565 // Get by numeric oid 566 $counter_poll['counter_value'] = snmp_get_oid($device, $counter_db['counter_oid'], 'SNMPv2-MIB'); 567 $counter_poll['counter_value'] = snmp_fix_numeric($counter_poll['counter_value']); 568 } 569 $unit = $counter_db['counter_unit']; 570 } 571 elseif ($counter_db['poller_type'] == "agent") 572 { 573 if (isset($agent_sensors)) 574 { 575 $counter_poll['counter_value'] = $agent_sensors[$class][$type][$counter_db['counter_index']]['current']; 576 } else { 577 print_warning("No agent counter data available."); 578 continue; 579 } 580 } 581 elseif ($counter_db['poller_type'] == "ipmi") 582 { 583 if (isset($ipmi_counters)) 584 { 585 $counter_poll['counter_value'] = snmp_fix_numeric($ipmi_counters[$class][$type][$counter_db['counter_index']]['current']); 586 $unit = $ipmi_counters[$class][$type][$counter_db['counter_index']]['unit']; 587 } else { 588 print_warning("No IPMI counter data available."); 589 continue; 590 } 591 } else { 592 print_warning("Unknown counter poller type."); 593 continue; 594 } 595 596 $counter_polled_time = time(); // Store polled time for current counter 597 $counter_polled_period = $counter_polled_time - $counter_db['counter_polled']; 598 599 if (isset($counter_db['counter_multiplier']) && $counter_db['counter_multiplier'] != 0) 600 { 601 //$counter_poll['counter_value'] *= $counter_db['counter_multiplier']; 602 $counter_poll['counter_value'] = scale_value($counter_poll['counter_value'], $counter_db['counter_multiplier']); 603 } 604 605 // Unit conversion to SI (if required) 606 $counter_poll['counter_value'] = value_to_si($counter_poll['counter_value'], $counter_db['counter_unit'], $class); 607 608 // Rate /s 609 $value_diff = int_sub($counter_poll['counter_value'], $counter_db['counter_value']); 610 $counter_poll['counter_rate'] = $value_diff / $counter_polled_period; 611 $counter_poll['counter_rate_min'] = $value_diff / ($counter_polled_period / 60); 612 $counter_poll['counter_rate_5min'] = $value_diff / ($counter_polled_period / 300); // This is mostly same as count per poll period 613 print_debug('Rate /sec: (' . $counter_poll['counter_value'] . ' - ' . $counter_db['counter_value'] . '(='.$value_diff.')) / ' . $counter_polled_period . ' = ' . $counter_poll['counter_rate']); 614 print_debug('Rate /min: ' . $counter_poll['counter_rate_min']); 615 print_debug('Rate /5min: ' . $counter_poll['counter_rate_5min']); 616 // Rate /h (more complex since counters grow up very rarely 617 $counter_poll['counter_history'] = $counter_db['counter_history'] != '' ? json_decode($counter_db['counter_history'], TRUE) : []; 618 // Now find first polled time around 3600s (1h) 619 foreach ($counter_poll['counter_history'] as $polled_time => $value) 620 { 621 $diff = $counter_polled_time - $polled_time; 622 if ($diff < (3600 + ($config['rrd']['step'] / 2))) // 3600 + 150 (possible change step in future) 623 { 624 if ($diff < 3300) 625 { 626 // If not have full hour history, use approximate to hour rate 627 $period = $diff / 3600; // Period in hours (around 1) 628 $counter_poll['counter_rate_hour'] = int_sub($counter_poll['counter_value'], $value) / $period; 629 print_debug("Hour rate by approximate: ".$counter_poll['counter_value']." - $value / $period"); 630 } else { 631 $counter_poll['counter_rate_hour'] = int_sub($counter_poll['counter_value'], $value); // Keep this value as integer, since we keep in history only 1 hour 632 print_debug("Hour rate by history: ".$counter_poll['counter_value']." - $value"); 633 } 634 break; 635 } else { 636 // Clear old entries 637 unset($counter_poll['counter_history'][$polled_time]); 638 } 639 } 640 // Just if initially not exist history 641 if (!isset($counter_poll['counter_rate_hour'])) 642 { 643 $counter_poll['counter_rate_hour'] = $counter_poll['counter_rate_5min'] * 12; 644 print_debug("Hour rate initially: ".$counter_poll['counter_rate_5min']." * 12"); 645 } 646 print_debug('Rate /hour: ' . $counter_poll['counter_rate_hour']); 647 648 // Append last value to history and json it 649 $counter_poll['counter_history'][$counter_polled_time] = $counter_poll['counter_value']; 650 print_debug_vars($counter_poll['counter_history']); 651 $counter_poll['counter_history'] = json_encode($counter_poll['counter_history']); 652 653 print_debug_vars($counter_poll, 1); 654 655 //print_cli_data("Value", $counter_poll['counter_value'] . "$unit ", 3); 656 657 // FIXME this block and the other block below it are kinda retarded. They should be merged and simplified. 658 659 if ($counter_db['counter_disable']) 660 { 661 $counter_poll['counter_event'] = 'ignore'; 662 $counter_poll['counter_status'] = 'Counter disabled.'; 663 } else { 664 // Select param for calculate limit events 665 switch ($counter_db['counter_limit_by']) 666 { 667 case 'sec': 668 $limit_by = 'counter_rate'; 669 $limit_unit = "$unit/sec"; 670 break; 671 case 'min': 672 $limit_by = 'counter_rate_min'; 673 $limit_unit = "$unit/min"; 674 break; 675 case '5min': 676 $limit_by = 'counter_rate_5min'; 677 $limit_unit = "$unit/5min"; 678 break; 679 case 'hour': 680 $limit_by = 'counter_rate_hour'; 681 $limit_unit = "$unit/hour"; 682 break; 683 case 'value': 684 $limit_by = 'counter_value'; 685 $limit_unit = $unit; 686 break; 687 } 688 689 $counter_poll['counter_event'] = check_thresholds($counter_db['counter_limit_low'], $counter_db['counter_limit_low_warn'], 690 $counter_db['counter_limit_warn'], $counter_db['counter_limit'], 691 $counter_poll[$limit_by]); 692 if ($counter_poll['counter_event'] == 'alert') 693 { 694 $counter_poll['counter_status'] = 'Counter critical thresholds exceeded.'; 695 } 696 elseif ($counter_poll['counter_event'] == 'warning') 697 { 698 $counter_poll['counter_status'] = 'Counter warning thresholds exceeded.'; 699 } else { 700 $counter_poll['counter_status'] = ''; 701 } 702 703 // Reset Alert if counter ignored 704 if ($counter_poll['counter_event'] != 'ok' && $counter_db['counter_ignore']) 705 { 706 $counter_poll['counter_event'] = 'ignore'; 707 $counter_poll['counter_status'] = 'Counter thresholds exceeded, but ignored.'; 708 } 709 } 710 711 // If last change never set, use current time 712 if (empty($counter_db['counter_last_change'])) 713 { 714 $counter_db['counter_last_change'] = $counter_polled_time; 715 } 716 717 if ($counter_poll['counter_event'] != $counter_db['counter_event']) 718 { 719 // Counter event changed, log and set counter_last_change 720 $counter_poll['counter_last_change'] = $counter_polled_time; 721 722 if ($counter_db['counter_event'] == 'ignore') 723 { 724 print_message("[%yCounter Ignored%n]", 'color'); 725 } 726 elseif (is_numeric($counter_db['counter_limit_low']) && 727 $counter_db[$limit_by] >= $counter_db['counter_limit_low'] && 728 $counter_poll[$limit_by] < $counter_db['counter_limit_low']) 729 { 730 // If old value greater than low limit and new value less than low limit 731 $msg = ucfirst($class) . " Alarm: " . $device['hostname'] . " " . $counter_db['counter_descr'] . " is under threshold: " . $counter_poll[$limit_by] . "$limit_unit (< " . $counter_db['counter_limit_low'] . "$limit_unit)"; 732 log_event(ucfirst($class) . ' ' . $counter_db['counter_descr'] . " under threshold: " . $counter_poll[$limit_by] . " $limit_unit (< " . $counter_db['counter_limit_low'] . " $limit_unit)", $device, 'counter', $counter_db['counter_id'], 'warning'); 733 } 734 elseif (is_numeric($counter_db['counter_limit']) && 735 $counter_db[$limit_by] <= $counter_db['counter_limit'] && 736 $counter_poll[$limit_by] > $counter_db['counter_limit']) 737 { 738 // If old value less than high limit and new value greater than high limit 739 $msg = ucfirst($class) . " Alarm: " . $device['hostname'] . " " . $counter_db['counter_descr'] . " is over threshold: " . $counter_poll[$limit_by] . "$limit_unit (> " . $counter_db['counter_limit'] . "$limit_unit)"; 740 log_event(ucfirst($class) . ' ' . $counter_db['counter_descr'] . " above threshold: " . $counter_poll[$limit_by] . " $limit_unit (> " . $counter_db['counter_limit'] . " $limit_unit)", $device, 'counter', $counter_db['counter_id'], 'warning'); 741 } 742 } else { 743 // If counter not changed, leave old last_change 744 $counter_poll['counter_last_change'] = $counter_db['counter_last_change']; 745 } 746 747 // Send statistics array via AMQP/JSON if AMQP is enabled globally and for the ports module 748 if ($config['amqp']['enable'] == TRUE && $config['amqp']['modules']['counters']) 749 { 750 $json_data = array('value' => $counter_poll['counter_value']); 751 messagebus_send(array('attribs' => array('t' => $counter_polled_time, 752 'device' => $device['hostname'], 753 'device_id' => $device['device_id'], 754 'e_type' => 'counter', 755 'e_class' => $counter_db['counter_class'], 756 'e_type' => $type, 757 'e_index' => $counter_db['counter_index']), 758 'data' => $json_data)); 759 } 760 761 // Add table row 762 763 $type = $counter_db['counter_mib'] . '::' . $counter_db['counter_object'] . '.' . $counter_db['counter_index']; 764 $format = strval($config['counter_types'][$counter_db['counter_class']]['format']); 765 $table_rows[] = array($counter_db['counter_descr'], 766 $counter_db['counter_class'], 767 $type, 768 $counter_poll['counter_value'] . $unit, 769 format_value($counter_poll['counter_rate'], $format) . '/s | ' . format_value($counter_poll['counter_rate_min'], $format) . '/min | ' . 770 format_value($counter_poll['counter_rate_5min'], $format) . '/5min | ' . format_value($counter_poll['counter_rate_hour'], $format) . '/h', 771 $counter_poll['counter_event'], 772 format_unixtime($counter_poll['counter_last_change']), 773 $counter_db['poller_type']); 774 775 // Update StatsD/Carbon 776 if ($config['statsd']['enable'] == TRUE) 777 { 778 StatsD::gauge(str_replace(".", "_", $device['hostname']) . '.' . 'counter' . '.' . $counter_db['counter_class'] . '.' . $type . '.' . $counter_db['counter_index'], $counter_poll['counter_value']); 779 } 780 781 // Update RRD (Counter store both rate(counter) and value(sensor) 782 //$rrd_file = get_counter_rrd($device, $counter_db); 783 //rrdtool_create($device, $rrd_file, "DS:counter:GAUGE:600:-20000:U"); 784 //rrdtool_update($device, $rrd_file, "N:" . $counter_poll['counter_value']); 785 $ds = ['sensor' => $counter_poll['counter_value'], 786 'counter' => round($counter_poll['counter_value'], 0)]; // RRD COUNTER must be integer 787 rrdtool_update_ng($device, 'counter', $ds, $counter_db['counter_id']); 788 789 // Enable graph 790 $graphs[$counter_db['counter_class']] = TRUE; 791 792 // Check alerts 793 $metrics = array(); 794 795 $metrics['counter_value'] = $counter_poll['counter_value']; 796 $metrics['counter_rate'] = $counter_poll['counter_rate']; 797 $metrics['counter_rate_min'] = $counter_poll['counter_rate_min']; 798 $metrics['counter_rate_5min'] = $counter_poll['counter_rate_5min']; 799 $metrics['counter_rate_hour'] = $counter_poll['counter_rate_hour']; 800 $metrics['counter_event'] = $counter_poll['counter_event']; 801 $metrics['counter_event_uptime'] = $counter_polled_time - $counter_poll['counter_last_change']; 802 $metrics['counter_status'] = $counter_poll['counter_status']; 803 804 check_entity('counter', $counter_db, $metrics); 805 806 // Add to MultiUpdate SQL State 807 808 $GLOBALS['multi_update_db'][] = array( 809 'counter_id' => $counter_db['counter_id'], // UNIQUE index 810 //'device_id' => $counter_db['device_id'], // Required 811 'counter_value' => $counter_poll['counter_value'], 812 'counter_rate' => $counter_poll['counter_rate'], 813 'counter_rate_5min' => $counter_poll['counter_rate_5min'], 814 'counter_rate_hour' => $counter_poll['counter_rate_hour'], 815 'counter_history' => $counter_poll['counter_history'], 816 'counter_event' => $counter_poll['counter_event'], 817 'counter_status' => $counter_poll['counter_status'], 818 'counter_last_change' => $counter_poll['counter_last_change'], 819 'counter_polled' => $counter_polled_time); 820 } 821} 822 823// DOCME needs phpdoc block 824// TESTME needs unit testing 825function check_valid_counter($device, $poller_type = 'snmp') 826{ 827 $valid = &$GLOBALS['valid']['counter']; 828 829 $entries = dbFetchRows("SELECT * FROM `counters` WHERE `device_id` = ? AND `poller_type` = ? AND `counter_deleted` = '0'", array($device['device_id'], $poller_type)); 830 831 foreach ($entries as $entry) 832 { 833 $index = $entry['counter_index']; 834 $object = $entry['counter_object']; 835 $mib = strlen($entry['counter_mib']) ? $entry['counter_mib'] : '__'; 836 if (!$valid[$mib][$object][$index] || 837 $valid[$mib][$object][$index] > 1) // Duplicate entry 838 { 839 echo("-"); 840 print_debug("Status deleted: $mib::$object.$index"); 841 //dbDelete('counter', "`counter_id` = ?", array($entry['counter_id'])); 842 843 dbUpdate(array('counter_deleted' => '1'), 'counters', '`counter_id` = ?', array($entry['counter_id'])); 844 845 foreach (get_entity_attribs('counter', $entry['counter_id']) as $attrib_type => $value) 846 { 847 del_entity_attrib('counter', $entry['counter_id'], $attrib_type); 848 } 849 log_event("Counter deleted: ".$entry['counter_class']." ". $entry['counter_index']." ".$entry['counter_descr'], $device, 'counter', $entry['counter_id']); 850 } else { 851 // Increase counter as hint for find duplicates 852 $valid[$mib][$object][$index]++; 853 } 854 } 855} 856 857// DOCME needs phpdoc block 858// TESTME needs unit testing 859function get_counter_rrd($device, $counter) 860{ 861 global $config; 862 863 # For IPMI, counters tend to change order, and there is no index, so we prefer to use the description as key here. 864 if ($config['os'][$device['os']]['counter_descr'] || ($counter['poller_type'] != "snmp" && $counter['poller_type'] != '')) 865 { 866 $rrd_file = "counter-".$counter['counter_class']."-".$counter['poller_type']."-".$counter['counter_descr'] . ".rrd"; 867 } 868 elseif ($counter['counter_mib'] == '' || $counter['counter_object'] == '') 869 { 870 // Seems as numeric Oid polling (not known MIB & Oid) 871 // counter_oid is full numeric oid with index 872 $rrd_file = "counter-".$counter['counter_class']."-".$counter['counter_oid'] . ".rrd"; 873 } else { 874 $rrd_file = "counter-".$counter['counter_class']."-".$counter['counter_mib']."-".$counter['counter_object']."-".$counter['counter_index'] . ".rrd"; 875 } 876 877 return($rrd_file); 878} 879 880// EOF 881