units.py 4.24 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import decimal

import pint

from horizon.utils import functions

# Mapping of units from Ceilometer to Pint
INFORMATION_UNITS = (
    ('B', 'byte'),
    ('KB', 'Kibyte'),
    ('MB', 'Mibyte'),
    ('GB', 'Gibyte'),
    ('TB', 'Tibyte'),
    ('PB', 'Pibyte'),
    ('EB', 'Eibyte'),
)

TIME_UNITS = ('ns', 's', 'min', 'hr', 'day', 'week', 'month', 'year')


ureg = pint.UnitRegistry()


def is_supported(unit):
    """Returns a bool indicating whether the unit specified is supported."""
    return unit in functions.get_keys(INFORMATION_UNITS) + TIME_UNITS


def is_larger(unit_1, unit_2):
    """Returns a boolean indicating whether unit_1 is larger than unit_2.

    E.g:

    >>> is_larger('KB', 'B')
    True
    >>> is_larger('min', 'day')
    False
    """
    unit_1 = functions.value_for_key(INFORMATION_UNITS, unit_1)
    unit_2 = functions.value_for_key(INFORMATION_UNITS, unit_2)

    return ureg.parse_expression(unit_1) > ureg.parse_expression(unit_2)


def convert(value, source_unit, target_unit, fmt=False):
    """Converts value from source_unit to target_unit.

    Returns a tuple containing the converted value and target_unit.
    Having fmt set to True causes the value to be formatted to 1 decimal digit
    if it's a decimal or be formatted as integer if it's an integer.

    E.g:

    >>> convert(2, 'hr', 'min')
    (120.0, 'min')
    >>> convert(2, 'hr', 'min', fmt=True)
    (120, 'min')
    >>> convert(30, 'min', 'hr', fmt=True)
    (0.5, 'hr')
    """
    orig_target_unit = target_unit
    source_unit = functions.value_for_key(INFORMATION_UNITS, source_unit)
    target_unit = functions.value_for_key(INFORMATION_UNITS, target_unit)

    q = ureg.Quantity(value, source_unit)
    q = q.to(ureg.parse_expression(target_unit))
    value = functions.format_value(q.magnitude) if fmt else q.magnitude
    return value, orig_target_unit


def normalize(value, unit):
    """Converts the value so that it belongs to some expected range.

    Returns the new value and new unit.

    E.g:

    >>> normalize(1024, 'KB')
    (1, 'MB')
    >>> normalize(90, 'min')
    (1.5, 'hr')
    >>> normalize(1.0, 'object')
    (1, 'object')
    """
    if value < 0:
        raise ValueError('Negative value: %s %s.' % (value, unit))

    if unit in functions.get_keys(INFORMATION_UNITS):
        return _normalize_information(value, unit)
    elif unit in TIME_UNITS:
        return _normalize_time(value, unit)
    else:
        # Unknown unit, just return it
        return functions.format_value(value), unit


def _normalize_information(value, unit):
    value = decimal.Decimal(str(value))

    while value < 1:
        prev_unit = functions.previous_key(INFORMATION_UNITS, unit)
        if prev_unit is None:
            break
        value, unit = convert(value, unit, prev_unit)

    while value >= 1024:
        next_unit = functions.next_key(INFORMATION_UNITS, unit)
        if next_unit is None:
            break
        value, unit = convert(value, unit, next_unit)

    return functions.format_value(value), unit


def _normalize_time(value, unit):
    # Normalize time by converting to next higher unit when value is
    # at least 2 units
    value, unit = convert(value, unit, 's')

    if value >= 120:
        value, unit = convert(value, 's', 'min')

        if value >= 120:
            value, unit = convert(value, 'min', 'hr')

            if value >= 48:
                value, unit = convert(value, 'hr', 'day')

                if value >= 730:
                    value, unit = convert(value, 'day', 'year')
                elif value >= 62:
                    value, unit = convert(value, 'day', 'month')
                elif value >= 14:
                    value, unit = convert(value, 'day', 'week')

    return functions.format_value(value), unit