Coverage for python/lsst/verify/tasks/commonMetrics.py : 36%

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#
24"""Code for measuring metrics that apply to any Task.
25"""
27__all__ = ["TimingMetricConfig", "TimingMetricTask",
28 "MemoryMetricConfig", "MemoryMetricTask",
29 ]
31import resource
32import sys
33import warnings
35import astropy.units as u
37import lsst.pex.config as pexConfig
39from lsst.verify import Measurement, Datum
40from lsst.verify.gen2tasks.metricRegistry import registerMultiple
41from lsst.verify.tasks import MetricComputationError, MetadataMetricTask, \
42 MetadataMetricConfig
45class TimeMethodMetricConfig(MetadataMetricConfig):
46 """Common config fields for metrics based on `~lsst.pipe.base.timeMethod`.
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 optional=True,
58 doc="The fully qualified name of the metric to store the "
59 "profiling information.",
60 deprecated="This field has been replaced by connections.package and "
61 "connections.metric. It will be removed along "
62 "with daf_persistence."
63 )
65 def validate(self):
66 super().validate()
68 if self.metric:
69 if self.metric != self.connections.package \
70 + "." + self.connections.metric:
71 warnings.warn(
72 "config.metric is deprecated; set connections.package "
73 "and connections.metric instead.",
74 FutureWarning)
75 try:
76 self.connections.package, self.connections.metric \
77 = self.metric.split(".")
78 except ValueError:
79 self.connections.package = ""
80 self.connections.metric = self.metric
83# Expose TimingMetricConfig name because config-writers expect it
84TimingMetricConfig = TimeMethodMetricConfig
87@registerMultiple("timing")
88class TimingMetricTask(MetadataMetricTask):
89 """A Task that computes a wall-clock time using metadata produced by the
90 `lsst.pipe.base.timeMethod` decorator.
92 Parameters
93 ----------
94 args
95 kwargs
96 Constructor parameters are the same as for
97 `lsst.verify.tasks.MetricTask`.
98 """
100 ConfigClass = TimingMetricConfig
101 _DefaultName = "timingMetric"
103 @classmethod
104 def getInputMetadataKeys(cls, config):
105 """Get search strings for the metadata.
107 Parameters
108 ----------
109 config : ``cls.ConfigClass``
110 Configuration for this task.
112 Returns
113 -------
114 keys : `dict`
115 A dictionary of keys, optionally prefixed by one or more tasks in
116 the format of `lsst.pipe.base.Task.getFullMetadata()`.
118 ``"StartTime"``
119 The key for when the target method started (`str`).
120 ``"EndTime"``
121 The key for when the target method ended (`str`).
122 """
123 keyBase = config.target
124 return {"StartTime": keyBase + "StartCpuTime",
125 "EndTime": keyBase + "EndCpuTime",
126 "StartTimestamp": keyBase + "StartUtc",
127 "EndTimestamp": keyBase + "EndUtc",
128 }
130 def makeMeasurement(self, timings):
131 """Compute a wall-clock measurement from metadata provided by
132 `lsst.pipe.base.timeMethod`.
134 Parameters
135 ----------
136 timings : `dict` [`str`, any]
137 A representation of the metadata passed to `run`. The `dict` has
138 the following keys:
140 ``"StartTime"``
141 The time the target method started (`float` or `None`).
142 ``"EndTime"``
143 The time the target method ended (`float` or `None`).
144 ``"StartTimestamp"``, ``"EndTimestamp"``
145 The start and end timestamps, in an ISO 8601-compliant format
146 (`str` or `None`).
148 Returns
149 -------
150 measurement : `lsst.verify.Measurement` or `None`
151 The running time of the target method.
153 Raises
154 ------
155 MetricComputationError
156 Raised if the timing metadata are invalid.
157 """
158 if timings["StartTime"] is not None or timings["EndTime"] is not None:
159 try:
160 totalTime = timings["EndTime"] - timings["StartTime"]
161 except TypeError:
162 raise MetricComputationError("Invalid metadata")
163 else:
164 meas = Measurement(self.config.metricName,
165 totalTime * u.second)
166 meas.notes["estimator"] = "pipe.base.timeMethod"
167 if timings["StartTimestamp"]:
168 meas.extras["start"] = Datum(timings["StartTimestamp"])
169 if timings["EndTimestamp"]:
170 meas.extras["end"] = Datum(timings["EndTimestamp"])
171 return meas
172 else:
173 self.log.info("Nothing to do: no timing information for %s found.",
174 self.config.target)
175 return None
178# Expose MemoryMetricConfig name because config-writers expect it
179MemoryMetricConfig = TimeMethodMetricConfig
182@registerMultiple("memory")
183class MemoryMetricTask(MetadataMetricTask):
184 """A Task that computes the maximum resident set size using metadata
185 produced by the `lsst.pipe.base.timeMethod` decorator.
187 Parameters
188 ----------
189 args
190 kwargs
191 Constructor parameters are the same as for
192 `lsst.verify.tasks.MetricTask`.
193 """
195 ConfigClass = MemoryMetricConfig
196 _DefaultName = "memoryMetric"
198 @classmethod
199 def getInputMetadataKeys(cls, config):
200 """Get search strings for the metadata.
202 Parameters
203 ----------
204 config : ``cls.ConfigClass``
205 Configuration for this task.
207 Returns
208 -------
209 keys : `dict`
210 A dictionary of keys, optionally prefixed by one or more tasks in
211 the format of `lsst.pipe.base.Task.getFullMetadata()`.
213 ``"EndMemory"``
214 The key for the memory usage at the end of the method (`str`).
215 """
216 keyBase = config.target
217 return {"EndMemory": keyBase + "EndMaxResidentSetSize"}
219 def makeMeasurement(self, memory):
220 """Compute a maximum resident set size measurement from metadata
221 provided by `lsst.pipe.base.timeMethod`.
223 Parameters
224 ----------
225 memory : `dict` [`str`, any]
226 A representation of the metadata passed to `run`. Each `dict` has
227 the following keys:
229 ``"EndMemory"``
230 The memory usage at the end of the method (`int` or `None`).
232 Returns
233 -------
234 measurement : `lsst.verify.Measurement` or `None`
235 The maximum memory usage of the target method.
237 Raises
238 ------
239 MetricComputationError
240 Raised if the memory metadata are invalid.
241 """
242 if memory["EndMemory"] is not None:
243 try:
244 maxMemory = int(memory["EndMemory"])
245 except (ValueError, TypeError) as e:
246 raise MetricComputationError("Invalid metadata") from e
247 else:
248 meas = Measurement(self.config.metricName,
249 self._addUnits(maxMemory))
250 meas.notes['estimator'] = 'pipe.base.timeMethod'
251 return meas
252 else:
253 self.log.info("Nothing to do: no memory information for %s found.",
254 self.config.target)
255 return None
257 def _addUnits(self, memory):
258 """Represent memory usage in correct units.
260 Parameters
261 ----------
262 memory : `int`
263 The memory usage as returned by `resource.getrusage`, in
264 platform-dependent units.
266 Returns
267 -------
268 memory : `astropy.units.Quantity`
269 The memory usage in absolute units.
270 """
271 if sys.platform.startswith('darwin'):
272 # MacOS uses bytes
273 return memory * u.byte
274 elif sys.platform.startswith('sunos') \
275 or sys.platform.startswith('solaris'):
276 # Solaris and SunOS use pages
277 return memory * resource.getpagesize() * u.byte
278 else:
279 # Assume Linux, which uses kibibytes
280 return memory * u.kibibyte