Coverage for python/lsst/dax/apdb/timer.py: 19%

78 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-27 03:01 -0700

1# This file is part of dax_apdb. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <http://www.gnu.org/licenses/>. 

21 

22"""Module with methods to return timing information. 

23 

24This was developed as a part of a prototype for performance studies. It 

25could probably be removed in the production system. 

26""" 

27 

28from __future__ import annotations 

29 

30import logging 

31import resource 

32import time 

33from collections.abc import Mapping 

34from typing import Any 

35 

36from .monitor import MonAgent 

37 

38_TagsType = Mapping[str, str | int] 

39 

40 

41class Timer: 

42 """Instance of this class can be used to track consumed time. 

43 

44 Parameters 

45 ---------- 

46 name : `str` 

47 Timer name, will be use for reporting to both monitoring and logging. 

48 Typically the name should look like an identifier for ease of use with 

49 downstream monitoring software. 

50 *args : `MonAgent` or `logging.Logger` 

51 Positional arguments can include a combination of `MonAgent` and 

52 `logging.Logger` instances. They will be used to report accumulated 

53 times on exit from context or by calling `dump` method directly. 

54 tags : `~collections.abc.Mapping` [`str`, `str` or `int`], optional 

55 Keyword argument, additional tags added to monitoring report and 

56 logging output. 

57 log_level : `int`, optional 

58 Keyword argument, level used for logging output, default is 

59 `logging.INFO`. 

60 

61 Notes 

62 ----- 

63 This class is also a context manager and can be used in a `with` statement. 

64 By default it prints consumed CPU time and real time spent in a context. 

65 

66 Example: 

67 

68 with Timer("SelectTimer", logger): 

69 engine.execute('SELECT ...') 

70 

71 """ 

72 

73 def __init__( 

74 self, 

75 name: str, 

76 *args: MonAgent | logging.Logger, 

77 tags: _TagsType | None = None, 

78 log_level: int = logging.INFO, 

79 ): 

80 self._name = name 

81 self._mon_agents: list[MonAgent] = [] 

82 self._loggers: list[logging.Logger] = [] 

83 self._tags = tags 

84 self._log_level = log_level 

85 

86 for arg in args: 

87 if isinstance(arg, MonAgent): 

88 self._mon_agents.append(arg) 

89 elif isinstance(arg, logging.Logger): 

90 self._loggers.append(arg) 

91 

92 self._startReal = -1.0 

93 self._startUser = -1.0 

94 self._startSys = -1.0 

95 self._sumReal = 0.0 

96 self._sumUser = 0.0 

97 self._sumSys = 0.0 

98 

99 def start(self) -> Timer: 

100 """Start timer.""" 

101 self._startReal = time.time() 

102 ru = resource.getrusage(resource.RUSAGE_SELF) 

103 self._startUser = ru.ru_utime 

104 self._startSys = ru.ru_stime 

105 return self 

106 

107 def stop(self) -> Timer: 

108 """Stop timer.""" 

109 if self._startReal > 0: 

110 self._sumReal += time.time() - self._startReal 

111 ru = resource.getrusage(resource.RUSAGE_SELF) 

112 self._sumUser += ru.ru_utime - self._startUser 

113 self._sumSys += ru.ru_stime - self._startSys 

114 self._startReal = -1.0 

115 self._startUser = -1.0 

116 self._startSys = -1.0 

117 return self 

118 

119 def dump(self) -> Timer: 

120 """Dump timer statistics""" 

121 for logger in self._loggers: 

122 logger.log(self._log_level, "%s", self) 

123 for agent in self._mon_agents: 

124 agent.add_record(self._name, values=self.as_dict(), tags=self._tags) 

125 return self 

126 

127 def accumulated(self) -> tuple[float, float, float]: 

128 """Return accumulated real, user, and system times in seconds.""" 

129 real = self._sumReal 

130 user = self._sumUser 

131 sys = self._sumSys 

132 if self._startReal > 0: 

133 real += time.time() - self._startReal 

134 ru = resource.getrusage(resource.RUSAGE_SELF) 

135 user += ru.ru_utime - self._startUser 

136 sys += ru.ru_stime - self._startSys 

137 return (real, user, sys) 

138 

139 def as_dict(self, prefix: str = "") -> dict[str, float]: 

140 """Return accumulated real, user, and system times as dictionary.""" 

141 real, user, sys = self.accumulated() 

142 return { 

143 f"{prefix}real": real, 

144 f"{prefix}user": user, 

145 f"{prefix}sys": sys, 

146 } 

147 

148 def __str__(self) -> str: 

149 real, user, sys = self.accumulated() 

150 info = "real=%.3f user=%.3f sys=%.3f" % (real, user, sys) 

151 if self._name: 

152 info = self._name + ": " + info 

153 if self._tags: 

154 info += f" (tags={self._tags})" 

155 return info 

156 

157 def __enter__(self) -> Timer: 

158 """Enter context, start timer""" 

159 self.start() 

160 return self 

161 

162 def __exit__(self, exc_type: type | None, exc_val: Any, exc_tb: Any) -> Any: 

163 """Exit context, stop and dump timer""" 

164 self.stop() 

165 if exc_type is None: 

166 self.dump() 

167 return False