Coverage for python/lsst/ctrl/bps/report.py: 7%

106 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-03-12 03:01 -0700

1# This file is part of ctrl_bps. 

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"""Supporting functions for reporting on runs submitted to a WMS. 

23 

24Note: Expectations are that future reporting effort will revolve around LSST 

25oriented database tables. 

26""" 

27 

28import logging 

29 

30from astropy.table import Table 

31 

32from lsst.utils import doImport 

33 

34from . import WmsStates 

35 

36 

37_LOG = logging.getLogger(__name__) 

38 

39 

40def report(wms_service, run_id, user, hist_days, pass_thru): 

41 """Print out summary of jobs submitted for execution. 

42 

43 Parameters 

44 ---------- 

45 wms_service : `str` 

46 Name of the class. 

47 run_id : `str` 

48 A run id the report will be restricted to. 

49 user : `str` 

50 A user name the report will be restricted to. 

51 hist_days : int 

52 Number of days 

53 pass_thru : `str` 

54 A string to pass directly to the WMS service class. 

55 """ 

56 wms_service_class = doImport(wms_service) 

57 wms_service = wms_service_class({}) 

58 

59 # If reporting on single run, increase history until better mechanism 

60 # for handling completed jobs is available. 

61 if run_id: 

62 hist_days = max(hist_days, 2) 

63 

64 runs, message = wms_service.report(run_id, user, hist_days, pass_thru) 

65 

66 if run_id: 

67 if not runs: 

68 print(f"No information found for id='{run_id}'.") 

69 print(f"Double check id and retry with a larger --hist value" 

70 f"(currently: {hist_days})") 

71 for run in runs: 

72 print_single_run_summary(run) 

73 else: 

74 summary = init_summary() 

75 for run in sorted(runs, key=lambda j: j.wms_id): 

76 summary = add_single_run_summary(summary, run) 

77 for line in summary.pformat_all(): 

78 print(line) 

79 print("\n\n") 

80 if message: 

81 print(message) 

82 print("\n\n") 

83 

84 

85def init_summary(): 

86 """Initialize the summary report table. 

87 

88 Returns 

89 ------- 

90 table : `astropy.table.Table` 

91 Initialized summary report table. 

92 """ 

93 columns = [ 

94 ("X", "S"), 

95 ("STATE", "S"), 

96 ("%S", "S"), 

97 ("ID", "S"), 

98 ("OPERATOR", "S"), 

99 ("PROJECT", "S"), 

100 ("CAMPAIGN", "S"), 

101 ("PAYLOAD", "S"), 

102 ("RUN", "S") 

103 ] 

104 return Table(dtype=columns) 

105 

106 

107def add_single_run_summary(summary, run_report): 

108 """Add a single run info to the summary. 

109 

110 Parameters 

111 ---------- 

112 summary : `astropy.tables.Table` 

113 The table representing the run summary. 

114 run_report : `lsst.ctrl.bps.WmsRunReport` 

115 Information for single run. 

116 """ 

117 # Flag any running workflow that might need human attention 

118 run_flag = " " 

119 if run_report.state == WmsStates.RUNNING: 

120 if run_report.job_state_counts.get(WmsStates.FAILED, 0): 

121 run_flag = "F" 

122 elif run_report.job_state_counts.get(WmsStates.DELETED, 0): 

123 run_flag = "D" 

124 elif run_report.job_state_counts.get(WmsStates.HELD, 0): 

125 run_flag = "H" 

126 

127 percent_succeeded = "UNK" 

128 _LOG.debug("total_number_jobs = %s", run_report.total_number_jobs) 

129 _LOG.debug("run_report.job_state_counts = %s", run_report.job_state_counts) 

130 if run_report.total_number_jobs: 

131 succeeded = run_report.job_state_counts.get(WmsStates.SUCCEEDED, 0) 

132 _LOG.debug("succeeded = %s", succeeded) 

133 percent_succeeded = f"{int(succeeded / run_report.total_number_jobs * 100)}" 

134 

135 row = ( 

136 run_flag, 

137 run_report.state.name, 

138 percent_succeeded, 

139 run_report.wms_id, 

140 run_report.operator, 

141 run_report.project, 

142 run_report.campaign, 

143 run_report.payload, 

144 run_report.run 

145 ) 

