Skip to content

Commit b497eb5

Browse files
committed
refactor: [job] pull out JobTime tests, improved testing and fix a few bugs
Signed-off-by: James McCorrie <[email protected]>
1 parent d097727 commit b497eb5

File tree

4 files changed

+302
-83
lines changed

4 files changed

+302
-83
lines changed

ruff-ci.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ ignore = [
7878
"PLW0602",
7979
"PLW0603",
8080
"PLW0604",
81-
"PLW1641",
8281
"PLW2901",
8382
"PT009",
8483
"PT011",

src/dvsim/job/time.py

Lines changed: 127 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,85 @@
44

55
"""An abstraction for maintaining job runtime and its units."""
66

7-
import unittest
8-
from copy import copy
9-
10-
11-
class JobTime:
12-
# Possible units.
13-
units = ["h", "m", "s", "ms", "us", "ns", "ps", "fs"]
14-
dividers = [
15-
60.0,
16-
] * 3 + [
17-
1000.0,
18-
] * 5
7+
__all__ = ("JobTime",)
8+
9+
# Supported time units and the divisor required to convert the value in the
10+
# current unit to the next largest unit.
11+
TIME_UNITS = ["h", "m", "s", "ms", "us", "ns", "ps", "fs"]
12+
TIME_DIVIDERS = [60.0] * 3 + [1000.0] * 5
13+
14+
15+
# TODO: Migrate to Time instead of a custom implementation
16+
class JobTime: # noqa: PLW1641 # Muitable object should not implement __hash__
17+
"""Job runtime."""
18+
19+
def __init__(self, time: float = 0.0, unit: str = "s", *, normalize: bool = True) -> None:
20+
"""Initialise."""
21+
self._time: float = time
22+
self._unit: str = unit
23+
24+
self.set(
25+
time=time,
26+
unit=unit,
27+
normalize=normalize,
28+
)
29+
30+
@property
31+
def time(self) -> float:
32+
"""Get time."""
33+
return self._time
34+
35+
@property
36+
def unit(self) -> str:
37+
"""Get unit."""
38+
return self._unit
39+
40+
@staticmethod
41+
def _check_valid_unit(unit: str) -> None:
42+
"""Assert unit is valid."""
43+
if unit not in TIME_UNITS:
44+
msg = f"unit '{unit}' is not a supported time unit: {TIME_UNITS}"
45+
raise KeyError(msg)
46+
47+
def set(self, time: float, unit: str, *, normalize: bool = True) -> None:
48+
"""Public API to set the instance variables time, unit."""
49+
self._check_valid_unit(unit=unit)
1950

20-
def __init__(self, time: float = 0.0, unit: str = "s", normalize: bool = True) -> None:
21-
self.set(time, unit, normalize)
51+
self._time = time
52+
self._unit = unit
2253

23-
def set(self, time: float, unit: str, normalize: bool = True) -> None:
24-
"""Public API to set the instance variables time, unit."""
25-
self.__time = time
26-
self.__unit = unit
27-
assert self.__unit in self.units
2854
if normalize:
2955
self._normalize()
3056

3157
def get(self) -> tuple[float, str]:
32-
"""Returns the time and unit as a tuple."""
33-
return self.__time, self.__unit
58+
"""Return the time and unit as a tuple."""
59+
return self._time, self._unit
3460

35-
def with_unit(self, unit: str):
36-
"""Return a copy of this object that has a specific unit and a value
37-
scaled accordingly.
61+
def with_unit(self, unit: str) -> "JobTime":
62+
"""Return a copy with the given unit.
3863
3964
Note that the scaling may not be lossless due to rounding errors and
4065
limited precision.
4166
"""
42-
target_index = self.units.index(unit)
43-
index = self.units.index(self.__unit)
44-
jt = copy(self)
67+
self._check_valid_unit(unit=unit)
68+
69+
target_index = TIME_UNITS.index(unit)
70+
index = TIME_UNITS.index(self._unit)
71+
72+
new_time = self._time
4573
while index < target_index:
4674
index += 1
47-
jt.__time *= self.dividers[index]
48-
jt.__unit = self.units[index]
75+
new_time *= TIME_DIVIDERS[index]
76+
4977
while index > target_index:
50-
jt.__time /= self.dividers[index]
78+
new_time /= TIME_DIVIDERS[index]
5179
index -= 1
52-
jt.__unit = self.units[index]
53-
return jt
80+
81+
return JobTime(
82+
time=new_time,
83+
unit=unit,
84+
normalize=False,
85+
)
5486

5587
def _normalize(self) -> None:
5688
"""Brings the time and its units to a more meaningful magnitude.
@@ -62,68 +94,81 @@ def _normalize(self) -> None:
6294
23434s -> 6.509h
6395
6496
The supported magnitudes and their associated divider values are
65-
provided by JobTime.units and JobTime.dividers.
97+
provided by TIME_UNITS and TIME_DIVIDERS.
6698
"""
67-
if self.__time == 0:
99+
if self._time == 0:
68100
return
69101

70-
index = self.units.index(self.__unit)
71-
normalized_time = self.__time
72-
while index > 0 and normalized_time >= self.dividers[index]:
73-
normalized_time = normalized_time / self.dividers[index]
102+
index = TIME_UNITS.index(self._unit)
103+
normalized_time = self._time
104+
105+
while index > 0 and normalized_time >= TIME_DIVIDERS[index]:
106+
normalized_time = normalized_time / TIME_DIVIDERS[index]
74107
index = index - 1
75-
self.__time = normalized_time
76-
self.__unit = self.units[index]
108+
109+
self._time = normalized_time
110+
self._unit = TIME_UNITS[index]
111+
112+
def _with_common_units(self, other: "JobTime") -> tuple[float, float]:
113+
"""Convert times to common units for relative comparison.
114+
115+
Returns:
116+
tuple of both times as floats in the smallest unit to be
117+
used for comparison.
118+
119+
"""
120+
stime = self._time
121+
otime = other.time
122+
123+
sidx = TIME_UNITS.index(self._unit)
124+
oidx = TIME_UNITS.index(other.unit)
125+
126+
# If the time units are not the same
127+
if sidx != oidx:
128+
# Pick the smallest unit and standardise on that unit. This means
129+
# the comparison is less likely to be lossy.
130+
if sidx < oidx:
131+
stime = self.with_unit(other.unit).time
132+
else:
133+
otime = other.with_unit(self.unit).time
134+
135+
return stime, otime
77136

78137
def __str__(self) -> str:
79-
"""Indicates <time><unit> as string.
138+
"""Time as a string <time><unit>.
80139
81140
The time value is truncated to 3 decimal places.
82141
Returns an empty string if the __time is set to 0.
83142
"""
84-
if self.__time == 0:
143+
if self._time == 0:
85144
return ""
86-
return f"{self.__time:.3f}{self.__unit}"
87-
88-
def __eq__(self, other) -> bool:
89-
assert isinstance(other, JobTime)
90-
other_time, other_unit = other.get()
91-
return self.__unit == other_unit and self.__time == other_time
145+
return f"{self._time:.3f}{self._unit}"
92146

93-
def __gt__(self, other) -> bool:
94-
if self.__time == 0:
147+
def __eq__(self, other: object) -> bool:
148+
"""Check equality."""
149+
if not isinstance(other, JobTime):
95150
return False
96151

97-
assert isinstance(other, JobTime)
98-
other_time, other_unit = other.get()
99-
if other_time == 0:
100-
return True
152+
stime, otime = self._with_common_units(other=other)
101153

102-
sidx = JobTime.units.index(self.__unit)
103-
oidx = JobTime.units.index(other_unit)
104-
if sidx < oidx:
105-
return True
106-
if sidx > oidx:
107-
return False
108-
return self.__time > other_time
109-
110-
111-
class TestJobTimeMethods(unittest.TestCase):
112-
def test_with_unit(self) -> None:
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-
assert 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), ("ms", ms)]:
129-
assert src.with_unit(unit) == dst
154+
return stime == otime
155+
156+
def __gt__(self, other: object) -> bool:
157+
"""Check time is greater than."""
158+
if not isinstance(other, JobTime):
159+
msg = f"Can't compare {self} with {other}"
160+
raise TypeError(msg)
161+
162+
stime, otime = self._with_common_units(other=other)
163+
164+
return stime > otime
165+
166+
def __ge__(self, other: object) -> bool:
167+
"""Check time is greater than or equal to."""
168+
if not isinstance(other, JobTime):
169+
msg = f"Can't compare {self} with {other}"
170+
raise TypeError(msg)
171+
172+
stime, otime = self._with_common_units(other=other)
173+
174+
return stime >= otime

tests/job/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copyright lowRISC contributors (OpenTitan project).
2+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
"""DVSim job tests."""

0 commit comments

Comments
 (0)