Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# 

2# This file is part of verify. 

3# 

4# Developed for the LSST Data Management System. 

5# This product includes software developed by the LSST Project 

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

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

8# for details of code ownership. 

9# 

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

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

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

13# (at your option) any later version. 

14# 

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

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

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

18# GNU General Public License for more details. 

19# 

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

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

22# 

23 

24"""Code for measuring metrics that apply to any Task. 

25""" 

26 

27__all__ = ["TimingMetricConfig", "TimingMetricTask", 

28 "MemoryMetricConfig", "MemoryMetricTask", 

29 ] 

30 

31import resource 

32import sys 

33import warnings 

34 

35import astropy.units as u 

36 

37import lsst.pex.config as pexConfig 

38 

39from lsst.verify import Measurement, Datum 

40from lsst.verify.gen2tasks.metricRegistry import registerMultiple 

41from lsst.verify.tasks import MetricComputationError, MetadataMetricTask, \ 

42 MetadataMetricConfig 

43 

44 

45class TimeMethodMetricConfig(MetadataMetricConfig): 

46 """Common config fields for metrics based on `~lsst.pipe.base.timeMethod`. 

47 

48 These fields let metrics distinguish between different methods that have 

49 been decorated with `~lsst.pipe.base.timeMethod`. 

50 """ 

51 target = pexConfig.Field( 

52 dtype=str, 

53 doc="The method to profile, optionally prefixed by one or more tasks " 

54 "in the format of `lsst.pipe.base.Task.getFullMetadata()`.") 

55 metric = pexConfig.Field( 

56 dtype=str, 

57 doc="The fully qualified name of the metric to store the " 

58 "profiling information.", 

59 deprecated="This field has been replaced by connections.package and " 

60 "connections.metric. It will be removed along " 

61 "with daf_persistence." 

62 ) 

63 

64 def validate(self): 

65 super().validate() 

66 

67 if self.metric: 

68 if self.metric != self.connections.package \ 

69 + "." + self.connections.metric: 

70 warnings.warn( 

71 "config.metric is deprecated; set connections.package " 

72 "and connections.metric instead.", 

73 FutureWarning) 

74 try: 

75 self.connections.package, self.connections.metric \ 

76 = self.metric.split(".") 

77 except ValueError: 

78 self.connections.package = "" 

79 self.connections.metric = self.metric 

80 

81 

82# Expose TimingMetricConfig name because config-writers expect it 

83TimingMetricConfig = TimeMethodMetricConfig 

84 

85 

86@registerMultiple("timing") 

87class TimingMetricTask(MetadataMetricTask): 

88 """A Task that computes a wall-clock time using metadata produced by the 

89 `lsst.pipe.base.timeMethod` decorator. 

90 

91 Parameters 

92 ---------- 

93 args 

94 kwargs 

95 Constructor parameters are the same as for 

96 `lsst.verify.tasks.MetricTask`. 

97 """ 

98 

99 ConfigClass = TimingMetricConfig 

100 _DefaultName = "timingMetric" 

101 

102 @classmethod 

103 def getInputMetadataKeys(cls, config): 

104 """Get search strings for the metadata. 

105 

106 Parameters 

107 ---------- 

108 config : ``cls.ConfigClass`` 

109 Configuration for this task. 

110 

111 Returns 

112 ------- 

113 keys : `dict` 

114 A dictionary of keys, optionally prefixed by one or more tasks in 

115 the format of `lsst.pipe.base.Task.getFullMetadata()`. 

116 

117 ``"StartTime"`` 

118 The key for when the target method started (`str`). 

119 ``"EndTime"`` 

120 The key for when the target method ended (`str`). 

121 """ 

122 keyBase = config.target 

123 return {"StartTime": keyBase + "StartCpuTime", 

124 "EndTime": keyBase + "EndCpuTime", 

125 "StartTimestamp": keyBase + "StartUtc", 

126 "EndTimestamp": keyBase + "EndUtc", 

127 } 

128 

129 def makeMeasurement(self, timings): 

130 """Compute a wall-clock measurement from metadata provided by 

131 `lsst.pipe.base.timeMethod`. 

132 

133 Parameters 

134 ---------- 

135 timings : `dict` [`str`, any] 

136 A representation of the metadata passed to `run`. The `dict` has 

137 the following keys: 

138 

139 ``"StartTime"`` 

140 The time the target method started (`float` or `None`). 

141 ``"EndTime"`` 

142 The time the target method ended (`float` or `None`). 

143 ``"StartTimestamp"``, ``"EndTimestamp"`` 

144 The start and end timestamps, in an ISO 8601-compliant format 

145 (`str` or `None`). 

146 

147 Returns 

148 ------- 

149 measurement : `lsst.verify.Measurement` or `None` 

150 The running time of the target method. 

151 

152 Raises 

153 ------ 

154 MetricComputationError 

155 Raised if the timing metadata are invalid. 

156 """ 

