From e8f28ebef0cf732d126e76d13a27e4bca505328b Mon Sep 17 00:00:00 2001 From: Dana Van Aken Date: Tue, 8 Oct 2019 06:09:45 -0400 Subject: [PATCH] Added another function that converts bytes/ms to human-readable values without loss of resolution. --- server/website/tests/test_utils.py | 267 +++++++++++++++++++--- server/website/website/parser/base.py | 54 ++++- server/website/website/parser/myrocks.py | 37 +-- server/website/website/parser/oracle.py | 7 + server/website/website/parser/postgres.py | 39 +--- server/website/website/utils.py | 42 ++++ 6 files changed, 337 insertions(+), 109 deletions(-) diff --git a/server/website/tests/test_utils.py b/server/website/tests/test_utils.py index e0103f4..9cb88b7 100644 --- a/server/website/tests/test_utils.py +++ b/server/website/tests/test_utils.py @@ -5,8 +5,11 @@ # import string +from collections import OrderedDict + import numpy as np from django.test import TestCase + from website.utils import JSONUtil, MediaUtil, DataUtil, ConversionUtil, LabelUtil, TaskUtil from website.types import LabelStyleType, VarType from website.models import Result, DBMSCatalog @@ -250,40 +253,246 @@ class DataUtilTest(TestCase): class ConversionUtilTest(TestCase): - def test_get_raw_size(self): - # Bytes - In Bytes - byte_test_convert = ['1PB', '2TB', '3GB', '4MB', '5kB', '6B'] - byte_ans = [1024**5, 2 * 1024**4, 3 * 1024**3, 4 * 1024**2, 5 * 1024**1, 6] - for i, byte_test in enumerate(byte_test_convert): - byte_conversion = ConversionUtil.get_raw_size( - byte_test, system=ConversionUtil.DEFAULT_BYTES_SYSTEM) - self.assertEqual(byte_conversion, byte_ans[i]) - # Time - In Milliseconds - day_test_convert = ['1000ms', '1s', '10min', '20h', '1d'] - day_ans = [1000, 1000, 600000, 72000000, 86400000] - for i, day_test in enumerate(day_test_convert): - day_conversion = ConversionUtil.get_raw_size( - day_test, system=ConversionUtil.DEFAULT_TIME_SYSTEM) - self.assertEqual(day_conversion, day_ans[i]) + def setUp(self): + self.bytes_map = OrderedDict( + [(suffix, factor) for factor, suffix in ConversionUtil.DEFAULT_BYTES_SYSTEM]) + self.ms_map = OrderedDict( + [(suffix, factor) for factor, suffix in ConversionUtil.DEFAULT_TIME_SYSTEM]) + + self.from_hr_bytes_simple = ['1PB', '2TB', '3GB', '4MB', '1024MB', '5kB', '6B'] + self.as_bytes_simple = [1024**5, 2 * 1024**4, 3 * 1024**3, 4 * 1024**2, 1024**3, + 5 * 1024, 6] + self.bytes_to_hr_simple = ['1PB', '2TB', '3GB', '4MB', '1GB', '5kB', '6B'] + self.assertListEqual( + [len(l) for l in (self.from_hr_bytes_simple, self.as_bytes_simple, + self.bytes_to_hr_simple)], [len(self.from_hr_bytes_simple)] * 3) + + self.from_hr_times_simple = ['500ms', '1000ms', '1s', '55s', '10min', '20h', '1d'] + self.as_ms_simple = [500, 1000, 1000, 55000, 600000, 72000000, 86400000] + self.ms_to_hr_simple = ['500ms', '1s', '1s', '55s', '10min', '20h', '1d'] + self.assertListEqual( + [len(l) for l in (self.from_hr_times_simple, self.as_ms_simple, + self.ms_to_hr_simple)], [len(self.from_hr_times_simple)] * 3) + + extra_bytes = [3 * factor for factor in self.bytes_map.values()] + neb = len(extra_bytes) + + self.test_bytes_lengths = [] + + self.from_hr_bytes = [ + '1PB', '43PB', '1023PB', '1024PB', '1025PB', + '1TB', '43TB', '1023TB', '1024TB', '1025TB', + '1GB', '43GB', '1023GB', '1024GB', '1025GB', + '1MB', '43MB', '1023MB', '1024MB', '1025MB', + '1kB', '43kB', '1023kB', '1024kB', '1025kB', + '1B', '43B', '1023B', '1024B', '1025B', + '46170898432MB', '45088768MB', '44032MB', + '44032kB', '44032B', '43kB', + ] + ['43{}'.format(suffix) for suffix in list(self.bytes_map.keys())[1:]] + \ + ['{}B'.format(sum(extra_bytes[i:])) for i in range(neb)] + self.test_bytes_lengths.append(len(self.from_hr_bytes)) + + self.as_bytes = [ + 1024**5, 43 * 1024**5, 1023 * 1024**5, 1024 * 1024**5, 1025 * 1024**5, + 1024**4, 43 * 1024**4, 1023 * 1024**4, 1024 * 1024**4, 1025 * 1024**4, + 1024**3, 43 * 1024**3, 1023 * 1024**3, 1024 * 1024**3, 1025 * 1024**3, + 1024**2, 43 * 1024**2, 1023 * 1024**2, 1024 * 1024**2, 1025 * 1024**2, + 1024**1, 43 * 1024**1, 1023 * 1024**1, 1024 * 1024**1, 1025 * 1024**1, + 1024**0, 43 * 1024**0, 1023 * 1024**0, 1024 * 1024**0, 1025 * 1024**0, + 46170898432 * 1024**2, 45088768 * 1024**2, 44032 * 1024**2, + 44032 * 1024, 44032, 43 * 1024, + ] + [43 * 1024**i for i in range(len(self.bytes_map) - 1)[::-1]] + \ + [sum(extra_bytes[i:]) for i in range(neb)] + self.test_bytes_lengths.append(len(self.as_bytes)) + + self.bytes_to_hr = [ + '1PB', '43PB', '1023PB', '1024PB', '1025PB', + '1TB', '43TB', '1023TB', '1PB', '1PB', + '1GB', '43GB', '1023GB', '1TB', '1TB', + '1MB', '43MB', '1023MB', '1GB', '1GB', + '1kB', '43kB', '1023kB', '1MB', '1MB', + '1B', '43B', '1023B', '1kB', '1kB', + '43PB', '43TB', '43GB', + '43MB', '43kB', '43kB', + ] + ['43{}'.format(suffix) for suffix in list(self.bytes_map.keys())[1:]] + \ + ['3{}'.format(suffix) for suffix in self.bytes_map.keys()] + self.test_bytes_lengths.append(len(self.bytes_to_hr)) + + self.bytes_to_hr2 = [ + '1PB', '43PB', '1023PB', '1024PB', '1025PB', + '1TB', '43TB', '1023TB', '1PB', '1025TB', + '1GB', '43GB', '1023GB', '1TB', '1025GB', + '1MB', '43MB', '1023MB', '1GB', '1025MB', + '1kB', '43kB', '1023kB', '1MB', '1025kB', + '1B', '43B', '1023B', '1kB', '1025B', + '43PB', '43TB', '43GB', + '43MB', '43kB', '43kB', + ] + ['43{}'.format(suffix) for suffix in list(self.bytes_map.keys())[1:]] + \ + ['{}kB'.format(sum(extra_bytes[i:]) // 1024) for i in range(neb - 1)] + \ + ['{}B'.format(extra_bytes[-1])] + self.test_bytes_lengths.append(len(self.bytes_to_hr2)) + + self.min_bytes_suffixes = (25 * ['kB']) + (11 * ['B']) + \ + list(self.bytes_map.keys())[:-1] + \ + ((neb - 1) * ['kB']) + ['B'] + self.test_bytes_lengths.append(len(self.min_bytes_suffixes)) + + self.assertListEqual(self.test_bytes_lengths, + [self.test_bytes_lengths[0]] * len(self.test_bytes_lengths)) + + self.test_ms_lengths = [] + + extra_ms = [3 * factor for factor in self.ms_map.values()] + nem = len(extra_ms) + + self.from_hr_times = [ + '1d', '5d', '6d', '7d', '8d', + '1h', '5h', '23h', '24h', '25h', + '1min', '5min', '59min', '60min', '61min', + '1s', '5s', '59s', '60s', '61s', + '1ms', '5ms', '999ms', '1000ms', '1001ms', + '7200min', '300min', '300s', '5000ms', '5s', + ] + ['5{}'.format(suffix) for suffix in list(self.ms_map.keys())[1:]] + \ + ['{}ms'.format(sum(extra_ms[i:])) for i in range(nem)] + self.test_ms_lengths.append(len(self.from_hr_times)) + + self.as_ms = [v * 86400000 for v in (1, 5, 6, 7, 8)] + \ + [v * 3600000 for v in (1, 5, 23, 24, 25)] + \ + [v * 60000 for v in (1, 5, 59, 60, 61)] + \ + [v * 1000 for v in (1, 5, 59, 60, 61)] + \ + [v * 1 for v in (1, 5, 999, 1000, 1001)] + \ + [432000000, 18000000, 300000, 5000, 5000] + \ + [5 * v for v in (3600000, 60000, 1000, 1)] + \ + [sum(extra_ms[i:]) for i in range(nem)] + self.test_ms_lengths.append(len(self.as_ms)) + + self.ms_to_hr = [ + '1d', '5d', '6d', '7d', '8d', + '1h', '5h', '23h', '1d', '1d', + '1min', '5min', '59min', '1h', '1h', + '1s', '5s', '59s', '1min', '1min', + '1ms', '5ms', '999ms', '1s', '1s', + '5d', '5h', '5min', '5s', '5s', + ] + ['5{}'.format(suffix) for suffix in list(self.ms_map.keys())[1:]] + \ + ['3{}'.format(suffix) for suffix in self.ms_map.keys()] + self.test_ms_lengths.append(len(self.ms_to_hr)) + + self.ms_to_hr2 = [ + '1d', '5d', '6d', '7d', '8d', + '1h', '5h', '23h', '1d', '25h', + '1min', '5min', '59min', '1h', '61min', + '1s', '5s', '59s', '1min', '61s', + '1ms', '5ms', '999ms', '1s', '1001ms', + '5d', '5h', '5min', '5s', '5s', + ] + ['5{}'.format(suffix) for suffix in list(self.ms_map.keys())[1:]] + \ + ['{}s'.format(sum(extra_ms[i:]) // 1000) for i in range(nem - 1)] + \ + ['{}ms'.format(extra_ms[-1])] + self.test_ms_lengths.append(len(self.ms_to_hr2)) + + self.min_time_suffixes = (20 * ['s']) + (10 * ['ms']) + list(self.ms_map.keys())[:-1] + \ + ((nem - 1) * ['s']) + ['ms'] + self.test_ms_lengths.append(len(self.min_time_suffixes)) + + self.assertListEqual(self.test_ms_lengths, + [self.test_ms_lengths[0]] * len(self.test_ms_lengths)) + + def test_default_system(self): + expected_byte_units = ('B', 'kB', 'MB', 'GB', 'TB', 'PB') + expected_byte_values = (1, 1024, 1024**2, 1024**3, 1024**4, 1024**5) + self.assertEqual(set(self.bytes_map.keys()), set(expected_byte_units)) + for unit, exp_val in zip(expected_byte_units, expected_byte_values): + self.assertEqual(self.bytes_map[unit], exp_val) + + expected_time_units = ('ms', 's', 'min', 'h', 'd') + expected_time_values = (1, 1000, 60000, 3600000, 86400000) + self.assertEqual(set(self.ms_map.keys()), set(expected_time_units)) + for unit, exp_val in zip(expected_time_units, expected_time_values): + self.assertEqual(self.ms_map[unit], exp_val) + + def test_get_raw_size_simple(self): + # Bytes + for hr_value, exp_value in zip(self.from_hr_bytes_simple, self.as_bytes_simple): + value = ConversionUtil.get_raw_size( + hr_value, system=ConversionUtil.DEFAULT_BYTES_SYSTEM) + self.assertEqual(value, exp_value) + + # Time + for hr_value, exp_value in zip(self.from_hr_times_simple, self.as_ms_simple): + value = ConversionUtil.get_raw_size( + hr_value, system=ConversionUtil.DEFAULT_TIME_SYSTEM) + self.assertEqual(value, exp_value) + + def test_get_raw_size(self): + # Bytes + for hr_value, exp_value in zip(self.from_hr_bytes, self.as_bytes): + byte_conversion = ConversionUtil.get_raw_size( + hr_value, system=ConversionUtil.DEFAULT_BYTES_SYSTEM) + self.assertEqual(byte_conversion, exp_value) + + # Time + for hr_value, exp_value in zip(self.from_hr_times, self.as_ms): + time_conversion = ConversionUtil.get_raw_size( + hr_value, system=ConversionUtil.DEFAULT_TIME_SYSTEM) + self.assertEqual(time_conversion, exp_value) + + def test_get_human_readable_simple(self): + # Bytes + for raw_value, exp_value in zip(self.as_bytes_simple, self.bytes_to_hr_simple): + value = ConversionUtil.get_human_readable( + raw_value, system=ConversionUtil.DEFAULT_BYTES_SYSTEM) + self.assertEqual(value, exp_value) + value2 = ConversionUtil.get_human_readable2( + raw_value, system=ConversionUtil.DEFAULT_BYTES_SYSTEM, + min_suffix='B') + self.assertEqual(value2, exp_value) + + value = ConversionUtil.get_human_readable2( + 44, system=ConversionUtil.DEFAULT_BYTES_SYSTEM, + min_suffix='kB') + self.assertEqual(value, '44B') + + # Time + for raw_value, exp_value in zip(self.as_ms_simple, self.ms_to_hr_simple): + value = ConversionUtil.get_human_readable( + raw_value, system=ConversionUtil.DEFAULT_TIME_SYSTEM) + self.assertEqual(value, exp_value) + value2 = ConversionUtil.get_human_readable2( + raw_value, system=ConversionUtil.DEFAULT_TIME_SYSTEM, + min_suffix='ms') + self.assertEqual(value2, exp_value) + + value = ConversionUtil.get_human_readable2( + 44, system=ConversionUtil.DEFAULT_TIME_SYSTEM, + min_suffix='s') + self.assertEqual(value, '44ms') def test_get_human_readable(self): # Bytes - byte_test_convert = [1024**5, 2 * 1024**4, 3 * 1024**3, - 4 * 1024**2, 5 * 1024**1, 6] - byte_ans = ['1PB', '2TB', '3GB', '4MB', '5kB', '6B'] - for i, byte_test in enumerate(byte_test_convert): - byte_readable = ConversionUtil.get_human_readable( - byte_test, system=ConversionUtil.DEFAULT_BYTES_SYSTEM) - self.assertEqual(byte_readable, byte_ans[i]) + for i, raw_bytes in enumerate(self.as_bytes): + exp_hr = self.bytes_to_hr[i] + exp_hr2 = self.bytes_to_hr2[i] + min_suffix = self.min_bytes_suffixes[i] + hr_value = ConversionUtil.get_human_readable( + raw_bytes, system=ConversionUtil.DEFAULT_BYTES_SYSTEM) + hr_value2 = ConversionUtil.get_human_readable2( + raw_bytes, system=ConversionUtil.DEFAULT_BYTES_SYSTEM, + min_suffix=min_suffix) + self.assertEqual(hr_value, exp_hr) + self.assertEqual(hr_value2, exp_hr2) # Time - day_test_convert = [500, 1000, 55000, 600000, 72000000, 86400000] - day_ans = ['500ms', '1s', '55s', '10min', '20h', '1d'] - for i, day_test in enumerate(day_test_convert): - day_readable = ConversionUtil.get_human_readable( - day_test, system=ConversionUtil.DEFAULT_TIME_SYSTEM) - self.assertEqual(day_readable, day_ans[i]) + for i, raw_time in enumerate(self.as_ms): + exp_hr = self.ms_to_hr[i] + exp_hr2 = self.ms_to_hr2[i] + min_suffix = self.min_time_suffixes[i] + hr_value = ConversionUtil.get_human_readable( + raw_time, system=ConversionUtil.DEFAULT_TIME_SYSTEM) + hr_value2 = ConversionUtil.get_human_readable2( + raw_time, system=ConversionUtil.DEFAULT_TIME_SYSTEM, + min_suffix=min_suffix) + self.assertEqual(hr_value, exp_hr) + self.assertEqual(hr_value2, exp_hr2) class LabelUtilTest(TestCase): @@ -314,7 +523,7 @@ class LabelUtilTest(TestCase): "Random Word"] for i, key in enumerate(test_keys): - if (key == "???"): # DBms -> DBMS or DBms? + if key == "???": # DBms -> DBMS or DBms? continue self.assertEqual(res_capfirst_label_map[key], cap_ans[i]) diff --git a/server/website/website/parser/base.py b/server/website/website/parser/base.py index cb0537c..08ad7c9 100644 --- a/server/website/website/parser/base.py +++ b/server/website/website/parser/base.py @@ -5,8 +5,9 @@ # from collections import OrderedDict -from website.models import KnobCatalog, MetricCatalog +from website.models import KnobCatalog, KnobUnitType, MetricCatalog from website.types import BooleanType, MetricType, VarType +from website.utils import ConversionUtil # pylint: disable=no-self-use @@ -31,6 +32,11 @@ class BaseParser: self.true_value = 'on' self.false_value = 'off' + self.bytes_system = ConversionUtil.DEFAULT_BYTES_SYSTEM + self.time_system = ConversionUtil.DEFAULT_TIME_SYSTEM + self.min_bytes_unit = 'kB' + self.min_time_unit = 'ms' + @property def transactions_counter(self): raise NotImplementedError() @@ -79,11 +85,25 @@ class BaseParser: def convert_integer(self, int_value, metadata): try: - res = int(int_value) - except ValueError: - res = int(float(int_value)) + try: + converted = int(int_value) + except ValueError: + converted = int(float(int_value)) - return res + except ValueError: + if metadata.unit == KnobUnitType.BYTES: + converted = ConversionUtil.get_raw_size( + int_value, system=self.bytes_system) + elif metadata.unit == KnobUnitType.MILLISECONDS: + converted = ConversionUtil.get_raw_size( + int_value, system=self.time_system) + else: + raise Exception( + 'Unknown unit type: {}'.format(metadata.unit)) + if converted is None: + raise Exception('Invalid integer format for {}: {}'.format( + metadata.name, int_value)) + return converted def convert_real(self, real_value, metadata): return float(real_value) @@ -307,7 +327,11 @@ class BaseParser: adj_val = end_val - start_val else: # MetricType.STATISTICS or MetricType.INFO adj_val = end_val - assert adj_val >= 0, '{} wrong metric type '.format(met_name) + assert adj_val >= 0, \ + '{} wrong metric type: {} (start={}, end={}, diff={})'.format( + met_name, MetricType.name(met_info.metric_type), start_val, + end_val, end_val - start_val) + adjusted_metrics[met_name] = adj_val else: # This metric is either a bool, enum, string, or timestamp @@ -333,8 +357,24 @@ class BaseParser: enumvals = metadata.enumvals.split(',') return enumvals[int(round(enum_value))] + # def format_integer(self, int_value, metadata): + # return int(round(int_value)) + def format_integer(self, int_value, metadata): - return int(round(int_value)) + int_value = int(round(int_value)) + if metadata.unit != KnobUnitType.OTHER and int_value > 0: + if metadata.unit == KnobUnitType.BYTES: + int_value = ConversionUtil.get_human_readable2( + int_value, self.bytes_system, 'kB') + elif metadata.unit == KnobUnitType.MILLISECONDS: + int_value = ConversionUtil.get_human_readable2( + int_value, self.time_system, 'ms') + else: + raise Exception( + 'Invalid unit type for {}: {}'.format( + metadata.name, metadata.unit)) + + return int_value def format_real(self, real_value, metadata): return round(float(real_value), 3) diff --git a/server/website/website/parser/myrocks.py b/server/website/website/parser/myrocks.py index d7b38bb..9e8877f 100644 --- a/server/website/website/parser/myrocks.py +++ b/server/website/website/parser/myrocks.py @@ -8,8 +8,7 @@ import re from collections import OrderedDict from .base import BaseParser -from website.types import KnobUnitType, MetricType, VarType -from website.utils import ConversionUtil +from website.types import MetricType, VarType class MyRocksParser(BaseParser): @@ -22,40 +21,6 @@ class MyRocksParser(BaseParser): def latency_timer(self): raise NotImplementedError() - def convert_integer(self, int_value, metadata): - converted = None - try: - converted = super().convert_integer( - int_value, metadata) - except ValueError: - if metadata.unit == KnobUnitType.BYTES: - converted = ConversionUtil.get_raw_size( - int_value, system=ConversionUtil.DEFAULT_BYTES_SYSTEM) - elif metadata.unit == KnobUnitType.MILLISECONDS: - converted = ConversionUtil.get_raw_size( - int_value, system=ConversionUtil.DEFAULT_TIME_SYSTEM) - else: - raise Exception('Unknown unit type: {}'.format(metadata.unit)) - if converted is None: - raise Exception('Invalid integer format for {}: {}'.format( - metadata.name, int_value)) - return converted - - def format_integer(self, int_value, metadata): - if metadata.unit != KnobUnitType.OTHER and int_value > 0: - if metadata.unit == KnobUnitType.BYTES: - int_value = ConversionUtil.get_human_readable( - int_value, ConversionUtil.DEFAULT_BYTES_SYSTEM) - elif metadata.unit == KnobUnitType.MILLISECONDS: - int_value = ConversionUtil.get_human_readable( - int_value, ConversionUtil.DEFAULT_TIME_SYSTEM) - else: - raise Exception('Invalid unit type for {}: {}'.format( - metadata.name, metadata.unit)) - else: - int_value = super().format_integer(int_value, metadata) - return int_value - def parse_version_string(self, version_string): dbms_version = version_string.split(',')[0] return re.search(r'\d+\.\d+(?=\.\d+)', dbms_version).group(0) diff --git a/server/website/website/parser/oracle.py b/server/website/website/parser/oracle.py index f677f2a..0957060 100644 --- a/server/website/website/parser/oracle.py +++ b/server/website/website/parser/oracle.py @@ -13,6 +13,13 @@ class OracleParser(BaseParser): super().__init__(dbms_obj) self.true_value = 'TRUE' self.false_value = 'FALSE' + self.bytes_system = ( + (1024 ** 4, 'T'), + (1024 ** 3, 'G'), + (1024 ** 2, 'M'), + (1024 ** 1, 'k'), + ) + self.min_bytes_unit = 'k' @property def transactions_counter(self): diff --git a/server/website/website/parser/postgres.py b/server/website/website/parser/postgres.py index be15ca3..2748b13 100644 --- a/server/website/website/parser/postgres.py +++ b/server/website/website/parser/postgres.py @@ -7,7 +7,6 @@ import re from .base import BaseParser -from website.types import KnobUnitType from website.utils import ConversionUtil @@ -17,6 +16,8 @@ class PostgresParser(BaseParser): super().__init__(dbms_obj) self.valid_true_val = ("on", "true", "yes", 1) self.valid_false_val = ("off", "false", "no", 0) + self.bytes_system = [(f, s) for f, s in ConversionUtil.DEFAULT_BYTES_SYSTEM + if s in ('TB', 'GB', 'MB', 'kB')] @property def transactions_counter(self): @@ -26,42 +27,6 @@ class PostgresParser(BaseParser): def latency_timer(self): raise NotImplementedError() - def convert_integer(self, int_value, metadata): - converted = None - try: - converted = super().convert_integer(int_value, metadata) - except ValueError: - if metadata.unit == KnobUnitType.BYTES: - converted = ConversionUtil.get_raw_size( - int_value, system=ConversionUtil.DEFAULT_BYTES_SYSTEM) - elif metadata.unit == KnobUnitType.MILLISECONDS: - converted = ConversionUtil.get_raw_size( - int_value, system=ConversionUtil.DEFAULT_TIME_SYSTEM) - else: - raise Exception( - 'Unknown unit type: {}'.format(metadata.unit)) - if converted is None: - raise Exception('Invalid integer format for {}: {}'.format( - metadata.name, int_value)) - return converted - - def format_integer(self, int_value, metadata): - int_value = int(round(int_value)) - if metadata.unit != KnobUnitType.OTHER and int_value > 0: - if metadata.unit == KnobUnitType.BYTES: - int_value = ConversionUtil.get_human_readable( - int_value, ConversionUtil.DEFAULT_BYTES_SYSTEM) - elif metadata.unit == KnobUnitType.MILLISECONDS: - int_value = ConversionUtil.get_human_readable( - int_value, ConversionUtil.DEFAULT_TIME_SYSTEM) - else: - raise Exception( - 'Invalid unit type for {}: {}'.format( - metadata.name, metadata.unit)) - else: - int_value = super().format_integer(int_value, metadata) - return int_value - def parse_version_string(self, version_string): dbms_version = version_string.split(',')[0] return re.search(r'\d+\.\d+(?=\.\d+)', dbms_version).group(0) diff --git a/server/website/website/utils.py b/server/website/website/utils.py index 259f418..844d546 100644 --- a/server/website/website/utils.py +++ b/server/website/website/utils.py @@ -6,6 +6,7 @@ import datetime import json import logging +import math import os import string import tarfile @@ -270,6 +271,47 @@ class ConversionUtil(object): from hurry.filesize import size return size(value, system=system) + @staticmethod + def get_human_readable2(value, system, min_suffix): + # Converts the value to larger units only if there is no loss of resolution. + # pylint: disable=line-too-long + # From https://github.com/le0pard/pgtune/blob/master/webpack/components/configurationView/index.jsx#L74 + # pylint: enable=line-too-long + min_factor = None + unit = None + mod_system = [] + for i, (factor, suffix) in enumerate(system): + if suffix == min_suffix: + if value < factor: + assert i + 1 < len(system), \ + ('i + 1 >= len(system) (i + 1: {}, len(system): {}, value: {}, ' + 'min_suffix: {})').format(i + 1, len(system), value, min_suffix) + min_suffix = system[i + 1][1] + LOG.warning('The value is smaller than the min factor: %s < %s%s. ' + 'Setting min_suffix=%s...', value, factor, suffix, min_suffix) + else: + min_factor = factor + unit = min_suffix + value = math.floor(float(value) / min_factor) + break + + mod_system.append((factor, suffix)) + + if min_factor is None: + raise ValueError('Invalid min suffix for system: suffix={}, system={}'.format( + min_suffix, system)) + + LOG.info('min_suffix: %s, min_factor: %s, unit: %s, value: %s\nMOD_SYS:\n%s\n\n', + min_suffix, min_factor, unit, value, mod_system) + for factor, suffix in mod_system: + adj_factor = factor / min_factor + if value % adj_factor == 0: + value = math.floor(float(value) / adj_factor) + unit = suffix + break + + return '{}{}'.format(int(value), unit) + class LabelUtil(object):