Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 1 | # Copyright lowRISC contributors. |
| 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | r"""An abstraction for maintaining job runtime and its units. |
| 5 | """ |
| 6 | |
Andreas Kurth | f2278bd | 2023-01-27 15:19:40 +0000 | [diff] [blame] | 7 | from copy import copy |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 8 | from typing import Tuple |
Andreas Kurth | f2278bd | 2023-01-27 15:19:40 +0000 | [diff] [blame] | 9 | import unittest |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 10 | |
| 11 | |
| 12 | class JobTime: |
| 13 | # Possible units. |
| 14 | units = ["h", "m", "s", "ms", "us", "ns", "ps", "fs"] |
| 15 | dividers = [60.0, ] * 3 + [1000.0, ] * 5 |
| 16 | |
Andreas Kurth | d15f28b | 2023-01-27 15:19:09 +0000 | [diff] [blame] | 17 | def __init__(self, time: float = 0.0, unit: str = "s", normalize: bool = True): |
| 18 | self.set(time, unit, normalize) |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 19 | |
Andreas Kurth | d15f28b | 2023-01-27 15:19:09 +0000 | [diff] [blame] | 20 | def set(self, time: float, unit: str, normalize: bool = True): |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 21 | """Public API to set the instance variables time, unit.""" |
| 22 | self.__time = time |
| 23 | self.__unit = unit |
| 24 | assert self.__unit in self.units |
Andreas Kurth | d15f28b | 2023-01-27 15:19:09 +0000 | [diff] [blame] | 25 | if normalize: |
| 26 | self._normalize() |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 27 | |
| 28 | def get(self) -> Tuple[float, str]: |
| 29 | """Returns the time and unit as a tuple.""" |
| 30 | return self.__time, self.__unit |
| 31 | |
Andreas Kurth | f2278bd | 2023-01-27 15:19:40 +0000 | [diff] [blame] | 32 | def with_unit(self, unit: str): |
| 33 | """Return a copy of this object that has a specific unit and a value |
| 34 | scaled accordingly. |
| 35 | |
| 36 | Note that the scaling may not be lossless due to rounding errors and |
| 37 | limited precision. |
| 38 | """ |
| 39 | target_index = self.units.index(unit) |
| 40 | index = self.units.index(self.__unit) |
| 41 | jt = copy(self) |
| 42 | while index < target_index: |
| 43 | index += 1 |
| 44 | jt.__time *= self.dividers[index] |
| 45 | jt.__unit = self.units[index] |
| 46 | while index > target_index: |
| 47 | jt.__time /= self.dividers[index] |
| 48 | index -= 1 |
| 49 | jt.__unit = self.units[index] |
| 50 | return jt |
| 51 | |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 52 | def _normalize(self): |
| 53 | """Brings the time and its units to a more meaningful magnitude. |
| 54 | |
| 55 | If the time is very large with a lower magnitude, this method divides |
| 56 | the time to get it to the next higher magnitude recursively, stopping |
| 57 | if the next division causes the time to go < 1. Examples: |
| 58 | 123123232ps -> 123.12us |
| 59 | 23434s -> 6.509h |
| 60 | |
| 61 | The supported magnitudes and their associated divider values are |
| 62 | provided by JobTime.units and JobTime.dividers. |
| 63 | """ |
| 64 | if self.__time == 0: |
| 65 | return |
| 66 | |
| 67 | index = self.units.index(self.__unit) |
| 68 | normalized_time = self.__time |
| 69 | while index > 0 and normalized_time >= self.dividers[index]: |
| 70 | normalized_time = normalized_time / self.dividers[index] |
| 71 | index = index - 1 |
| 72 | self.__time = normalized_time |
| 73 | self.__unit = self.units[index] |
| 74 | |
| 75 | def __str__(self): |
| 76 | """Indicates <time><unit> as string. |
| 77 | |
| 78 | The time value is truncated to 3 decimal places. |
| 79 | Returns an empty string if the __time is set to 0. |
| 80 | """ |
| 81 | if self.__time == 0: |
| 82 | return "" |
| 83 | else: |
| 84 | return f"{self.__time:.3f}{self.__unit}" |
| 85 | |
| 86 | def __eq__(self, other) -> bool: |
| 87 | assert isinstance(other, JobTime) |
| 88 | other_time, other_unit = other.get() |
| 89 | return self.__unit == other_unit and self.__time == other_time |
| 90 | |
| 91 | def __gt__(self, other) -> bool: |
| 92 | if self.__time == 0: |
| 93 | return False |
| 94 | |
| 95 | assert isinstance(other, JobTime) |
| 96 | other_time, other_unit = other.get() |
| 97 | if other_time == 0: |
| 98 | return True |
| 99 | |
| 100 | sidx = JobTime.units.index(self.__unit) |
| 101 | oidx = JobTime.units.index(other_unit) |
| 102 | if sidx < oidx: |
| 103 | return True |
| 104 | elif sidx > oidx: |
| 105 | return False |
| 106 | else: |
| 107 | return self.__time > other_time |
Andreas Kurth | f2278bd | 2023-01-27 15:19:40 +0000 | [diff] [blame] | 108 | |
| 109 | |
| 110 | class TestJobTimeMethods(unittest.TestCase): |
| 111 | |
| 112 | def test_with_unit(self): |
| 113 | # First data set |
| 114 | h = JobTime(6, 'h', normalize=False) |
| 115 | m = JobTime(360, 'm', normalize=False) |
| 116 | s = JobTime(21600, 's', normalize=False) |
| 117 | ms = JobTime(21600000, 'ms', normalize=False) |
| 118 | for src in [h, m, s, ms]: |
| 119 | for unit, dst in [('h', h), ('m', m), ('s', s), ('ms', ms)]: |
| 120 | self.assertEqual(src.with_unit(unit), dst) |
| 121 | # Second data set |
| 122 | fs = JobTime(123456000000, 'fs', normalize=False) |
| 123 | ps = JobTime(123456000, 'ps', normalize=False) |
| 124 | ns = JobTime(123456, 'ns', normalize=False) |
| 125 | us = JobTime(123.456, 'us', normalize=False) |
| 126 | ms = JobTime(0.123456, 'ms', normalize=False) |
| 127 | for src in [fs, ps, ns, us, ms]: |
| 128 | for unit, dst in [('fs', fs), ('ps', ps), ('ns', ns), ('us', us), |
| 129 | ('ms', ms)]: |
| 130 | self.assertEqual(src.with_unit(unit), dst) |