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