lsst-utils 25.2023.600__py3-none-any.whl → 29.2025.4800__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lsst/utils/__init__.py +0 -3
- lsst/utils/_packaging.py +2 -0
- lsst/utils/argparsing.py +79 -0
- lsst/utils/classes.py +27 -9
- lsst/utils/db_auth.py +339 -0
- lsst/utils/deprecated.py +10 -7
- lsst/utils/doImport.py +8 -9
- lsst/utils/inheritDoc.py +34 -6
- lsst/utils/introspection.py +285 -19
- lsst/utils/iteration.py +193 -7
- lsst/utils/logging.py +155 -105
- lsst/utils/packages.py +324 -82
- lsst/utils/plotting/__init__.py +15 -0
- lsst/utils/plotting/figures.py +159 -0
- lsst/utils/plotting/limits.py +155 -0
- lsst/utils/plotting/publication_plots.py +184 -0
- lsst/utils/plotting/rubin.mplstyle +46 -0
- lsst/utils/tests.py +231 -102
- lsst/utils/threads.py +9 -3
- lsst/utils/timer.py +207 -110
- lsst/utils/usage.py +6 -6
- lsst/utils/version.py +1 -1
- lsst/utils/wrappers.py +74 -29
- {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info}/METADATA +19 -15
- lsst_utils-29.2025.4800.dist-info/RECORD +32 -0
- {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info}/WHEEL +1 -1
- lsst/utils/_forwarded.py +0 -28
- lsst/utils/backtrace/__init__.py +0 -33
- lsst/utils/ellipsis.py +0 -54
- lsst/utils/get_caller_name.py +0 -45
- lsst_utils-25.2023.600.dist-info/RECORD +0 -29
- {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info/licenses}/COPYRIGHT +0 -0
- {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info/licenses}/LICENSE +0 -0
- {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info}/top_level.txt +0 -0
- {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info}/zip-safe +0 -0
lsst/utils/threads.py
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
# Use of this source code is governed by a 3-clause BSD-style
|
|
10
10
|
# license that can be found in the LICENSE file.
|
|
11
11
|
#
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
12
|
"""Support for threading and multi-processing."""
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
__all__ = ["disable_implicit_threading", "set_thread_envvars"]
|
|
17
17
|
|
|
18
18
|
import os
|
|
19
19
|
|
|
@@ -50,6 +50,12 @@ def set_thread_envvars(num_threads: int = 1, override: bool = False) -> None:
|
|
|
50
50
|
if override or var not in os.environ:
|
|
51
51
|
os.environ[var] = str(num_threads)
|
|
52
52
|
|
|
53
|
+
# Also specify an explicit value for OMP_PROC_BIND to tell OpenMP not to
|
|
54
|
+
# set CPU affinity.
|
|
55
|
+
var = "OMP_PROC_BIND"
|
|
56
|
+
if override or var not in os.environ:
|
|
57
|
+
os.environ[var] = "false"
|
|
58
|
+
|
|
53
59
|
|
|
54
60
|
def disable_implicit_threading() -> None:
|
|
55
61
|
"""Do whatever is necessary to try to prevent implicit threading.
|
lsst/utils/timer.py
CHANGED
|
@@ -10,40 +10,34 @@
|
|
|
10
10
|
# license that can be found in the LICENSE file.
|
|
11
11
|
#
|
|
12
12
|
|
|
13
|
-
"""Utilities for measuring execution time.
|
|
14
|
-
"""
|
|
13
|
+
"""Utilities for measuring execution time."""
|
|
15
14
|
|
|
16
15
|
from __future__ import annotations
|
|
17
16
|
|
|
18
|
-
__all__ = ["
|
|
17
|
+
__all__ = ["duration_from_timeMethod", "logInfo", "profile", "timeMethod", "time_this"]
|
|
19
18
|
|
|
19
|
+
import dataclasses
|
|
20
20
|
import datetime
|
|
21
21
|
import functools
|
|
22
|
-
import inspect
|
|
23
22
|
import logging
|
|
23
|
+
import os
|
|
24
|
+
import platform
|
|
25
|
+
import sys
|
|
24
26
|
import time
|
|
25
|
-
|
|
26
|
-
from
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Callable,
|
|
30
|
-
Collection,
|
|
31
|
-
Iterable,
|
|
32
|
-
Iterator,
|
|
33
|
-
MutableMapping,
|
|
34
|
-
Optional,
|
|
35
|
-
Tuple,
|
|
36
|
-
)
|
|
27
|
+
import traceback
|
|
28
|
+
from collections.abc import Callable, Collection, Iterable, Iterator, MutableMapping
|
|
29
|
+
from contextlib import contextmanager, suppress
|
|
30
|
+
from typing import TYPE_CHECKING, Any, TypeVar, overload
|
|
37
31
|
|
|
38
32
|
from astropy import units as u
|
|
39
33
|
|
|
34
|
+
from .introspection import find_outside_stacklevel
|
|
35
|
+
from .logging import LsstLoggers, _is_structlog_logger
|
|
40
36
|
from .usage import _get_current_rusage, get_current_mem_usage, get_peak_mem_usage
|
|
41
37
|
|
|
42
38
|
if TYPE_CHECKING:
|
|
43
39
|
import cProfile
|
|
44
40
|
|
|
45
|
-
from .logging import LsstLoggers
|
|
46
|
-
|
|
47
41
|
|
|
48
42
|
_LOG = logging.getLogger(__name__)
|
|
49
43
|
|
|
@@ -80,54 +74,15 @@ def _add_to_metadata(metadata: MutableMapping, name: str, value: Any) -> None:
|
|
|
80
74
|
metadata[name].append(value)
|
|
81
75
|
|
|
82
76
|
|
|
83
|
-
def _find_outside_stacklevel() -> int:
|
|
84
|
-
"""Find the stack level corresponding to caller code outside of this
|
|
85
|
-
module.
|
|
86
|
-
|
|
87
|
-
This can be passed directly to `logging.Logger.log()` to ensure
|
|
88
|
-
that log messages are issued as if they are coming from caller code.
|
|
89
|
-
|
|
90
|
-
Returns
|
|
91
|
-
-------
|
|
92
|
-
stacklevel : `int`
|
|
93
|
-
The stack level to use to refer to a caller outside of this module.
|
|
94
|
-
A ``stacklevel`` of ``1`` corresponds to the caller of this internal
|
|
95
|
-
function and that is the default expected by `logging.Logger.log()`.
|
|
96
|
-
|
|
97
|
-
Notes
|
|
98
|
-
-----
|
|
99
|
-
Intended to be called from the function that is going to issue a log
|
|
100
|
-
message. The result should be passed into `~logging.Logger.log` via the
|
|
101
|
-
keyword parameter ``stacklevel``.
|
|
102
|
-
"""
|
|
103
|
-
stacklevel = 1 # the default for `Logger.log`
|
|
104
|
-
for i, s in enumerate(inspect.stack()):
|
|
105
|
-
module = inspect.getmodule(s.frame)
|
|
106
|
-
if module is None:
|
|
107
|
-
# Stack frames sometimes hang around so explicilty delete.
|
|
108
|
-
del s
|
|
109
|
-
continue
|
|
110
|
-
if not module.__name__.startswith("lsst.utils"):
|
|
111
|
-
# 0 will be this function.
|
|
112
|
-
# 1 will be the caller which will be the default for `Logger.log`
|
|
113
|
-
# and so does not need adjustment.
|
|
114
|
-
stacklevel = i
|
|
115
|
-
break
|
|
116
|
-
# Stack frames sometimes hang around so explicilty delete.
|
|
117
|
-
del s
|
|
118
|
-
|
|
119
|
-
return stacklevel
|
|
120
|
-
|
|
121
|
-
|
|
122
77
|
def logPairs(
|
|
123
78
|
obj: Any,
|
|
124
|
-
pairs: Collection[
|
|
79
|
+
pairs: Collection[tuple[str, Any]],
|
|
125
80
|
logLevel: int = logging.DEBUG,
|
|
126
|
-
metadata:
|
|
127
|
-
logger:
|
|
128
|
-
stacklevel:
|
|
81
|
+
metadata: MutableMapping | None = None,
|
|
82
|
+
logger: LsstLoggers | None = None,
|
|
83
|
+
stacklevel: int | None = None,
|
|
129
84
|
) -> None:
|
|
130
|
-
"""Log ``(name, value)`` pairs to ``obj.metadata`` and ``obj.log
|
|
85
|
+
"""Log ``(name, value)`` pairs to ``obj.metadata`` and ``obj.log``.
|
|
131
86
|
|
|
132
87
|
Parameters
|
|
133
88
|
----------
|
|
@@ -147,7 +102,7 @@ def logPairs(
|
|
|
147
102
|
Log level (an `logging` level constant, such as `logging.DEBUG`).
|
|
148
103
|
metadata : `collections.abc.MutableMapping`, optional
|
|
149
104
|
Metadata object to write entries to. Ignored if `None`.
|
|
150
|
-
logger : `logging.Logger`
|
|
105
|
+
logger : `logging.Logger` or `lsst.utils.logging.LsstLogAdapter`
|
|
151
106
|
Log object to write entries to. Ignored if `None`.
|
|
152
107
|
stacklevel : `int`, optional
|
|
153
108
|
The stack level to pass to the logger to adjust which stack frame
|
|
@@ -158,15 +113,13 @@ def logPairs(
|
|
|
158
113
|
"""
|
|
159
114
|
if obj is not None:
|
|
160
115
|
if metadata is None:
|
|
161
|
-
|
|
116
|
+
with suppress(AttributeError):
|
|
162
117
|
metadata = obj.metadata
|
|
163
|
-
|
|
164
|
-
pass
|
|
118
|
+
|
|
165
119
|
if logger is None:
|
|
166
|
-
|
|
120
|
+
with suppress(AttributeError):
|
|
167
121
|
logger = obj.log
|
|
168
|
-
|
|
169
|
-
pass
|
|
122
|
+
|
|
170
123
|
strList = []
|
|
171
124
|
for name, value in pairs:
|
|
172
125
|
if metadata is not None:
|
|
@@ -179,7 +132,7 @@ def logPairs(
|
|
|
179
132
|
timer_logger = logging.getLogger("timer." + logger.name)
|
|
180
133
|
if timer_logger.isEnabledFor(logLevel):
|
|
181
134
|
if stacklevel is None:
|
|
182
|
-
stacklevel =
|
|
135
|
+
stacklevel = find_outside_stacklevel("lsst.utils")
|
|
183
136
|
else:
|
|
184
137
|
# Account for the caller stack.
|
|
185
138
|
stacklevel += 1
|
|
@@ -190,9 +143,9 @@ def logInfo(
|
|
|
190
143
|
obj: Any,
|
|
191
144
|
prefix: str,
|
|
192
145
|
logLevel: int = logging.DEBUG,
|
|
193
|
-
metadata:
|
|
194
|
-
logger:
|
|
195
|
-
stacklevel:
|
|
146
|
+
metadata: MutableMapping | None = None,
|
|
147
|
+
logger: LsstLoggers | None = None,
|
|
148
|
+
stacklevel: int | None = None,
|
|
196
149
|
) -> None:
|
|
197
150
|
"""Log timer information to ``obj.metadata`` and ``obj.log``.
|
|
198
151
|
|
|
@@ -216,7 +169,7 @@ def logInfo(
|
|
|
216
169
|
Log level (a `logging` level constant, such as `logging.DEBUG`).
|
|
217
170
|
metadata : `collections.abc.MutableMapping`, optional
|
|
218
171
|
Metadata object to write entries to, overriding ``obj.metadata``.
|
|
219
|
-
logger : `logging.Logger`
|
|
172
|
+
logger : `logging.Logger` or `lsst.utils.logging.LsstLogAdapter`
|
|
220
173
|
Log object to write entries to, overriding ``obj.log``.
|
|
221
174
|
stacklevel : `int`, optional
|
|
222
175
|
The stack level to pass to the logger to adjust which stack frame
|
|
@@ -246,18 +199,24 @@ def logInfo(
|
|
|
246
199
|
* Version 1: ``MaxResidentSetSize`` will be stored in bytes.
|
|
247
200
|
"""
|
|
248
201
|
if metadata is None and obj is not None:
|
|
249
|
-
|
|
202
|
+
with suppress(AttributeError):
|
|
250
203
|
metadata = obj.metadata
|
|
251
|
-
|
|
252
|
-
pass
|
|
204
|
+
|
|
253
205
|
if metadata is not None:
|
|
254
206
|
# Log messages already have timestamps.
|
|
255
|
-
|
|
207
|
+
if sys.version_info < (3, 11, 0):
|
|
208
|
+
now = datetime.datetime.utcnow()
|
|
209
|
+
else:
|
|
210
|
+
now = datetime.datetime.now(datetime.UTC)
|
|
211
|
+
utcStr = now.isoformat()
|
|
256
212
|
_add_to_metadata(metadata, name=prefix + "Utc", value=utcStr)
|
|
257
213
|
|
|
258
214
|
# Force a version number into the metadata.
|
|
259
215
|
# v1: Ensure that max_rss field is always bytes.
|
|
260
216
|
metadata["__version__"] = 1
|
|
217
|
+
|
|
218
|
+
# Add node information.
|
|
219
|
+
metadata["nodeName"] = platform.node()
|
|
261
220
|
if stacklevel is not None:
|
|
262
221
|
# Account for the caller of this routine not knowing that we
|
|
263
222
|
# are going one down in the stack.
|
|
@@ -274,23 +233,41 @@ def logInfo(
|
|
|
274
233
|
)
|
|
275
234
|
|
|
276
235
|
|
|
236
|
+
_F = TypeVar("_F", bound=Callable)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@overload
|
|
240
|
+
def timeMethod(_func: _F) -> _F: ...
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@overload
|
|
277
244
|
def timeMethod(
|
|
278
|
-
_func: Optional[Any] = None,
|
|
279
245
|
*,
|
|
280
|
-
metadata:
|
|
281
|
-
logger:
|
|
246
|
+
metadata: MutableMapping | None = None,
|
|
247
|
+
logger: LsstLoggers | None = None,
|
|
248
|
+
logLevel: int = logging.DEBUG,
|
|
249
|
+
) -> Callable[[_F], _F]: ...
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def timeMethod(
|
|
253
|
+
_func: Any | None = None,
|
|
254
|
+
*,
|
|
255
|
+
metadata: MutableMapping | None = None,
|
|
256
|
+
logger: LsstLoggers | None = None,
|
|
282
257
|
logLevel: int = logging.DEBUG,
|
|
283
258
|
) -> Callable:
|
|
284
259
|
"""Measure duration of a method.
|
|
285
260
|
|
|
261
|
+
Set LSST_UTILS_DISABLE_TIMER in your environment to disable this method.
|
|
262
|
+
|
|
286
263
|
Parameters
|
|
287
264
|
----------
|
|
288
|
-
|
|
265
|
+
_func : `~collections.abc.Callable` or `None`
|
|
289
266
|
The method to wrap.
|
|
290
267
|
metadata : `collections.abc.MutableMapping`, optional
|
|
291
268
|
Metadata to use as override if the instance object attached
|
|
292
269
|
to this timer does not support a ``metadata`` property.
|
|
293
|
-
logger : `logging.Logger`, optional
|
|
270
|
+
logger : `logging.Logger` or `lsst.utils.logging.LsstLogAdapter`, optional
|
|
294
271
|
Logger to use when the class containing the decorated method does not
|
|
295
272
|
have a ``log`` property.
|
|
296
273
|
logLevel : `int`, optional
|
|
@@ -358,37 +335,66 @@ def timeMethod(
|
|
|
358
335
|
|
|
359
336
|
return timeMethod_wrapper
|
|
360
337
|
|
|
338
|
+
if "LSST_UTILS_DISABLE_TIMER" in os.environ:
|
|
339
|
+
if _func is not None:
|
|
340
|
+
return _func
|
|
341
|
+
else:
|
|
342
|
+
|
|
343
|
+
def pass_through(func: Callable) -> Callable:
|
|
344
|
+
"""Pass through decorator.
|
|
345
|
+
|
|
346
|
+
This decorator will not appear in the call stack.
|
|
347
|
+
"""
|
|
348
|
+
return func
|
|
349
|
+
|
|
350
|
+
return pass_through
|
|
351
|
+
|
|
361
352
|
if _func is None:
|
|
362
353
|
return decorator_timer
|
|
363
354
|
else:
|
|
364
355
|
return decorator_timer(_func)
|
|
365
356
|
|
|
366
357
|
|
|
358
|
+
@dataclasses.dataclass
|
|
359
|
+
class _TimerResult:
|
|
360
|
+
duration: float = 0.0
|
|
361
|
+
"""Duration of context in seconds."""
|
|
362
|
+
mem_current_usage: float | None = None
|
|
363
|
+
"""Memory usage at end of context in requested units."""
|
|
364
|
+
mem_peak_delta: float | None = None
|
|
365
|
+
"""Peak differential between entering and leaving context."""
|
|
366
|
+
mem_current_delta: float | None = None
|
|
367
|
+
"""Difference in usage between entering and leaving context."""
|
|
368
|
+
|
|
369
|
+
|
|
367
370
|
@contextmanager
|
|
368
371
|
def time_this(
|
|
369
|
-
log:
|
|
370
|
-
msg:
|
|
372
|
+
log: LsstLoggers | None = None,
|
|
373
|
+
msg: str | None = None,
|
|
371
374
|
level: int = logging.DEBUG,
|
|
372
|
-
prefix:
|
|
375
|
+
prefix: str | None = "timer",
|
|
373
376
|
args: Iterable[Any] = (),
|
|
377
|
+
kwargs: dict[str, Any] | None = None,
|
|
374
378
|
mem_usage: bool = False,
|
|
375
379
|
mem_child: bool = False,
|
|
376
|
-
mem_unit: u.
|
|
380
|
+
mem_unit: u.Unit = u.byte,
|
|
377
381
|
mem_fmt: str = ".0f",
|
|
378
|
-
|
|
382
|
+
force_mem_usage: bool = False,
|
|
383
|
+
) -> Iterator[_TimerResult]:
|
|
379
384
|
"""Time the enclosed block and issue a log message.
|
|
380
385
|
|
|
381
386
|
Parameters
|
|
382
387
|
----------
|
|
383
388
|
log : `logging.Logger`, optional
|
|
384
389
|
Logger to use to report the timer message. The root logger will
|
|
385
|
-
be used if none is given.
|
|
390
|
+
be used if none is given. Is also allowed to be a `structlog` bound
|
|
391
|
+
logger.
|
|
386
392
|
msg : `str`, optional
|
|
387
393
|
Context to include in log message.
|
|
388
394
|
level : `int`, optional
|
|
389
395
|
Python logging level to use to issue the log message. If the
|
|
390
|
-
code block raises an exception the log message will
|
|
391
|
-
|
|
396
|
+
code block raises an exception the log message will include some
|
|
397
|
+
information about the exception that occurred.
|
|
392
398
|
prefix : `str`, optional
|
|
393
399
|
Prefix to use to prepend to the supplied logger to
|
|
394
400
|
create a new logger to use instead. No prefix is used if the value
|
|
@@ -396,9 +402,15 @@ def time_this(
|
|
|
396
402
|
args : iterable of any
|
|
397
403
|
Additional parameters passed to the log command that should be
|
|
398
404
|
written to ``msg``.
|
|
405
|
+
kwargs : `dict`, optional
|
|
406
|
+
Additional keyword parameters passed to the log command. If a Structlog
|
|
407
|
+
is used then these will be added to the structured data. Otherwise
|
|
408
|
+
they will be converted to a single string for inclusion in the log
|
|
409
|
+
message.
|
|
399
410
|
mem_usage : `bool`, optional
|
|
400
411
|
Flag indicating whether to include the memory usage in the report.
|
|
401
|
-
Defaults, to False.
|
|
412
|
+
Defaults, to False. Does nothing if the log message will not be
|
|
413
|
+
generated.
|
|
402
414
|
mem_child : `bool`, optional
|
|
403
415
|
Flag indication whether to include memory usage of the child processes.
|
|
404
416
|
mem_unit : `astropy.units.Unit`, optional
|
|
@@ -406,22 +418,49 @@ def time_this(
|
|
|
406
418
|
mem_fmt : `str`, optional
|
|
407
419
|
Format specifier to use when displaying values related to memory usage.
|
|
408
420
|
Defaults to '.0f'.
|
|
421
|
+
force_mem_usage : `bool`, optional
|
|
422
|
+
If `True` memory usage is always calculated even if the log message
|
|
423
|
+
will not be delivered. Use this if you want the information recorded
|
|
424
|
+
by the context manager.
|
|
425
|
+
|
|
426
|
+
Yields
|
|
427
|
+
------
|
|
428
|
+
timer_result : `_TimerResult`
|
|
429
|
+
Simple data class containing the duration of the block in seconds via
|
|
430
|
+
the ``duration`` property.
|
|
409
431
|
"""
|
|
410
432
|
if log is None:
|
|
411
433
|
log = logging.getLogger()
|
|
412
|
-
|
|
434
|
+
is_structlog = _is_structlog_logger(log)
|
|
435
|
+
if prefix and not is_structlog:
|
|
436
|
+
# Struct log loggers do not have a name property and so the prefix
|
|
437
|
+
# is not applied to them.
|
|
413
438
|
log_name = f"{prefix}.{log.name}" if not isinstance(log, logging.RootLogger) else prefix
|
|
414
439
|
log = logging.getLogger(log_name)
|
|
415
440
|
|
|
416
|
-
|
|
441
|
+
# Some structured data that can be used if we have been given a
|
|
442
|
+
# structlog logger.
|
|
443
|
+
structured_args: dict[str, Any] = {}
|
|
444
|
+
|
|
417
445
|
start = time.time()
|
|
446
|
+
|
|
447
|
+
if mem_usage and not log.isEnabledFor(level):
|
|
448
|
+
mem_usage = False
|
|
449
|
+
if force_mem_usage:
|
|
450
|
+
mem_usage = True
|
|
451
|
+
|
|
418
452
|
if mem_usage:
|
|
419
453
|
current_usages_start = get_current_mem_usage()
|
|
420
454
|
peak_usages_start = get_peak_mem_usage()
|
|
421
455
|
|
|
456
|
+
timer_result = _TimerResult()
|
|
457
|
+
errmsg = ""
|
|
422
458
|
try:
|
|
423
|
-
yield
|
|
424
|
-
|
|
459
|
+
yield timer_result
|
|
460
|
+
except BaseException as e:
|
|
461
|
+
frame, lineno = list(traceback.walk_tb(e.__traceback__))[-1]
|
|
462
|
+
errmsg = f"{e!r} @ {frame.f_code.co_filename}:{lineno}"
|
|
463
|
+
raise
|
|
425
464
|
finally:
|
|
426
465
|
end = time.time()
|
|
427
466
|
|
|
@@ -435,16 +474,18 @@ def time_this(
|
|
|
435
474
|
# mypy stop complaining when additional parameters will be added below.
|
|
436
475
|
params = list(args) if args else []
|
|
437
476
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
# this.
|
|
441
|
-
level = logging.ERROR
|
|
477
|
+
duration = end - start
|
|
478
|
+
timer_result.duration = duration
|
|
442
479
|
|
|
443
480
|
# Specify stacklevel to ensure the message is reported from the
|
|
444
481
|
# caller (1 is this file, 2 is contextlib, 3 is user)
|
|
445
|
-
params += (": " if msg else "",
|
|
482
|
+
params += (": " if msg else "", duration)
|
|
446
483
|
msg += "%sTook %.4f seconds"
|
|
447
|
-
|
|
484
|
+
structured_args["duration"] = duration
|
|
485
|
+
if errmsg:
|
|
486
|
+
params += (f" (timed code triggered exception of {errmsg!r})",)
|
|
487
|
+
msg += "%s"
|
|
488
|
+
if mem_usage:
|
|
448
489
|
current_usages_end = get_current_mem_usage()
|
|
449
490
|
peak_usages_end = get_peak_mem_usage()
|
|
450
491
|
|
|
@@ -463,18 +504,37 @@ def time_this(
|
|
|
463
504
|
_LOG.warning("Invalid memory unit '%s', using '%s' instead", mem_unit, u.byte)
|
|
464
505
|
mem_unit = u.byte
|
|
465
506
|
|
|
507
|
+
current_usage = current_usage.to(mem_unit)
|
|
508
|
+
current_delta = current_delta.to(mem_unit)
|
|
509
|
+
peak_delta = peak_delta.to(mem_unit)
|
|
510
|
+
|
|
511
|
+
timer_result.mem_current_usage = float(current_usage.value)
|
|
512
|
+
timer_result.mem_current_delta = float(current_delta.value)
|
|
513
|
+
timer_result.mem_peak_delta = float(peak_delta.value)
|
|
514
|
+
|
|
466
515
|
msg += (
|
|
467
|
-
f"; current memory usage: {current_usage
|
|
468
|
-
f", delta: {current_delta
|
|
469
|
-
f", peak delta: {peak_delta
|
|
516
|
+
f"; current memory usage: {current_usage:{mem_fmt}}"
|
|
517
|
+
f", delta: {current_delta:{mem_fmt}}"
|
|
518
|
+
f", peak delta: {peak_delta:{mem_fmt}}"
|
|
470
519
|
)
|
|
471
|
-
|
|
520
|
+
structured_args["mem_current_usage"] = float(current_usage.value)
|
|
521
|
+
structured_args["mem_current_delta"] = float(current_delta.value)
|
|
522
|
+
structured_args["mem_peak_delta"] = float(peak_delta.value)
|
|
523
|
+
if not is_structlog:
|
|
524
|
+
# Can only use the structured content if we have structlog logger
|
|
525
|
+
# but stacklevel is only supported by standard loggers.
|
|
526
|
+
structured_args = {"stacklevel": 3}
|
|
527
|
+
if kwargs is not None:
|
|
528
|
+
msg += " %s"
|
|
529
|
+
params += ("; ".join(f"{k}={v!r}" for k, v in kwargs.items()),)
|
|
530
|
+
elif kwargs:
|
|
531
|
+
structured_args.update(kwargs)
|
|
532
|
+
|
|
533
|
+
log.log(level, msg, *params, **structured_args)
|
|
472
534
|
|
|
473
535
|
|
|
474
536
|
@contextmanager
|
|
475
|
-
def profile(
|
|
476
|
-
filename: Optional[str], log: Optional[logging.Logger] = None
|
|
477
|
-
) -> Iterator[Optional[cProfile.Profile]]:
|
|
537
|
+
def profile(filename: str | None, log: LsstLoggers | None = None) -> Iterator[cProfile.Profile | None]:
|
|
478
538
|
"""Profile the enclosed code block and save the result to a file.
|
|
479
539
|
|
|
480
540
|
Parameters
|
|
@@ -482,7 +542,7 @@ def profile(
|
|
|
482
542
|
filename : `str` or `None`
|
|
483
543
|
Filename to which to write profile (profiling disabled if `None` or
|
|
484
544
|
empty string).
|
|
485
|
-
log : `logging.Logger`, optional
|
|
545
|
+
log : `logging.Logger` or `lsst.utils.logging.LsstLogAdapter`, optional
|
|
486
546
|
Log object for logging the profile operations.
|
|
487
547
|
|
|
488
548
|
Yields
|
|
@@ -523,3 +583,40 @@ def profile(
|
|
|
523
583
|
profile.dump_stats(filename)
|
|
524
584
|
if log is not None:
|
|
525
585
|
log.info("cProfile stats written to %s", filename)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def duration_from_timeMethod(
|
|
589
|
+
metadata: MutableMapping | None, method_name: str, clock: str = "Cpu"
|
|
590
|
+
) -> float | None:
|
|
591
|
+
"""Parse the metadata entries from ``timeMethod`` and return a duration.
|
|
592
|
+
|
|
593
|
+
Parameters
|
|
594
|
+
----------
|
|
595
|
+
metadata : `collections.abc.MutableMapping`
|
|
596
|
+
The Task metadata that timing metrics were added to.
|
|
597
|
+
method_name : `str`
|
|
598
|
+
Name of the timed method to extract a duration for.
|
|
599
|
+
clock : `str`, optional
|
|
600
|
+
Options are "Cpu", "User", "System", or "Utc".
|
|
601
|
+
|
|
602
|
+
Returns
|
|
603
|
+
-------
|
|
604
|
+
duration : `float`
|
|
605
|
+
The time elapsed between the start and end of the timed method.
|
|
606
|
+
"""
|
|
607
|
+
if metadata is None:
|
|
608
|
+
return None
|
|
609
|
+
if clock.lower() == "utc":
|
|
610
|
+
start = metadata[method_name + "StartUtc"]
|
|
611
|
+
end = metadata[method_name + "EndUtc"]
|
|
612
|
+
else:
|
|
613
|
+
start = metadata[method_name + "Start" + clock + "Time"]
|
|
614
|
+
end = metadata[method_name + "End" + clock + "Time"]
|
|
615
|
+
if isinstance(start, list):
|
|
616
|
+
start = start[-1]
|
|
617
|
+
if isinstance(end, list):
|
|
618
|
+
end = end[-1]
|
|
619
|
+
if isinstance(start, str) and isinstance(end, str):
|
|
620
|
+
return (datetime.datetime.fromisoformat(end) - datetime.datetime.fromisoformat(start)).total_seconds()
|
|
621
|
+
else:
|
|
622
|
+
return end - start
|
lsst/utils/usage.py
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
# Use of this source code is governed by a 3-clause BSD-style
|
|
10
10
|
# license that can be found in the LICENSE file.
|
|
11
11
|
|
|
12
|
-
"""Utilities for measuring resource consumption.
|
|
13
|
-
|
|
12
|
+
"""Utilities for measuring resource consumption."""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
14
15
|
|
|
15
16
|
__all__ = ["get_current_mem_usage", "get_peak_mem_usage"]
|
|
16
17
|
|
|
@@ -18,7 +19,6 @@ import dataclasses
|
|
|
18
19
|
import platform
|
|
19
20
|
import resource
|
|
20
21
|
import time
|
|
21
|
-
from typing import Dict, Tuple, Union
|
|
22
22
|
|
|
23
23
|
import astropy.units as u
|
|
24
24
|
import psutil
|
|
@@ -48,7 +48,7 @@ def _get_rusage_multiplier() -> int:
|
|
|
48
48
|
_RUSAGE_MEMORY_MULTIPLIER = _get_rusage_multiplier()
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def get_current_mem_usage() ->
|
|
51
|
+
def get_current_mem_usage() -> tuple[u.Quantity, u.Quantity]:
|
|
52
52
|
"""Report current memory usage.
|
|
53
53
|
|
|
54
54
|
Returns
|
|
@@ -72,7 +72,7 @@ def get_current_mem_usage() -> Tuple[u.Quantity, u.Quantity]:
|
|
|
72
72
|
return usage_main, usage_child
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def get_peak_mem_usage() ->
|
|
75
|
+
def get_peak_mem_usage() -> tuple[u.Quantity, u.Quantity]:
|
|
76
76
|
"""Report peak memory usage.
|
|
77
77
|
|
|
78
78
|
Returns
|
|
@@ -112,7 +112,7 @@ class _UsageInfo:
|
|
|
112
112
|
voluntaryContextSwitches: int
|
|
113
113
|
involuntaryContextSwitches: int
|
|
114
114
|
|
|
115
|
-
def dict(self) ->
|
|
115
|
+
def dict(self) -> dict[str, float | int]:
|
|
116
116
|
return dataclasses.asdict(self)
|
|
117
117
|
|
|
118
118
|
|
lsst/utils/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "
|
|
2
|
+
__version__ = "29.2025.4800"
|