Hide keyboard shortcuts

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/>. 

21 

22"""Supporting functions for reporting on runs submitted to a WMS 

23 

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

25 LSST oriented database tables. 

26""" 

27 

28import logging 

29 

30from .wms_service import WmsStates 

31 

32 

33SUMMARY_FMT = "{:1} {:>10} {:>3} {:>8} {:10} {:5} {:8} {:10} {:<50}" 

34 

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

43 

44_LOG = logging.getLogger() 

45 

46 

47def print_headers(): 

48 """Print headers. 

49 """ 

50 print(SUMMARY_FMT.format("X", "STATE", "%S", "ID", "OPERATOR", "PRJ", "CMPGN", "PAYLOAD", "RUN")) 

51 print("-" * 119) 

52 

53 

54def print_run(run_report): 

55 """Print single run info. 

56 

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' 

71 

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

79 

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])) 

83 

84 

85def group_jobs_by_state(jobs): 

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

87 

88 Parameters 

89 ---------- 

90 jobs : `list` of `~lsst.ctrl.bps.wms_service.WmsJobReport` 

91 Jobs to divide into groups based on state. 

92 

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. 

102 

103 for job in jobs: 

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

105 return by_state 

106 

107 

108def group_jobs_by_label(jobs): 

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

110 

111 Parameters 

112 ---------- 

113 jobs : `list` of `~lsst.ctrl.bps.wms_service.WmsJobReport` 

114 Jobs to divide into groups based on label. 

115 

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 

127 

128 

129def print_single_run_summary(run_report): 

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

131 

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

141 

142 # Print more run information. 

143 print(f"Path: {run_report.path}\n") 

144 

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

148 

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) 

155 

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]) 

163 

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])}") 

169 

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