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

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
25 LSST oriented database tables.
26"""
28import logging
30from .wms_service import WmsStates
33SUMMARY_FMT = "{:1} {:>10} {:>3} {:>8} {:10} {:5} {:8} {:10} {:<50}"
35# logging properties
36_LOG_PROP = """\
37log4j.rootLogger=INFO, A1
38log4j.appender.A1=ConsoleAppender
39log4j.appender.A1.Target=System.err
40log4j.appender.A1.layout=PatternLayout
41log4j.appender.A1.layout.ConversionPattern={}
42"""
44_LOG = logging.getLogger()
47def print_headers():
48 """Print headers.
49 """
50 print(SUMMARY_FMT.format("X", "STATE", "%S", "ID", "OPERATOR", "PRJ", "CMPGN", "PAYLOAD", "RUN"))
51 print("-" * 119)
54def print_run(run_report):
55 """Print single run info.
57 Parameters
58 ----------
59 run_report : `WmsRunReport`
60 Information for single run.
61 """
62 # Flag any running workflow that might need human attention
63 run_flag = ' '
64 if run_report.state == WmsStates.RUNNING:
65 if run_report.job_state_counts.get(WmsStates.FAILED, 0):
66 run_flag = 'F'
67 elif run_report.job_state_counts.get(WmsStates.DELETED, 0):
68 run_flag = 'D'
69 elif run_report.job_state_counts.get(WmsStates.HELD, 0):
70 run_flag = 'H'
72 percent_succeeded = 'UNK'
73 _LOG.debug("total_number_jobs = %s", run_report.total_number_jobs)
74 _LOG.debug("run_report.job_state_counts = %s", run_report.job_state_counts)
75 if run_report.total_number_jobs:
76 succeeded = run_report.job_state_counts.get(WmsStates.SUCCEEDED, 0)
77 _LOG.debug("succeeded = %s", succeeded)
78 percent_succeeded = f"{int(succeeded / run_report.total_number_jobs * 100)}"
80 print(SUMMARY_FMT.format(run_flag, run_report.state.name, percent_succeeded, run_report.wms_id,
81 run_report.operator[:10], run_report.project, run_report.campaign,
82 run_report.payload, run_report.run[:50]))
85def group_jobs_by_state(jobs):
86 """Divide given jobs into groups based on their state value.
88 Parameters
89 ----------
90 jobs : `list` of `~lsst.ctrl.bps.wms_service.WmsJobReport`
91 Jobs to divide into groups based on state.
93 Returns
94 -------
95 by_state : `dict`
96 Mapping of job state to a list of jobs.
97 """
98 _LOG.debug("group_jobs_by_state: jobs=%s", jobs)
99 by_state = dict.fromkeys(WmsStates)
100 for state in by_state:
101 by_state[state] = [] # Note: If added [] to fromkeys(), they shared single list.
103 for job in jobs:
104 by_state[job.state].append(job)
105 return by_state
108def group_jobs_by_label(jobs):
109 """Divide given jobs into groups based on their label value.
111 Parameters
112 ----------
113 jobs : `list` of `~lsst.ctrl.bps.wms_service.WmsJobReport`
114 Jobs to divide into groups based on label.
116 Returns
117 -------
118 by_label : `dict`
119 Mapping of job state to a list of jobs .
120 """
121 by_label = {}
122 for job in jobs:
123 if job.label not in by_label:
124 by_label[job.label] = []
125 by_label[job.label].append(job)
126 return by_label
129def print_single_run_summary(run_report):
130 """Print runtime info for single run including job summary per task abbrev.
132 Parameters
133 ----------
134 run_report : `~lsst.ctrl.bps.wms_service.WmsRunReport`
135 Summary runtime info for a run + runtime info for jobs.
136 """
137 # Print normal run summary.
138 print_headers()
139 print_run(run_report)
140 print("\n\n")
142 # Print more run information.
143 print(f"Path: {run_report.path}\n")
145 print(f"{'':35} {' | '.join([f'{s.name[:5]:5}' for s in WmsStates])}")
146 print(f"{'Total':35} {' | '.join([f'{run_report.job_state_counts[s]:5}' for s in WmsStates])}")
147 print("-" * (38 + 8 * len(run_report.job_state_counts)))
149 # Print job level info by print counts of jobs by label and WMS state.
150 by_label_totals = {}
151 if run_report.run_summary:
152 for part in run_report.run_summary.split(';'):
153 label, count = part.split(':')
154 by_label_totals[label] = int(count)
156 by_label = group_jobs_by_label(run_report.jobs)
157 for label in by_label:
158 by_label_state = group_jobs_by_state(by_label[label])
159 _LOG.debug("by_label_state = %s", by_label_state)
160 counts = dict.fromkeys(WmsStates)
161 for state in WmsStates:
162 counts[state] = len(by_label_state[state])
164 if label in by_label_totals:
165 already_counted = sum(counts.values())
166 if already_counted != by_label_totals[label]:
167 counts[WmsStates.UNREADY] += by_label_totals[label] - already_counted
168 print(f"{label[:35]:35} {' | '.join([f'{counts[s]:5}' for s in WmsStates])}")
170 # Print out lines for labels not yet printed.
171 for label in by_label_totals:
172 if label not in by_label:
173 counts = dict.fromkeys(WmsStates, 0)
174 counts[WmsStates.UNREADY] = by_label_totals[label]
175 print(f"{label[:35]:35} {' | '.join([f'{counts[s]:5}' for s in WmsStates])}")
176 print("\n")