146 summary.add_row(row) 

147 return summary 

148 

149 

150def group_jobs_by_state(jobs): 

151 """Divide given jobs into groups based on their state value. 

152 

153 Parameters 

154 ---------- 

155 jobs : `list` [`lsst.ctrl.bps.WmsJobReport`] 

156 Jobs to divide into groups based on state. 

157 

158 Returns 

159 ------- 

160 by_state : `dict` 

161 Mapping of job state to a list of jobs. 

162 """ 

163 _LOG.debug("group_jobs_by_state: jobs=%s", jobs) 

164 by_state = {state: [] for state in WmsStates} 

165 for job in jobs: 

166 by_state[job.state].append(job) 

167 return by_state 

168 

169 

170def group_jobs_by_label(jobs): 

171 """Divide given jobs into groups based on their label value. 

172 

173 Parameters 

174 ---------- 

175 jobs : `list` [`lsst.ctrl.bps.WmsJobReport`] 

176 Jobs to divide into groups based on label. 

177 

178 Returns 

179 ------- 

180 by_label : `dict` [`str`, `lsst.ctrl.bps.WmsJobReport`] 

181 Mapping of job state to a list of jobs. 

182 """ 

183 by_label = {} 

184 for job in jobs: 

185 group = by_label.setdefault(job.label, []) 

186 group.append(job) 

187 return by_label 

188 

189 

190def print_single_run_summary(run_report): 

191 """Print runtime info for single run including job summary per task abbrev. 

192 

193 Parameters 

194 ---------- 

195 run_report : `lsst.ctrl.bps.WmsRunReport` 

196 Summary runtime info for a run + runtime info for jobs. 

197 """ 

198 # Print normal run summary. 

199 summary = init_summary() 

200 summary = add_single_run_summary(summary, run_report) 

201 for line in summary.pformat_all(): 

202 print(line) 

203 print("\n\n") 

204 

205 # Print more run information. 

206 print(f"Path: {run_report.path}") 

207 print("\n\n") 

208 

209 by_label = group_jobs_by_label(run_report.jobs) 

210 

211 # Count the jobs by label and WMS state. 

212 label_order = [] 

213 by_label_expected = {} 

214 if run_report.run_summary: 

215 for part in run_report.run_summary.split(";"): 

216 label, count = part.split(":") 

217 label_order.append(label) 

218 by_label_expected[label] = int(count) 

219 else: 

220 print("Warning: Cannot determine order of pipeline. Instead printing alphabetical.") 

221 label_order = sorted(by_label.keys()) 

222 

223 # Initialize table for saving the detailed run info. 

224 columns = [(" ", "S")] + [(s.name, "i") for s in WmsStates] + [("EXPECTED", "i")] 

225 details = Table(dtype=columns) 

226 

227 total = ["TOTAL"] 

228 total.extend([run_report.job_state_counts[state] for state in WmsStates]) 

229 total.append(sum(by_label_expected.values())) 

230 details.add_row(total) 

231 

232 for label in label_order: 

233 if label in by_label: 

234 by_label_state = group_jobs_by_state(by_label[label]) 

235 _LOG.debug("by_label_state = %s", by_label_state) 

236 counts = {state: len(jobs) for state, jobs in by_label_state.items()} 

237 if label in by_label_expected: 

238 already_counted = sum(counts.values()) 

239 if already_counted != by_label_expected[label]: 

240 counts[WmsStates.UNREADY] += by_label_expected[label] - already_counted 

241 else: 

242 counts = dict.fromkeys(WmsStates, -1) 

243 

244 row = [label] 

245 row.extend([counts[state] for state in WmsStates]) 

246 row.append([by_label_expected[label]]) 

247 details.add_row(row) 

248 

249 # Format the report summary and print it out. 

250 alignments = ["<"] 

251 alignments.extend([">" for _ in WmsStates]) 

252 alignments.append(">") 

253 lines = details.pformat_all(align=alignments) 

254 lines.insert(3, lines[1]) 

255 for line in lines: 

256 print(line)