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 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 )
64 def validate(self):
65 super().validate()
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
82# Expose TimingMetricConfig name because config-writers expect it
83TimingMetricConfig = TimeMethodMetricConfig
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.
91 Parameters
92 ----------
93 args
94 kwargs
95 Constructor parameters are the same as for
96 `lsst.verify.tasks.MetricTask`.
97 """
99 ConfigClass = TimingMetricConfig
100 _DefaultName = "timingMetric"
102 @classmethod
103 def getInputMetadataKeys(cls, config):
104 """Get search strings for the metadata.
106 Parameters
107 ----------
108 config : ``cls.ConfigClass``
109 Configuration for this task.
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()`.
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 }
129 def makeMeasurement(self, timings):
130 """Compute a wall-clock measurement from metadata provided by
131 `lsst.pipe.base.timeMethod`.
133 Parameters
134 ----------
135 timings : `dict` [`str`, any]
136 A representation of the metadata passed to `run`. The `dict` has
137 the following keys:
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`).
147 Returns
148 -------
149 measurement : `lsst.verify.Measurement` or `None`
150 The running time of the target method.
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
177# Expose MemoryMetricConfig name because config-writers expect it
178MemoryMetricConfig = TimeMethodMetricConfig
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.
186 Parameters
187 ----------
188 args
189 kwargs
190 Constructor parameters are the same as for
191 `lsst.verify.tasks.MetricTask`.
192 """
194 ConfigClass = MemoryMetricConfig
195 _DefaultName = "memoryMetric"
197 @classmethod
198 def getInputMetadataKeys(cls, config):
199 """Get search strings for the metadata.
201 Parameters
202 ----------
203 config : ``cls.ConfigClass``
204 Configuration for this task.
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()`.
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"}
218 def makeMeasurement(self, memory):
219 """Compute a maximum resident set size measurement from metadata
220 provided by `lsst.pipe.base.timeMethod`.
222 Parameters
223 ----------
224 memory : `dict` [`str`, any]
225 A representation of the metadata passed to `run`. Each `dict` has
226 the following keys:
228 ``"EndMemory"``
229 The memory usage at the end of the method (`int` or `None`).
231 Returns
232 -------
233 measurement : `lsst.verify.Measurement` or `None`
234 The maximum memory usage of the target method.
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
256 def _addUnits(self, memory):
257 """Represent memory usage in correct units.
259 Parameters
260 ----------
261 memory : `int`
262 The memory usage as returned by `resource.getrusage`, in
263 platform-dependent units.
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