From 07655c24af90145b7dda11bdf0e560b4ead72d92 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Tue, 30 Apr 2024 07:40:08 -0500 Subject: [PATCH] Merge duplicate toBytes functions (#15994) * Merge duplicate toBytes functions Replace other duplicate code and add tests * Allow space between similar to formatSI output * Style fixes * Match previous behavior in Docker app --- LibreNMS/Util/Number.php | 42 ++++++------------- .../Maps/CustomMapDataController.php | 40 +++--------------- includes/polling/applications/docker.inc.php | 4 +- tests/Unit/Util/NumberTest.php | 39 +++++++++++++++++ 4 files changed, 58 insertions(+), 67 deletions(-) create mode 100644 tests/Unit/Util/NumberTest.php diff --git a/LibreNMS/Util/Number.php b/LibreNMS/Util/Number.php index e66ea75686..dc5d2a4220 100644 --- a/LibreNMS/Util/Number.php +++ b/LibreNMS/Util/Number.php @@ -29,14 +29,14 @@ use LibreNMS\Enum\IntegerType; class Number { - public static function formatBase($value, $base = 1000, $round = 2, $sf = 3, $suffix = 'B') + public static function formatBase($value, $base = 1000, $round = 2, $sf = 3, $suffix = 'B'): string { return $base == 1000 ? self::formatSi($value, $round, $sf, $suffix) : self::formatBi($value, $round, $sf, $suffix); } - public static function formatSi($value, $round = 2, $sf = 3, $suffix = 'B') + public static function formatSi($value, $round = 2, $sf = 3, $suffix = 'B'): string { $value = (float) $value; $neg = $value < 0; @@ -67,7 +67,7 @@ class Number return self::cast(number_format(round($value, $round), $sf, '.', '')) . " $ext$suffix"; } - public static function formatBi($value, $round = 2, $sf = 3, $suffix = 'B') + public static function formatBi($value, $round = 2, $sf = 3, $suffix = 'B'): string { $value = (float) $value; $neg = $value < 0; @@ -90,15 +90,19 @@ class Number /** * Convert an Si or Bi formatted value to bytes (or bits) + * Returns NAN for invalidly formatted strings. */ public static function toBytes(string $formatted): int|float { - preg_match('/^([\d.]+)([KMGTPEZY]?)(\w?)\w?$/', $formatted, $matches); - [, $number, $magnitude, $baseIndicator] = $matches; - $base = $baseIndicator == 'i' ? 1024 : 1000; - $exponent = ['K' => 1, 'M' => 2, 'G' => 3, 'T' => 4, 'P' => 5, 'E' => 6, 'Z' => 7, 'Y' => 8]; + if (preg_match('/^([\d.]+)\s?([kKMGTPEZY]?)(i?)([bB]\w*)?$/', $formatted, $matches)) { + [, $number, $magnitude, $baseIndicator] = $matches; + $base = $baseIndicator == 'i' ? 1024 : 1000; + $exponent = ['k' => 1, 'K' => 1, 'M' => 2, 'G' => 3, 'T' => 4, 'P' => 5, 'E' => 6, 'Z' => 7, 'Y' => 8]; - return self::cast($number) * pow($base, $exponent[$magnitude] ?? 0); + return self::cast($number) * pow($base, $exponent[$magnitude] ?? 0); + } + + return NAN; } /** @@ -156,28 +160,6 @@ class Number return round($part / $total * 100, $precision); } - /** - * This converts a memory size containing the unit to bytes. example 1 MiB to 1048576 bytes - */ - public static function convertToBytes(string $from): ?int - { - $units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']; - $number = floatval(substr($from, 0, -3)); - $suffix = substr($from, -3); - - //B or no suffix - if (is_numeric(substr($suffix, 0, 1))) { - return (int) $from; - } - - $exponent = array_flip($units)[$suffix] ?? null; - if ($exponent === null) { - return null; - } - - return (int) ($number * (1024 ** $exponent)); - } - public static function constrainInteger(int $value, IntegerType $integerSize): int { if ($integerSize->isSigned()) { diff --git a/app/Http/Controllers/Maps/CustomMapDataController.php b/app/Http/Controllers/Maps/CustomMapDataController.php index 3522417899..697281db0d 100644 --- a/app/Http/Controllers/Maps/CustomMapDataController.php +++ b/app/Http/Controllers/Maps/CustomMapDataController.php @@ -34,6 +34,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use LibreNMS\Config; +use LibreNMS\Util\Number; use LibreNMS\Util\Url; class CustomMapDataController extends Controller @@ -291,50 +292,19 @@ class CustomMapDataController extends Controller private function rateString(int $rate): string { - if ($rate < 1000) { - return $rate . ' bps'; - } elseif ($rate < 1000000) { - return intval($rate / 1000) . ' kbps'; - } elseif ($rate < 1000000000) { - return intval($rate / 1000000) . ' Mbps'; - } elseif ($rate < 1000000000000) { - return intval($rate / 1000000000) . ' Gbps'; - } elseif ($rate < 1000000000000000) { - return intval($rate / 1000000000000) . ' Tbps'; - } else { - return intval($rate / 1000000000000000) . ' Pbps'; - } + return Number::formatSi($rate, 0, 0, 'bps'); } private function snmpSpeed(string $speeds): int { - // Only succeed if the string startes with a number optionally followed by a unit - if (preg_match('/^(\d+)([kMGTP])?/', $speeds, $matches)) { - $speed = (int) $matches[1]; - if (count($matches) < 3) { - return $speed; - } elseif ($matches[2] == 'k') { - $speed *= 1000; - } elseif ($matches[2] == 'M') { - $speed *= 1000000; - } elseif ($matches[2] == 'G') { - $speed *= 1000000000; - } elseif ($matches[2] == 'T') { - $speed *= 1000000000000; - } elseif ($matches[2] == 'P') { - $speed *= 1000000000000000; - } - - return $speed; - } - - return 0; + // Only succeed if the string starts with a number optionally followed by a unit, return 0 for non-parsable + return (int) Number::toBytes($speeds); } private function speedColour(float $pct): string { // For the maths below, the 5.1 is worked out as 255 / 50 - // (255 being the max colour value and 50 is the max of the $pct calcluation) + // (255 being the max colour value and 50 is the max of the $pct calculation) if ($pct < 0) { // Black if we can't determine the percentage (link down or speed 0) return '#000000'; diff --git a/includes/polling/applications/docker.inc.php b/includes/polling/applications/docker.inc.php index fc692ba8a7..5389cf5a1b 100644 --- a/includes/polling/applications/docker.inc.php +++ b/includes/polling/applications/docker.inc.php @@ -66,8 +66,8 @@ foreach ($docker_data as $data) { 'cpu_usage' => (float) $data['cpu'], 'pids' => $data['pids'], 'mem_perc' => (float) $data['memory']['perc'], - 'mem_used' => Number::convertToBytes($data['memory']['used']), - 'mem_limit' => Number::convertToBytes($data['memory']['limit']), + 'mem_used' => (int) Number::toBytes($data['memory']['used']), + 'mem_limit' => (int) Number::toBytes($data['memory']['limit']), 'uptime' => $data['state']['uptime'], 'size_rw' => $data['size']['size_rw'], 'size_root_fs' => $data['size']['size_root_fs'], diff --git a/tests/Unit/Util/NumberTest.php b/tests/Unit/Util/NumberTest.php new file mode 100644 index 0000000000..46a4fc0414 --- /dev/null +++ b/tests/Unit/Util/NumberTest.php @@ -0,0 +1,39 @@ +assertEquals(2147483648, Number::toBytes('2GiB')); + $this->assertEquals(2147483648, Number::toBytes('2GiBytes')); + $this->assertEquals(2147483648, Number::toBytes('2Gib')); + $this->assertEquals(2000000000, Number::toBytes('2GB')); + $this->assertEquals(2000000000, Number::toBytes('2 Gbps')); // match Number::formatSI() output + $this->assertEquals(2000000000, Number::toBytes('2Gb')); + $this->assertEquals(2000000000, Number::toBytes('2G')); + $this->assertEquals(3145728, Number::toBytes('3MiB')); + $this->assertEquals(3000000, Number::toBytes('3M')); + $this->assertEquals(4398046511104, Number::toBytes('4TiB')); + $this->assertEquals(4000000000000, Number::toBytes('4TB')); + $this->assertEquals(5629499534213120, Number::toBytes('5PiB')); + $this->assertEquals(5000000000000000, Number::toBytes('5PB')); + $this->assertEquals(12000, Number::toBytes('12k')); + $this->assertEquals(12000, Number::toBytes('12Kb')); + $this->assertEquals(12288, Number::toBytes('12Ki')); + $this->assertEquals(12288, Number::toBytes('12KiB')); + $this->assertEquals(12288, Number::toBytes('12kiB')); // not technically valid, but allowed + $this->assertEquals(12, Number::toBytes('12B')); + $this->assertEquals(1234, Number::toBytes('1234')); + $this->assertSame(0, (int) Number::toBytes('garbage')); // NAN cast to int is 0 + $this->assertNan(Number::toBytes('1m')); + $this->assertNan(Number::toBytes('1234a')); + $this->assertNan(Number::toBytes('1234as')); + $this->assertNan(Number::toBytes('1234asd')); + $this->assertNan(Number::toBytes('fluff')); + } +}