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

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# 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/>.
22"""Supporting functions for reporting on runs submitted to a WMS.
24Note: Expectations are that future reporting effort will revolve around LSST
25oriented database tables.
26"""
28import logging
30from astropy.table import Table
32from lsst.utils import doImport
34from . import WmsStates
37_LOG = logging.getLogger(__name__)
40def report(wms_service, run_id, user, hist_days, pass_thru):
41 """Print out summary of jobs submitted for execution.
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({})
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)
64 runs, message = wms_service.report(run_id, user, hist_days, pass_thru)
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")
85def init_summary():
86 """Initialize the summary report table.
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)
107def add_single_run_summary(summary, run_report):
108 """Add a single run info to the summary.
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"
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)}"
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
150def group_jobs_by_state(jobs):
151 """Divide given jobs into groups based on their state value.
153 Parameters
154 ----------
155 jobs : `list` [`lsst.ctrl.bps.WmsJobReport`]
156 Jobs to divide into groups based on state.
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
170def group_jobs_by_label(jobs):
171 """Divide given jobs into groups based on their label value.
173 Parameters
174 ----------
175 jobs : `list` [`lsst.ctrl.bps.WmsJobReport`]
176 Jobs to divide into groups based on label.
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
190def print_single_run_summary(run_report):
191 """Print runtime info for single run including job summary per task abbrev.
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")
205 # Print more run information.
206 print(f"Path: {run_report.path}")
207 print("\n\n")
209 by_label = group_jobs_by_label(run_report.jobs)
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())
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)
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)
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)
244 row = [label]
245 row.extend([counts[state] for state in WmsStates])
246 row.append([by_label_expected[label]])
247 details.add_row(row)
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)