Coverage for tests/test_timer.py: 15%
195 statements
« prev ^ index » next coverage.py v6.4, created at 2022-06-02 03:24 -0700
« prev ^ index » next coverage.py v6.4, created at 2022-06-02 03:24 -0700
1# This file is part of utils.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12import datetime
13import logging
14import os.path
15import time
16import unittest
17from dataclasses import dataclass
19from astropy import units as u
20from lsst.utils.timer import logInfo, logPairs, time_this, timeMethod
22log = logging.getLogger("test_timer")
24THIS_FILE = os.path.basename(__file__)
26# Only use this in a single test but needs to be associated
27# with the function up front.
28test_metadata = {}
31@dataclass
32class Example1:
33 log: logging.Logger
34 metadata: dict
36 @timeMethod
37 def sleeper(self, duration: float) -> None:
38 """Sleep for some time."""
39 time.sleep(duration)
42@timeMethod
43def decorated_sleeper_nothing(self, duration: float) -> None:
44 time.sleep(duration)
47@timeMethod(logger=log)
48def decorated_sleeper_logger(self, duration: float) -> None:
49 time.sleep(duration)
52@timeMethod(logger=log, logLevel=logging.INFO)
53def decorated_sleeper_logger_level(self, duration: float) -> None:
54 time.sleep(duration)
57@timeMethod(metadata=test_metadata)
58def decorated_sleeper_metadata(self, duration: float) -> None:
59 time.sleep(duration)
62class TestTimeMethod(unittest.TestCase):
63 def testLogPairs(self):
64 # Test the non-obj case.
65 logger = logging.getLogger("test")
66 pairs = (("name1", 0), ("name2", 1))
67 metadata = {}
68 with self.assertLogs(level=logging.INFO) as cm:
69 logPairs(None, pairs, logLevel=logging.INFO, logger=logger, metadata=metadata)
70 self.assertEqual(len(cm.output), 1, cm.output)
71 self.assertTrue(cm.output[0].endswith("name1=0; name2=1"), cm.output)
72 self.assertEqual(cm.records[0].filename, THIS_FILE, "log message should originate from here")
73 self.assertEqual(metadata, {"name1": [0], "name2": [1]})
75 # Call it again with an explicit stack level.
76 # Force it to come from lsst.utils.
77 with self.assertLogs(level=logging.INFO) as cm:
78 logPairs(None, pairs, logLevel=logging.INFO, logger=logger, metadata=metadata, stacklevel=0)
79 self.assertEqual(cm.records[0].filename, "timer.py")
81 # Check that the log message is filtered by default.
82 with self.assertLogs(level=logging.INFO) as cm:
83 logPairs(None, pairs, logger=logger, metadata=metadata)
84 logger.info("Message")
85 self.assertEqual(len(cm.records), 1)
87 def testLogInfo(self):
88 metadata = {}
89 logger = logging.getLogger("testLogInfo")
90 with self.assertLogs(level=logging.INFO) as cm:
91 logInfo(None, prefix="Prefix", metadata=metadata, logger=logger, logLevel=logging.INFO)
92 self.assertEqual(cm.records[0].filename, THIS_FILE)
93 self.assertIn("PrefixUtc", metadata)
94 self.assertIn("PrefixMaxResidentSetSize", metadata)
95 self.assertEqual(metadata["__version__"], 1)
97 # Again with no log output.
98 logInfo(None, prefix="Prefix", metadata=metadata)
99 self.assertEqual(len(metadata["PrefixUtc"]), 2)
101 # With an explicit stacklevel.
102 with self.assertLogs(level=logging.INFO) as cm:
103 logInfo(
104 None, prefix="Prefix", metadata=metadata, logger=logger, logLevel=logging.INFO, stacklevel=0
105 )
106 self.assertEqual(cm.records[0].filename, "timer.py")
108 def assertTimer(self, duration, task):
109 # Call it twice to test the "add" functionality.
110 task.sleeper(duration)
111 task.sleeper(duration)
112 counter = 2
114 has_logger = getattr(task, "log", None) is not None and task.log is not None
115 has_metadata = getattr(task, "metadata", None) is not None and task.metadata is not None
117 if has_logger:
118 counter += 1
119 with self.assertLogs("timer.task", level=logging.DEBUG) as cm:
120 task.sleeper(duration)
121 self.assertEqual(cm.records[0].filename, THIS_FILE, "log message should originate from here")
123 if has_metadata:
124 self.assertEqual(len(task.metadata["sleeperStartUserTime"]), counter)
126 start = datetime.datetime.fromisoformat(task.metadata["sleeperStartUtc"][1])
127 end = datetime.datetime.fromisoformat(task.metadata["sleeperEndUtc"][1])
128 delta = end - start
129 delta_sec = delta.seconds + (delta.microseconds / 1e6)
130 self.assertGreaterEqual(delta_sec, duration)
132 def testTaskLike(self):
133 """Test timer on something that looks like a Task."""
135 # Call with different parameters.
136 parameters = (
137 (logging.getLogger("task"), {}),
138 (logging.getLogger("task"), None),
139 (None, {}),
140 (None, None),
141 )
143 duration = 0.1
144 for log, metadata in parameters:
145 with self.subTest(log=log, metadata=metadata):
146 task = Example1(log=log, metadata=metadata)
147 self.assertTimer(duration, task)
149 def testDecorated(self):
150 """Test timeMethod on non-Task like instances."""
151 duration = 0.1
153 # The "self" object shouldn't be usable but this should do nothing
154 # and not crash.
155 decorated_sleeper_nothing(self, duration)
157 # Use a function decorated for logging.
158 with self.assertLogs("timer.test_timer", level=logging.DEBUG) as cm:
159 decorated_sleeper_logger(self, duration)
160 self.assertEqual(cm.records[0].filename, THIS_FILE, "log message should originate from here")
162 # And adjust the log level
163 with self.assertLogs("timer.test_timer", level=logging.INFO):
164 decorated_sleeper_logger_level(self, duration)
166 # Use a function decorated for metadata.
167 self.assertEqual(len(test_metadata), 0)
168 with self.assertLogs("timer.test_timer", level=logging.DEBUG) as cm:
169 # Check that we only get a single log message and nothing from
170 # timeMethod itself.
171 decorated_sleeper_metadata(self, duration)
172 logging.getLogger("timer.test_timer").debug("sentinel")
173 self.assertEqual(len(cm.output), 1)
174 self.assertIn("decorated_sleeper_metadataStartUserTime", test_metadata)
177class TimerTestCase(unittest.TestCase):
178 def testTimer(self):
179 with self.assertLogs(level="DEBUG") as cm:
180 with time_this():
181 pass
182 self.assertEqual(cm.records[0].name, "timer")
183 self.assertEqual(cm.records[0].levelname, "DEBUG")
184 self.assertEqual(cm.records[0].filename, THIS_FILE)
186 with self.assertLogs(level="DEBUG") as cm:
187 with time_this(prefix=None):
188 pass
189 self.assertEqual(cm.records[0].name, "root")
190 self.assertEqual(cm.records[0].levelname, "DEBUG")
191 self.assertIn("Took", cm.output[0])
192 self.assertNotIn(": Took", cm.output[0])
193 self.assertNotIn("; ", cm.output[0])
194 self.assertEqual(cm.records[0].filename, THIS_FILE)
196 # Report memory usage.
197 with self.assertLogs(level="DEBUG") as cm:
198 with time_this(level=logging.DEBUG, prefix=None, mem_usage=True):
199 pass
200 self.assertEqual(cm.records[0].name, "root")
201 self.assertEqual(cm.records[0].levelname, "DEBUG")
202 self.assertIn("Took", cm.output[0])
203 self.assertIn("memory", cm.output[0])
204 self.assertIn("delta", cm.output[0])
205 self.assertIn("peak delta", cm.output[0])
206 self.assertIn("byte", cm.output[0])
208 # Report memory usage including child processes.
209 with self.assertLogs(level="DEBUG") as cm:
210 with time_this(level=logging.DEBUG, prefix=None, mem_usage=True, mem_child=True):
211 pass
212 self.assertEqual(cm.records[0].name, "root")
213 self.assertEqual(cm.records[0].levelname, "DEBUG")
214 self.assertIn("Took", cm.output[0])
215 self.assertIn("memory", cm.output[0])
216 self.assertIn("delta", cm.output[0])
217 self.assertIn("peak delta", cm.output[0])
218 self.assertIn("byte", cm.output[0])
220 # Report memory usage, use non-default, but a valid memory unit.
221 with self.assertLogs(level="DEBUG") as cm:
222 with time_this(level=logging.DEBUG, prefix=None, mem_usage=True, mem_unit=u.kilobyte):
223 pass
224 self.assertEqual(cm.records[0].name, "root")
225 self.assertEqual(cm.records[0].levelname, "DEBUG")
226 self.assertIn("Took", cm.output[0])
227 self.assertIn("memory", cm.output[0])
228 self.assertIn("delta", cm.output[0])
229 self.assertIn("peak delta", cm.output[0])
230 self.assertIn("kbyte", cm.output[0])
232 # Report memory usage, use an invalid memory unit.
233 with self.assertLogs(level="DEBUG") as cm:
234 with time_this(level=logging.DEBUG, prefix=None, mem_usage=True, mem_unit=u.gram):
235 pass
236 self.assertEqual(cm.records[0].name, "lsst.utils.timer")
237 self.assertEqual(cm.records[0].levelname, "WARNING")
238 self.assertIn("Invalid", cm.output[0])
239 self.assertIn("byte", cm.output[0])
240 self.assertEqual(cm.records[1].name, "root")
241 self.assertEqual(cm.records[1].levelname, "DEBUG")
242 self.assertIn("Took", cm.output[1])
243 self.assertIn("memory", cm.output[1])
244 self.assertIn("delta", cm.output[1])
245 self.assertIn("peak delta", cm.output[1])
246 self.assertIn("byte", cm.output[1])
248 # Change logging level
249 with self.assertLogs(level="INFO") as cm:
250 with time_this(level=logging.INFO, prefix=None):
251 pass
252 self.assertEqual(cm.records[0].name, "root")
253 self.assertIn("Took", cm.output[0])
254 self.assertIn("seconds", cm.output[0])
256 # Use a new logger with a message.
257 msg = "Test message %d"
258 test_num = 42
259 logname = "test"
260 with self.assertLogs(level="DEBUG") as cm:
261 with time_this(log=logging.getLogger(logname), msg=msg, args=(42,), prefix=None):
262 pass
263 self.assertEqual(cm.records[0].name, logname)
264 self.assertIn("Took", cm.output[0])
265 self.assertIn(msg % test_num, cm.output[0])
267 # Prefix the logger.
268 prefix = "prefix"
269 with self.assertLogs(level="DEBUG") as cm:
270 with time_this(prefix=prefix):
271 pass
272 self.assertEqual(cm.records[0].name, prefix)
273 self.assertIn("Took", cm.output[0])
275 # Prefix explicit logger.
276 with self.assertLogs(level="DEBUG") as cm:
277 with time_this(log=logging.getLogger(logname), prefix=prefix):
278 pass
279 self.assertEqual(cm.records[0].name, f"{prefix}.{logname}")
281 # Trigger a problem.
282 with self.assertLogs(level="ERROR") as cm:
283 with self.assertRaises(RuntimeError):
284 with time_this(log=logging.getLogger(logname), prefix=prefix):
285 raise RuntimeError("A problem")
286 self.assertEqual(cm.records[0].name, f"{prefix}.{logname}")
287 self.assertEqual(cm.records[0].levelname, "ERROR")
290if __name__ == "__main__": 290 ↛ 291line 290 didn't jump to line 291, because the condition on line 290 was never true
291 unittest.main()