157 if timings["StartTime"] is not None or timings["EndTime"] is not None: 

158 try: 

159 totalTime = timings["EndTime"] - timings["StartTime"] 

160 except TypeError: 

161 raise MetricComputationError("Invalid metadata") 

162 else: 

163 meas = Measurement(self.config.metricName, 

164 totalTime * u.second) 

165 meas.notes["estimator"] = "pipe.base.timeMethod" 

166 if timings["StartTimestamp"]: 

167 meas.extras["start"] = Datum(timings["StartTimestamp"]) 

168 if timings["EndTimestamp"]: 

169 meas.extras["end"] = Datum(timings["EndTimestamp"]) 

170 return meas 

171 else: 

172 self.log.info("Nothing to do: no timing information for %s found.", 

173 self.config.target) 

174 return None 

175 

176 

177# Expose MemoryMetricConfig name because config-writers expect it 

178MemoryMetricConfig = TimeMethodMetricConfig 

179 

180 

181@registerMultiple("memory") 

182class MemoryMetricTask(MetadataMetricTask): 

183 """A Task that computes the maximum resident set size using metadata 

184 produced by the `lsst.pipe.base.timeMethod` decorator. 

185 

186 Parameters 

187 ---------- 

188 args 

189 kwargs 

190 Constructor parameters are the same as for 

191 `lsst.verify.tasks.MetricTask`. 

192 """ 

193 

194 ConfigClass = MemoryMetricConfig 

195 _DefaultName = "memoryMetric" 

196 

197 @classmethod 

198 def getInputMetadataKeys(cls, config): 

199 """Get search strings for the metadata. 

200 

201 Parameters 

202 ---------- 

203 config : ``cls.ConfigClass`` 

204 Configuration for this task. 

205 

206 Returns 

207 ------- 

208 keys : `dict` 

209 A dictionary of keys, optionally prefixed by one or more tasks in 

210 the format of `lsst.pipe.base.Task.getFullMetadata()`. 

211 

212 ``"EndMemory"`` 

213 The key for the memory usage at the end of the method (`str`). 

214 """ 

215 keyBase = config.target 

216 return {"EndMemory": keyBase + "EndMaxResidentSetSize"} 

217 

218 def makeMeasurement(self, memory): 

219 """Compute a maximum resident set size measurement from metadata 

220 provided by `lsst.pipe.base.timeMethod`. 

221 

222 Parameters 

223 ---------- 

224 memory : `dict` [`str`, any] 

225 A representation of the metadata passed to `run`. Each `dict` has 

226 the following keys: 

227 

228 ``"EndMemory"`` 

229 The memory usage at the end of the method (`int` or `None`). 

230 

231 Returns 

232 ------- 

233 measurement : `lsst.verify.Measurement` or `None` 

234 The maximum memory usage of the target method. 

235 

236 Raises 

237 ------ 

238 MetricComputationError 

239 Raised if the memory metadata are invalid. 

240 """ 

241 if memory["EndMemory"] is not None: 

242 try: 

243 maxMemory = int(memory["EndMemory"]) 

244 except (ValueError, TypeError) as e: 

245 raise MetricComputationError("Invalid metadata") from e 

246 else: 

247 meas = Measurement(self.config.metricName, 

248 self._addUnits(maxMemory)) 

249 meas.notes['estimator'] = 'pipe.base.timeMethod' 

250 return meas 

251 else: 

252 self.log.info("Nothing to do: no memory information for %s found.", 

253 self.config.target) 

254 return None 

255 

256 def _addUnits(self, memory): 

257 """Represent memory usage in correct units. 

258 

259 Parameters 

260 ---------- 

261 memory : `int` 

262 The memory usage as returned by `resource.getrusage`, in 

263 platform-dependent units. 

264 

265 Returns 

266 ------- 

267 memory : `astropy.units.Quantity` 

268 The memory usage in absolute units. 

269 """ 

270 if sys.platform.startswith('darwin'): 

271 # MacOS uses bytes 

272 return memory * u.byte 

273 elif sys.platform.startswith('sunos') \ 

274 or sys.platform.startswith('solaris'): 

275 # Solaris and SunOS use pages 

276 return memory * resource.getpagesize() * u.byte 

277 else: 

278 # Assume Linux, which uses kibibytes 

279 return memory * u.kibibyte