Coverage for python / lsst / analysis / tools / bin / verifyToSasquatch.py: 30%

53 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-22 09:09 +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/>. 

21 

22__all__ = [ 

23 "main", 

24] 

25 

26import argparse 

27import datetime 

28import logging 

29from collections import defaultdict 

30from collections.abc import Iterable, Mapping 

31 

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 

37 

38logging.basicConfig() 

39_LOG = logging.getLogger(__name__) 

40_LOG.setLevel(logging.INFO) 

41 

42 

43_BASE_URL = "https://usdf-rsp-dev.slac.stanford.edu/sasquatch-rest-proxy/" 

44 

45 

46def makeParser(): 

47 parser = argparse.ArgumentParser( 

48 description="""Upload Measurement datasets from a Butler repository to Sasquatch. 

49 

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 ) 

91 

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.") 

105 

106 return parser 

107 

108 

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. 

114 

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. 

122 

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.") 

140 

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 

152 

153 bundle = bundles[(ref.run, ref.datasetType.name, ref.dataId)] 

154 bundle.setdefault(actionId, []).append(value) 

155 return bundles 

156 

157 

158def main(): 

159 args = makeParser().parse_args() 

160 if args.test: 

161 args.namespace = "lsst.debug" 

162 

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) 

167 

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 )