From d4502575d3766103bc8dfb3618794a124e31eeda Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 May 2014 16:15:26 +0200 Subject: [PATCH 1/4] Fix filter test location --- .../library/{ => Monitoring}/Filter/Type/StatusFilterTest.php | 2 +- .../php/library/{ => Monitoring}/Filter/UrlViewFilterTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename modules/monitoring/test/php/library/{ => Monitoring}/Filter/Type/StatusFilterTest.php (98%) rename modules/monitoring/test/php/library/{ => Monitoring}/Filter/UrlViewFilterTest.php (99%) diff --git a/modules/monitoring/test/php/library/Filter/Type/StatusFilterTest.php b/modules/monitoring/test/php/library/Monitoring/Filter/Type/StatusFilterTest.php similarity index 98% rename from modules/monitoring/test/php/library/Filter/Type/StatusFilterTest.php rename to modules/monitoring/test/php/library/Monitoring/Filter/Type/StatusFilterTest.php index 994987717..84f76d135 100644 --- a/modules/monitoring/test/php/library/Filter/Type/StatusFilterTest.php +++ b/modules/monitoring/test/php/library/Monitoring/Filter/Type/StatusFilterTest.php @@ -2,7 +2,7 @@ // {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}} -namespace Tests\Icinga\Module\Monitoring\Library\Filter\Type; +namespace Tests\Icinga\Module\Monitoring\Filter\Type; use Icinga\Module\Monitoring\Filter\Type\StatusFilter; use Icinga\Filter\Type\TimeRangeSpecifier; diff --git a/modules/monitoring/test/php/library/Filter/UrlViewFilterTest.php b/modules/monitoring/test/php/library/Monitoring/Filter/UrlViewFilterTest.php similarity index 99% rename from modules/monitoring/test/php/library/Filter/UrlViewFilterTest.php rename to modules/monitoring/test/php/library/Monitoring/Filter/UrlViewFilterTest.php index 278211405..e7afd3911 100644 --- a/modules/monitoring/test/php/library/Filter/UrlViewFilterTest.php +++ b/modules/monitoring/test/php/library/Monitoring/Filter/UrlViewFilterTest.php @@ -2,7 +2,7 @@ // {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}} -namespace Tests\Icinga\Module\Monitoring\Library\Filter; +namespace Tests\Icinga\Module\Monitoring\Filter; use \Mockery; use Icinga\Module\Monitoring\Filter\Type\StatusFilter; From 3d88e720c4909a5037a3cbbbd98d2592511e2d6d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 May 2014 16:15:49 +0200 Subject: [PATCH 2/4] Rewrite perfdata plugin classes refs #5973 --- .../library/Monitoring/Plugin/Perfdata.php | 439 ++++++++++-------- .../library/Monitoring/Plugin/PerfdataSet.php | 146 ++++-- .../Monitoring/Plugin/PerfdataSetTest.php | 101 ++++ .../Monitoring/Plugin/PerfdataTest.php | 352 ++++++++++++++ 4 files changed, 812 insertions(+), 226 deletions(-) create mode 100644 modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataSetTest.php create mode 100644 modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataTest.php diff --git a/modules/monitoring/library/Monitoring/Plugin/Perfdata.php b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php index 4d7cb13db..f01880885 100644 --- a/modules/monitoring/library/Monitoring/Plugin/Perfdata.php +++ b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php @@ -1,223 +1,280 @@ unit) { - case self::BYTES: - return $this->formatBytes() . ' von ' . $this->formatBytes($this->max); - break; - case self::SECONDS: - return $this->formatSeconds(); - break; - case self::PERCENT: - return number_format($this->val, 2, ',', '.') . '%'; - break; - default: - return $this->val; - } - } + /** + * The value + * + * @var float + */ + protected $value; - public function hasMax() - { - return $this->max !== null && $this->max > 0; - } + /** + * The minimum value + * + * @var float + */ + protected $minValue; - public function getPercentage() - { - if ($this->unit === self::PERCENT) { - return $this->val; - } - if ($this->hasMax()) { - return $this->val / $this->max * 100; - } - return false; - } + /** + * The maximum value + * + * @var float + */ + protected $maxValue; - public function getValue() - { - return $this->val; - } + /** + * The WARNING treshold + * + * @var string + */ + protected $warningTreshold; - protected function formatBytes($val = null) - { - $steps = array( - 1 => 'Byte', - 1024 => 'KByte', - 1024 * 1024 => 'MByte', - 1024 * 1024 * 1024 => 'GByte', - 1024 * 1024 * 1024 * 1024 => 'TByte' - ); - return $this->formatSpecial($steps, 1, $val); - } + /** + * The CRITICAL treshold + * + * @var string + */ + protected $criticalTreshold; - protected function formatSeconds() + /** + * Create a new Perfdata object based on the given performance data value + * + * @param string $perfdataValue The value to parse + */ + protected function __construct($perfdataValue) { - $steps = array( - 1 => 'us', - 1000 => 'ms', - 10000000 => 's', - ); - return $this->formatSpecial($steps, 1000000, $this->val); - } + $this->perfdataValue = $perfdataValue; + $this->parse(); - protected function formatSpecial($steps, $multi = 1, $val = null) - { - if ($val === null) { - $val = abs($this->val); - } else { - $val = abs($val); - } - // TODO: Check this, prefix fails if $val is given - if ($this->val < 0) { - $prefix = '-'; - } else { - $prefix = ''; - } - $val *= $multi; - $step = 1; - foreach (array_keys($steps) as $key) { - if ($key > $val * 1) { - break; + if ($this->unit === '%') { + if ($this->minValue === null) { + $this->minValue = 0.0; } - $step = $key; - } - if ($step <= 0) $step = 1; - return $prefix - . number_format($val / $step, 1, ',', '.') - . ' ' - . $steps[$step]; - } - - protected function __construct(& $perfdata) - { - $this->byte_map = array( - 'b' => 1, - 'kb' => 1024, - 'mb' => 1024 * 1024, - 'gb' => 1024 * 1024 * 1024, - 'tb' => 1024 * 1024 * 1024 * 1024 - ); - - // UGLY, fixes floats using comma: - $perfdata = preg_replace('~\,~', '.', $perfdata); - - $parts = preg_split('~;~', $perfdata, 5); - while (count($parts) < 5) { - $parts[] = null; - } - list( - $this->val, - $this->warn, - $this->crit, - $this->min, - $this->max - ) = $parts; - // TODO: check numbers! - - $unit = null; - if (! preg_match('~^(\-?[\d+\.]+(?:E\-?\d+)?)([^\d]+)?$~', $this->val, $m)) { - return $perfdata; - // Numbers with an exponential base will be rendered invalid using the regex above -// throw new \Exception('Got invalid perfdata: ' . $perfdata); - } - $this->val = $m[1]; - if (isset($m[2])) { - $unit = strtolower($m[2]); - } - if ($unit === 'c') { - $this->unit = self::COUNTER; - } - - if ($unit === '%') { - if (! is_numeric($this->min)) { - $this->min = 0; - } - if (! is_numeric($this->max)) { - $this->max = 100; - } - $this->unit = self::PERCENT; - } else { - if (! is_numeric($this->max) && $this->crit > 0) { - $this->max = $this->crit; - } - } - - if (array_key_exists($unit, $this->byte_map)) { - $this->unit = self::BYTES; - $this->val = $this->val * $this->byte_map[$unit]; - $this->min = $this->min * $this->byte_map[$unit]; - $this->max = $this->max * $this->byte_map[$unit]; - } - if ($unit === 's') { - $this->unit = self::SECONDS; - } - if ($unit === 'ms') { - $this->unit = self::SECONDS; - $this->val = $this->val / 1000; - } - if ($unit === '%') { - if (! is_numeric($this->min)) { - $this->min = 0; - } - if (! is_numeric($this->max)) { - $this->max = 100; - } - } else { - if (! is_numeric($this->max) && $this->crit > 0) { - $this->max = $this->crit; + if ($this->maxValue === null) { + $this->maxValue = 100.0; } } } + /** + * Return a new Perfdata object based on the given performance data value + * + * @param string $perfdataValue The value to parse + * + * @return Perfdata + * + * @throws ProgrammingError In case the given performance data value has no content + */ + public static function fromString($perfdataValue) + { + if (empty($perfdataValue)) { + throw new ProgrammingError('Perfdata::fromString expects a string with content'); + } + + return new static($perfdataValue); + } + + /** + * Return whether this performance data value is a number + * + * @return bool True in case it's a number, otherwise False + */ + public function isNumber() + { + return $this->unit === null; + } + + /** + * Return whether this performance data value are seconds + * + * @return bool True in case it's seconds, otherwise False + */ + public function isSeconds() + { + return in_array($this->unit, array('s', 'ms', 'us')); + } + + /** + * Return whether this performance data value is in percentage + * + * @return bool True in case it's in percentage, otherwise False + */ + public function isPercentage() + { + return $this->unit === '%'; + } + + /** + * Return whether this performance data value is in bytes + * + * @return bool True in case it's in bytes, otherwise False + */ + public function isBytes() + { + return in_array($this->unit, array('b', 'kb', 'mb', 'gb', 'tb')); + } + + /** + * Return whether this performance data value is a counter + * + * @return bool True in case it's a counter, otherwise False + */ public function isCounter() { - return $this->unit === self::COUNTER; + return $this->unit === 'c'; } - public static function fromString(& $perfdata) + /** + * Return the value or null if it is unknown (U) + * + * @return null|float + */ + public function getValue() { - $pdat = new Perfdata($perfdata); - return $pdat; + return $this->value; } - protected function normalizeNumber($num) + /** + * Return the value as percentage (0-100) + * + * @return null|float + */ + public function getPercentage() { - return $num; - // Bullshit, still TODO - /* - $dot = strpos($num, '.'); - $comma = strpos($num, ','); - - if ($dot === false) { - // No dot... - if ($comma === false) { - // ...and no comma, it's an integer: - return (int) $num; - } else { - // just a comma - } - } else { - if ($comma === false) { + if ($this->isPercentage()) { + return $this->value; + } + + if ($this->maxValue !== null) { + $minValue = $this->minValue !== null ? $this->minValue : 0; + + if ($this->value > $minValue) { + return (($this->value - $minValue) / ($this->maxValue - $minValue)) * 100; + } + } + } + + /** + * Return this value's warning treshold or null if it is not available + * + * @return null|string + */ + public function getWarningTreshold() + { + return $this->warningTreshold; + } + + /** + * Return this value's critical treshold or null if it is not available + * + * @return null|string + */ + public function getCriticalTreshold() + { + return $this->criticalTreshold; + } + + /** + * Return the minimum value or null if it is not available + * + * @return null|float + */ + public function getMinimumValue() + { + return $this->minValue; + } + + /** + * Return the maximum value or null if it is not available + * + * @return null|float + */ + public function getMaximumValue() + { + return $this->maxValue; + } + + /** + * Parse the current performance data value + * + * @todo Handle optional min/max if UOM == % + */ + protected function parse() + { + $parts = explode(';', $this->perfdataValue); + + $matches = array(); + if (preg_match('@^(\d+(\.\d+)?)([a-zA-Z%]{1,2})$@', $parts[0], $matches)) { + $this->unit = strtolower($matches[3]); + $this->value = self::convert($matches[1], $this->unit); + } else { + $this->value = self::convert($parts[0]); + } + + switch (count($parts)) + { + case 5: + $this->maxValue = self::convert($parts[4], $this->unit); + case 4: + $this->minValue = self::convert($parts[3], $this->unit); + case 3: + // TODO(#6123): Tresholds have the same UOM and need to be converted as well! + $this->criticalTreshold = trim($parts[2]) ? trim($parts[2]) : null; + case 2: + // TODO(#6123): Tresholds have the same UOM and need to be converted as well! + $this->warningTreshold = trim($parts[1]) ? trim($parts[1]) : null; + } + } + + /** + * Return the given value converted to its smallest supported representation + * + * @param string $value The value to convert + * @param string $fromUnit The unit the value currently represents + * + * @return null|float Null in case the value is not a number + */ + protected static function convert($value, $fromUnit = null) + { + if (is_numeric($value)) { + switch ($fromUnit) + { + case 'us': + return $value / pow(10, 6); + case 'ms': + return $value / pow(10, 3); + case 'tb': + return floatval($value) * pow(2, 40); + case 'gb': + return floatval($value) * pow(2, 30); + case 'mb': + return floatval($value) * pow(2, 20); + case 'kb': + return floatval($value) * pow(2, 10); + default: + return (float) $value; + } } - */ } } diff --git a/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php b/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php index 7f6876f1a..8038354a4 100644 --- a/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php +++ b/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php @@ -1,68 +1,144 @@ ptr = & $perfdata; - $this->len = strlen($this->ptr); - while ($this->pos < $this->len) { - $label = $this->readLabel(); - $perf = $this->readUntil(' '); - if (empty($perf)) continue; - $this->perfdata[$label] = Perfdata::fromString($perf); + if (($perfdataStr = trim($perfdataStr)) !== '') { + $this->perfdataStr = $perfdataStr; + $this->parse(); } } - public static function fromString(& $perfdata) + /** + * Return a iterator for this set of performance data + * + * @return ArrayIterator + */ + public function getIterator() { - $pset = new PerfdataSet($perfdata); - return $pset; + return new ArrayIterator($this->asArray()); } - public function getAll() + /** + * Return a new set of performance data + * + * @param string $perfdataStr A space separated list of label/value pairs + * + * @return PerfdataSet + */ + public static function fromString($perfdataStr) { - return $this->perfdata === null ? array() : $this->perfdata; + return new static($perfdataStr); } + /** + * Return this set of performance data as array + * + * @return array + */ + public function asArray() + { + return $this->perfdata; + } + + /** + * Parse the current performance data + */ + protected function parse() + { + while ($this->parserPos < strlen($this->perfdataStr)) { + $label = trim($this->readLabel()); + $value = trim($this->readUntil(' ')); + + if ($label && $value) { + $this->perfdata[$label] = Perfdata::fromString($value); + } + } + } + + /** + * Return the next label found in the performance data + * + * @return string The label found + */ protected function readLabel() { $this->skipSpaces(); - if (in_array($this->ptr[$this->pos], array('"', "'"))) { - $this->pos++; - $label = $this->readUntil($this->ptr[$this->pos - 1]); - $this->pos++; // Skip ' or " - $skip = $this->readUntil('='); - $this->pos++; + if (in_array($this->perfdataStr[$this->parserPos], array('"', "'"))) { + $quoteChar = $this->perfdataStr[$this->parserPos++]; + $label = $this->readUntil('='); + $this->parserPos++; + + if (($closingPos = strpos($label, $quoteChar)) > 0) { + $label = substr($label, 0, $closingPos); + } } else { $label = $this->readUntil('='); - $this->pos++; + $this->parserPos++; } + $this->skipSpaces(); - return trim($label); + return $label; } - protected function readUntil($stop_char) + /** + * Return all characters between the current parser position and the given character + * + * @param string $stopChar The character on which to stop + * + * @return string + */ + protected function readUntil($stopChar) { - $start = $this->pos; - while ($this->pos < $this->len && $this->ptr[$this->pos] !== $stop_char) { - $this->pos++; + $start = $this->parserPos; + while ($this->parserPos < strlen($this->perfdataStr) && $this->perfdataStr[$this->parserPos] !== $stopChar) { + $this->parserPos++; } - return substr($this->ptr, $start, $this->pos - $start); + + return substr($this->perfdataStr, $start, $this->parserPos - $start); } + /** + * Advance the parser position to the next non-whitespace character + */ protected function skipSpaces() { - while ($this->pos < $this->len && $this->ptr[$this->pos] === ' ') { - $this->pos++; + while ($this->parserPos < strlen($this->perfdataStr) && $this->perfdataStr[$this->parserPos] === ' ') { + $this->parserPos++; } } } diff --git a/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataSetTest.php b/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataSetTest.php new file mode 100644 index 000000000..86c0f71a7 --- /dev/null +++ b/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataSetTest.php @@ -0,0 +1,101 @@ +assertArrayHasKey( + 'key1', + $pset->perfdata, + 'PerfdataSet does not correctly parse valid simple labels' + ); + $this->assertArrayHasKey( + 'key2', + $pset->perfdata, + 'PerfdataSet does not correctly parse valid simple labels' + ); + $this->assertArrayHasKey( + 'key3', + $pset->perfdata, + 'PerfdataSet does not correctly parse valid simple labels' + ); + } + + public function testWhetherNonQuotedPerfdataLablesWithSpacesAreProperlyParsed() + { + $pset = PerfdataSetWithPublicData::fromString('key 1=val1 key 1 + 1=val2'); + $this->assertArrayHasKey( + 'key 1', + $pset->perfdata, + 'PerfdataSet does not correctly parse non quoted labels with spaces' + ); + $this->assertArrayHasKey( + 'key 1 + 1', + $pset->perfdata, + 'PerfdataSet does not correctly parse non quoted labels with spaces' + ); + } + + public function testWhetherValidQuotedPerfdataLabelsAreProperlyParsed() + { + $pset = PerfdataSetWithPublicData::fromString('\'key 1\'=val1 "key 2"=val2'); + $this->assertArrayHasKey( + 'key 1', + $pset->perfdata, + 'PerfdataSet does not correctly parse valid quoted labels' + ); + $this->assertArrayHasKey( + 'key 2', + $pset->perfdata, + 'PerfdataSet does not correctly parse valid quoted labels' + ); + } + + public function testWhetherInvalidQuotedPerfdataLabelsAreProperlyParsed() + { + $pset = PerfdataSetWithPublicData::fromString('\'key 1=val1 key 2"=val2'); + $this->assertArrayHasKey( + 'key 1', + $pset->perfdata, + 'PerfdataSet does not correctly parse invalid quoted labels' + ); + $this->assertArrayHasKey( + 'key 2"', + $pset->perfdata, + 'PerfdataSet does not correctly parse invalid quoted labels' + ); + } + + /** + * @depends testWhetherValidSimplePerfdataLabelsAreProperlyParsed + */ + public function testWhetherAPerfdataSetIsIterable() + { + $pset = PerfdataSet::fromString('key=value'); + foreach ($pset as $label => $value) { + $this->assertEquals('key', $label); + return; + } + + $this->fail('PerfdataSet objects cannot be iterated'); + } + + public function testWhetherPerfdataSetsCanBeInitializedWithEmptyStrings() + { + $pset = PerfdataSetWithPublicData::fromString(''); + $this->assertEmpty($pset->perfdata, 'PerfdataSet::fromString does not accept emtpy strings'); + } +} diff --git a/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataTest.php b/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataTest.php new file mode 100644 index 000000000..9bb32ffd8 --- /dev/null +++ b/modules/monitoring/test/php/library/Monitoring/Plugin/PerfdataTest.php @@ -0,0 +1,352 @@ +assertEquals( + 1337.0, + Perfdata::fromString('1337')->getValue(), + 'Perfdata::getValue does not return correct values' + ); + $this->assertEquals( + 1337.0, + Perfdata::fromString('1337;;;;')->getValue(), + 'Perfdata::getValue does not return correct values' + ); + } + + public function testWhetherDecimalValuesAreCorrectlyParsed() + { + $this->assertEquals( + 1337.5, + Perfdata::fromString('1337.5')->getValue(), + 'Perfdata objects do not parse decimal values correctly' + ); + $this->assertEquals( + 1337.5, + Perfdata::fromString('1337.5B')->getValue(), + 'Perfdata objects do not parse decimal values correctly' + ); + } + + public function testWhetherGetValueReturnsNullForInvalidOrUnknownValues() + { + $this->assertNull( + Perfdata::fromString('U')->getValue(), + 'Perfdata::getValue does not return null for unknown values' + ); + $this->assertNull( + Perfdata::fromString('i am not a value')->getValue(), + 'Perfdata::getValue does not return null for invalid values' + ); + } + + public function testWhetherGetWarningTresholdReturnsCorrectValues() + { + $this->assertEquals( + '10', + Perfdata::fromString('1;10')->getWarningTreshold(), + 'Perfdata::getWarningTreshold does not return correct values' + ); + $this->assertEquals( + '10:', + Perfdata::fromString('1;10:')->getWarningTreshold(), + 'Perfdata::getWarningTreshold does not return correct values' + ); + $this->assertEquals( + '~:10', + Perfdata::fromString('1;~:10')->getWarningTreshold(), + 'Perfdata::getWarningTreshold does not return correct values' + ); + $this->assertEquals( + '10:20', + Perfdata::fromString('1;10:20')->getWarningTreshold(), + 'Perfdata::getWarningTreshold does not return correct values' + ); + $this->assertEquals( + '@10:20', + Perfdata::fromString('1;@10:20')->getWarningTreshold(), + 'Perfdata::getWarningTreshold does not return correct values' + ); + } + + public function testWhetherGetCriticalTresholdReturnsCorrectValues() + { + $this->assertEquals( + '10', + Perfdata::fromString('1;;10')->getCriticalTreshold(), + 'Perfdata::getCriticalTreshold does not return correct values' + ); + $this->assertEquals( + '10:', + Perfdata::fromString('1;;10:')->getCriticalTreshold(), + 'Perfdata::getCriticalTreshold does not return correct values' + ); + $this->assertEquals( + '~:10', + Perfdata::fromString('1;;~:10')->getCriticalTreshold(), + 'Perfdata::getCriticalTreshold does not return correct values' + ); + $this->assertEquals( + '10:20', + Perfdata::fromString('1;;10:20')->getCriticalTreshold(), + 'Perfdata::getCriticalTreshold does not return correct values' + ); + $this->assertEquals( + '@10:20', + Perfdata::fromString('1;;@10:20')->getCriticalTreshold(), + 'Perfdata::getCriticalTreshold does not return correct values' + ); + } + + public function testWhetherGetMinimumValueReturnsCorrectValues() + { + $this->assertEquals( + 1337.0, + Perfdata::fromString('1;;;1337')->getMinimumValue(), + 'Perfdata::getMinimumValue does not return correct values' + ); + $this->assertEquals( + 1337.5, + Perfdata::fromString('1;;;1337.5')->getMinimumValue(), + 'Perfdata::getMinimumValue does not return correct values' + ); + } + + public function testWhetherGetMaximumValueReturnsCorrectValues() + { + $this->assertEquals( + 1337.0, + Perfdata::fromString('1;;;;1337')->getMaximumValue(), + 'Perfdata::getMaximumValue does not return correct values' + ); + $this->assertEquals( + 1337.5, + Perfdata::fromString('1;;;;1337.5')->getMaximumValue(), + 'Perfdata::getMaximumValue does not return correct values' + ); + } + + public function testWhetherMissingValuesAreReturnedAsNull() + { + $perfdata = Perfdata::fromString('1;;3;5'); + $this->assertNull( + $perfdata->getWarningTreshold(), + 'Perfdata objects do not return null for missing warning tresholds' + ); + $this->assertNull( + $perfdata->getMaximumValue(), + 'Perfdata objects do not return null for missing maximum values' + ); + } + + /** + * @depends testWhetherGetValueReturnsValidValues + */ + public function testWhetherValuesAreIdentifiedAsNumber() + { + $this->assertTrue( + Perfdata::fromString('666')->isNumber(), + 'Perfdata objects do not identify ordinary digits as number' + ); + } + + /** + * @depends testWhetherGetValueReturnsValidValues + */ + public function testWhetherValuesAreIdentifiedAsSeconds() + { + $this->assertTrue( + Perfdata::fromString('666s')->isSeconds(), + 'Perfdata objects do not identify seconds as seconds' + ); + $this->assertTrue( + Perfdata::fromString('666us')->isSeconds(), + 'Perfdata objects do not identify microseconds as seconds' + ); + $this->assertTrue( + Perfdata::fromString('666ms')->isSeconds(), + 'Perfdata objects do not identify milliseconds as seconds' + ); + } + + /** + * @depends testWhetherGetValueReturnsValidValues + */ + public function testWhetherValuesAreIdentifiedAsPercentage() + { + $this->assertTrue( + Perfdata::fromString('66%')->isPercentage(), + 'Perfdata objects do not identify percentages as percentages' + ); + } + + /** + * @depends testWhetherValuesAreIdentifiedAsPercentage + */ + public function testWhetherMinAndMaxAreNotRequiredIfUnitIsInPercent() + { + $perfdata = Perfdata::fromString('1%'); + $this->assertEquals( + 0.0, + $perfdata->getMinimumValue(), + 'Perfdata objects do not set minimum value to 0 if UOM is %' + ); + $this->assertEquals( + 100.0, + $perfdata->getMaximumValue(), + 'Perfdata objects do not set maximum value to 100 if UOM is %' + ); + } + + /** + * @depends testWhetherGetValueReturnsValidValues + */ + public function testWhetherValuesAreIdentifiedAsBytes() + { + $this->assertTrue( + Perfdata::fromString('66666B')->isBytes(), + 'Perfdata objects do not identify bytes as bytes' + ); + $this->assertTrue( + Perfdata::fromString('6666KB')->isBytes(), + 'Perfdata objects do not identify kilobytes as bytes' + ); + $this->assertTrue( + Perfdata::fromString('666MB')->isBytes(), + 'Perfdata objects do not identify megabytes as bytes' + ); + $this->assertTrue( + Perfdata::fromString('66GB')->isBytes(), + 'Perfdata objects do not identify gigabytes as bytes' + ); + $this->assertTrue( + Perfdata::fromString('6TB')->isBytes(), + 'Perfdata objects do not identify terabytes as bytes' + ); + } + + /** + * @depends testWhetherGetValueReturnsValidValues + */ + public function testWhetherValuesAreIdentifiedAsCounter() + { + $this->assertTrue( + Perfdata::fromString('123c')->isCounter(), + 'Perfdata objects do not identify counters as counters' + ); + } + + /** + * @depends testWhetherValuesAreIdentifiedAsSeconds + */ + public function testWhetherMicroSecondsAreCorrectlyConvertedToSeconds() + { + $this->assertEquals( + 666 / pow(10, 6), + Perfdata::fromString('666us')->getValue(), + 'Perfdata objects do not correctly convert microseconds to seconds' + ); + } + + /** + * @depends testWhetherValuesAreIdentifiedAsSeconds + */ + public function testWhetherMilliSecondsAreCorrectlyConvertedToSeconds() + { + $this->assertEquals( + 666 / pow(10, 3), + Perfdata::fromString('666ms')->getValue(), + 'Perfdata objects do not correctly convert microseconds to seconds' + ); + } + + /** + * @depends testWhetherValuesAreIdentifiedAsPercentage + */ + public function testWhetherPercentagesAreHandledCorrectly() + { + $this->assertEquals( + 66.0, + Perfdata::fromString('66%')->getPercentage(), + 'Perfdata objects do not correctly handle native percentages' + ); + $this->assertEquals( + 50.0, + Perfdata::fromString('0;;;-250;250')->getPercentage(), + 'Perfdata objects do not correctly convert suitable values to percentages' + ); + $this->assertNull( + Perfdata::fromString('50')->getPercentage(), + 'Perfdata objects do return a percentage though their unit is not % and no maximum is given' + ); + $this->assertNull( + Perfdata::fromString('25;;;50;100')->getPercentage(), + 'Perfdata objects do return a percentage though their value is lower than it\'s allowed minimum' + ); + } + + /** + * @depends testWhetherValuesAreIdentifiedAsBytes + */ + public function testWhetherKiloBytesAreCorrectlyConvertedToBytes() + { + $this->assertEquals( + 6666.0 * pow(2, 10), + Perfdata::fromString('6666KB')->getValue(), + 'Perfdata objects do not corretly convert kilobytes to bytes' + ); + } + + /** + * @depends testWhetherValuesAreIdentifiedAsBytes + */ + public function testWhetherMegaBytesAreCorrectlyConvertedToBytes() + { + $this->assertEquals( + 666.0 * pow(2, 20), + Perfdata::fromString('666MB')->getValue(), + 'Perfdata objects do not corretly convert megabytes to bytes' + ); + } + + /** + * @depends testWhetherValuesAreIdentifiedAsBytes + */ + public function testWhetherGigaBytesAreCorrectlyConvertedToBytes() + { + $this->assertEquals( + 66.0 * pow(2, 30), + Perfdata::fromString('66GB')->getValue(), + 'Perfdata objects do not corretly convert gigabytes to bytes' + ); + } + + /** + * @depends testWhetherValuesAreIdentifiedAsBytes + */ + public function testWhetherTeraBytesAreCorrectlyConvertedToBytes() + { + $this->assertEquals( + 6.0 * pow(2, 40), + Perfdata::fromString('6TB')->getValue(), + 'Perfdata objects do not corretly convert terabytes to bytes' + ); + } +} From 0627d954acd6a72dd1aaa715bd4a644130503db1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 May 2014 16:16:45 +0200 Subject: [PATCH 3/4] Fix usages of Icinga\Module\Monitoring\Plugin\* refs #5973 --- library/Icinga/Util/Format.php | 19 ++- .../application/clicommands/ListCommand.php | 4 +- .../application/views/helpers/Perfdata.php | 130 ++++++++---------- 3 files changed, 81 insertions(+), 72 deletions(-) diff --git a/library/Icinga/Util/Format.php b/library/Icinga/Util/Format.php index 8c55cbafa..a7c0ee0e3 100644 --- a/library/Icinga/Util/Format.php +++ b/library/Icinga/Util/Format.php @@ -50,6 +50,9 @@ class Format ); protected static $byteBase = array(1024, 1000); + protected static $secondPrefix = array('s', 'ms', 'µs', 'ns', 'ps', 'fs', 'as'); + protected static $secondBase = 1000; + public static function getInstance() { if (self::$instance === null) { @@ -76,6 +79,20 @@ class Format ); } + public static function seconds($value) + { + if ($value < 60) { + return self::formatForUnits($value, self::$secondPrefix, self::$secondBase); + } elseif ($value < 3600) { + return sprintf('0.2f m', $value / 60); + } elseif ($value < 86400) { + return sprintf('0.2f h', $value / 3600); + } + + // TODO: Do we need weeks, months and years? + return sprintf('0.2f d', $value / 86400); + } + public static function duration($duration) { if ($duration === null || $duration === false) { @@ -148,7 +165,7 @@ class Format '%s%0.2f %s', $sign, $result, - $units[$pow] + $units[abs($pow)] ); } diff --git a/modules/monitoring/application/clicommands/ListCommand.php b/modules/monitoring/application/clicommands/ListCommand.php index a07601871..4ddf2e781 100644 --- a/modules/monitoring/application/clicommands/ListCommand.php +++ b/modules/monitoring/application/clicommands/ListCommand.php @@ -263,9 +263,9 @@ class ListCommand extends Command try { $pset = PerfdataSet::fromString($row->service_perfdata); $perfs = array(); - foreach ($pset->getAll() as $perfName => $p) { + foreach ($pset as $perfName => $p) { if ($percent = $p->getPercentage()) { - if ($percent < 0 || $percent > 300) { + if ($percent < 0 || $percent > 100) { continue; } $perfs[] = ' ' diff --git a/modules/monitoring/application/views/helpers/Perfdata.php b/modules/monitoring/application/views/helpers/Perfdata.php index 13997de18..49e88cae6 100644 --- a/modules/monitoring/application/views/helpers/Perfdata.php +++ b/modules/monitoring/application/views/helpers/Perfdata.php @@ -1,90 +1,82 @@ getAll(); - $perfdata = preg_replace_callback('~\'([^\']+)\'~', function($match) { return str_replace(' ', '\'', $match[1]); }, $perfdata); - $parts = preg_split('~\s+~', $perfdata, -1, PREG_SPLIT_NO_EMPTY); - - $table = array(); $result = ''; - if ($compact === true) { - $compact = 5; - } - if ($compact && count($parts) > $compact) { - $parts = array_slice($parts, 0, $compact); - } - foreach ($parts as $part) { - if (strpos($part, '=') === false) continue; - list($name, $vals) = preg_split('~=~', $part, 2); - $name = str_replace("'", ' ', $name); - $parts = preg_split('~;~', $vals, 5); - while (count($parts) < 5) $parts[] = null; - list($val, $warn, $crit, $min, $max) = $parts; - - $unit = ''; - if (preg_match('~^([\d+\.]+)([^\d]+)$~', $val, $m)) { - $unit = $m[2]; - $val = $m[1]; - } else { + $table = array(); + $pset = array_slice(PerfdataSet::fromString($perfdataStr)->asArray(), 0, ($compact ? 5 : null)); + foreach ($pset as $label => $perfdata) { + if (!$perfdata->isPercentage() && $perfdata->getMaximumValue() === null) { continue; } - if ($unit == 'c') continue; // Counter pie graphs are not useful - if ($compact && $val < 0.0001) continue; - if ($unit == '%') { - if (! $min ) $min = 0; - if (! $max) $max = 100; - } else { - if (! $max && $crit > 0) $max = $crit; - //return ''; - } - if (! $max) continue; - $green = 0; - $orange = 0; - $red = 0; - $gray = $max - $val; - if ($val < $warn) $green = $val; - elseif ($val < $crit) $orange = $val; - else $red = $val; - $inlinePie = new InlinePie(array($green, $orange, $red, $gray)); + + $pieChart = new InlinePie($this->calculatePieChartData($perfdata)); if ($compact) { - $inlinePie->setTitle(htmlspecialchars($name) . ': ' . htmlspecialchars($ps[$name]->getFormattedValue())); - $inlinePie->setStyle('float: right;'); - $result .= $inlinePie->render(); + $pieChart->setTitle( + htmlspecialchars($label) . ': ' . htmlspecialchars($this->formatPerfdataValue($perfdata)) + ); + $pieChart->setStyle('float: right;'); + $result .= $pieChart->render(); } else { - $inlinePie->setTitle(htmlspecialchars($name)); - $inlinePie->setStyle('float: left; margin: 0.2em 0.5em 0.2em 0;'); - $table[] = '' . $inlinePie->render() - . htmlspecialchars($name) + $pieChart->setTitle(htmlspecialchars($label)); + $pieChart->setStyle('float: left; margin: 0.2em 0.5em 0.2em 0;'); + $table[] = '' . $pieChart->render() + . htmlspecialchars($label) . '' - . htmlspecialchars($ps[$name]->getFormattedValue()) . + . htmlspecialchars($this->formatPerfdataValue($perfdata)) . ''; } } - if ($result == '' && ! $compact) { - $result = $perfdata; + + // TODO: What if we have both? And should we trust sprintf-style placeholders in perfdata titles? + if (empty($table)) { + return $compact ? $result : $perfdataStr; + } else { + return '' . implode("\n", $table) . '
'; } - if (! empty($table)) { - // TODO: What if we have both? And should we trust sprintf-style placeholders in perfdata titles? - $result = '' . implode("\n", $table) . '
'; + } + + protected function calculatePieChartData(Perfdata $perfdata) + { + $rawValue = $perfdata->getValue(); + $minValue = $perfdata->getMinimumValue() !== null ? $perfdata->getMinimumValue() : 0; + $maxValue = $perfdata->getMaximumValue(); + $usedValue = ($rawValue - $minValue); + $unusedValue = ($maxValue - $minValue) - $usedValue; + + $gray = $unusedValue; + $green = $orange = $red = 0; + // TODO(#6122): Add proper treshold parsing. + if ($perfdata->getCriticalTreshold() && $perfdata->getValue() > $perfdata->getCriticalTreshold()) { + $red = $usedValue; + } elseif ($perfdata->getWarningTreshold() && $perfdata->getValue() > $perfdata->getWarningTreshold()) { + $orange = $usedValue; + } else { + $green = $usedValue; } - return $result; + return array($green, $orange, $red, $gray); + } + + protected function formatPerfdataValue(Perfdata $perfdata) + { + if ($perfdata->isBytes()) { + return Format::bytes($perfdata->getValue()); + } elseif ($perfdata->isSeconds()) { + return Format::seconds($perfdata->getValue()); + } elseif ($perfdata->isPercentage()) { + return $perfdata->getValue() . '%'; + } + + return $perfdata->getValue(); } } From c711e3405dea7385a2943e1e829fdf7e17ab6bad Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 May 2014 16:17:21 +0200 Subject: [PATCH 4/4] Fix cli default log configuration and js-loader debug message --- library/Icinga/Application/Cli.php | 2 +- public/js/icinga/loader.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Application/Cli.php b/library/Icinga/Application/Cli.php index 5ccd2ed1e..72b2b43f8 100644 --- a/library/Icinga/Application/Cli.php +++ b/library/Icinga/Application/Cli.php @@ -79,7 +79,7 @@ class Cli extends ApplicationBootstrap array( 'enable' => true, 'level' => Logger::$INFO, - 'type' => 'stream', + 'type' => 'file', 'target' => 'php://stderr' ) ) diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index f525c2437..5d2679d55 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -508,7 +508,7 @@ // Icinga.debug(req.getResponseHeader('X-Icinga-Redirect')); } else { if (errorThrown === 'abort') { - this.icinga.logger.info( + this.icinga.logger.debug( 'Request to ' + url + ' has been aborted for ', req.$target );