Coverage for python / lsst / analysis / tools / bin / verifyToSasquatch.py: 30%
53 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-07 08:53 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-07 08:53 +0000
1# This file is part of analysis_tools.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://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 <https://www.gnu.org/licenses/>.
22__all__ = [
23 "main",
24]
26import argparse
27import datetime
28import logging
29from collections import defaultdict
30from collections.abc import Iterable, Mapping
32import lsst.verify
33from lsst.analysis.tools.interfaces import MetricMeasurementBundle
34from lsst.analysis.tools.interfaces.datastore import SasquatchDispatcher
35from lsst.daf.butler import Butler, DataCoordinate, DatasetRef
36from lsst.utils.argparsing import AppendDict
38logging.basicConfig()
39_LOG = logging.getLogger(__name__)
40_LOG.setLevel(logging.INFO)
43_BASE_URL = "https://usdf-rsp-dev.slac.stanford.edu/sasquatch-rest-proxy/"
46def makeParser():
47 parser = argparse.ArgumentParser(
48 description="""Upload Measurement datasets from a Butler repository to Sasquatch.
50 This script handles metric values persisted directly using
51 lsst.verify tooling. It is neither necessary nor useful for
52 MetricMeasurementBundles created using analysis_tools
53 tooling, and is provided solely for backwards compatibility
54 with the older system.
55 """,
56 add_help=True,
57 )
58 parser.add_argument("repo", help="The Butler repository from which to upload metric values.")
59 parser.add_argument(
60 "collections",
61 action="extend",
62 nargs="+",
63 help="The collection(s) in which to search for metric values. These can "
64 "be specified in any notation recognized by Middleware.",
65 )
66 parser.add_argument("--dataset", required=True, help="The dataset on which the metrics were measured.")
67 parser.add_argument(
68 "--test",
69 action="store_true",
70 help="Run this command while uploading to the lsst.debug test "
71 "namespace. Any --namespace argument is ignored.",
72 )
73 parser.add_argument(
74 "--where", default="", help="Butler query to select metric values for upload (default: all values)."
75 )
76 parser.add_argument(
77 "--date-created",
78 type=datetime.datetime.fromisoformat,
79 help="ISO8601 formatted datetime in UTC for the Measurement creation "
80 "date, e.g. 2021-06-30T22:28:25Z. If not provided, the run time or "
81 "current time is used.",
82 )
83 parser.add_argument(
84 "--extra",
85 action=AppendDict,
86 help="Extra field (in the form key=value) to be added to any records "
87 "uploaded to Sasquatch. See SasquatchDispatcher.dispatch and "
88 ".dispatchRef for more details. The --extra argument can be passed "
89 "multiple times.",
90 )
92 api_group = parser.add_argument_group("Sasquatch API arguments")
93 api_group.add_argument(
94 "--namespace",
95 default="lsst.dm",
96 help="The Sasquatch namespace to which to upload the metric values (default: lsst.dm)",
97 )
98 api_group.add_argument(
99 "--url",
100 dest="base_url",
101 default=_BASE_URL,
102 help=f"Root URL of Sasquatch proxy server (default: {_BASE_URL}).",
103 )
104 api_group.add_argument("--token", default="na", help="Authentication token for the proxy server.")
106 return parser
109def _bundle_metrics(
110 butler: Butler, metricValues: Iterable[DatasetRef]
111) -> Mapping[tuple[str, str, DataCoordinate], MetricMeasurementBundle]:
112 """Organize free metric values into metric bundles while preserving as much
113 information as practical.
115 Parameters
116 ----------
117 butler : `lsst.daf.butler.Butler`
118 The Butler repository containing the metric values.
119 metricValues : `~collections.abc.Iterable` [`lsst.daf.butler.DatasetRef`]
120 The datasets to bundle. All references must point to ``MetricValue``
121 datasets.
123 Returns
124 -------
125 bundles : `~collections.abc.Mapping`
126 A collection of
127 `lsst.analysis.tools.interfaces.MetricMeasurementBundle`, one for each
128 combination of distinct metadata. The mapping key is a tuple of (run,
129 dataset type, data ID), and the value is the corresponding bundle.
130 To simplify the uploaded schemas, the bundle uses metrics' relative
131 (unqualified) names even if the input measurements were
132 fully-qualified.
133 """
134 bundles = defaultdict(MetricMeasurementBundle)
135 for ref in metricValues:
136 value = butler.get(ref)
137 # MeasurementMetricBundle doesn't validate input.
138 if not isinstance(value, lsst.verify.Measurement):
139 raise ValueError(f"{ref} is not a metric value.")
141 # HACK: in general, metric names are fully qualified, and this becomes
142 # the InfluxDB field name. lsst.verify-style metrics have unique names
143 # already, so remove the package qualification.
144 value = lsst.verify.Measurement(
145 value.metric_name.metric, value.quantity, value.blobs.values(), value.extras, value.notes
146 )
147 # These metrics weren't created by actions. Sasquatch requires that
148 # each actionId produce the same metrics on every run (see
149 # https://sasquatch.lsst.io/user-guide/avro.html), so choose something
150 # unique to the metric.
151 actionId = value.metric_name.metric
153 bundle = bundles[(ref.run, ref.datasetType.name, ref.dataId)]
154 bundle.setdefault(actionId, []).append(value)
155 return bundles
158def main():
159 args = makeParser().parse_args()
160 if args.test:
161 args.namespace = "lsst.debug"
163 butler = Butler(args.repo, collections=args.collections, writeable=False)
164 metricTypes = {t for t in butler.registry.queryDatasetTypes() if t.storageClass_name == "MetricValue"}
165 metricValues = butler.registry.queryDatasets(metricTypes, where=args.where, findFirst=True)
166 _LOG.info("Found %d metric values in %s.", metricValues.count(), args.collections)
168 bundles = _bundle_metrics(butler, metricValues)
169 dispatcher = SasquatchDispatcher(url=args.base_url, token=args.token, namespace=args.namespace)
170 _LOG.info("Uploading to %s @ %s...", args.namespace, args.base_url)
171 for (run, datasetType, dataId), bundle in bundles.items():
172 dispatcher.dispatch(
173 bundle,
174 run=run,
175 datasetType=datasetType,
176 timestamp=args.date_created,
177 datasetIdentifier=args.dataset,
178 identifierFields=dataId,
179 extraFields=args.extra,
180 )