Belle II Software light-2406-ragdoll
viewer.py
1#!/usr/bin/env python3
2
3
10
11import numbers
12import json
13import time
14from io import StringIO
15from html import escape
16
17from dateutil.relativedelta import relativedelta
18
19
21 """
22 A base class for widgets in the hep ipython projects.
23 """
24
25 def create(self):
26 """
27 Override this method!
28 """
29 return None
30
31 def show(self):
32 """
33 Show the widget
34 """
35 from IPython.core.display import display
36
37 a = self.create()
38 display(a)
39
40
42 """The css string for styling the notebook."""
43
44
45 css_string = """
46 #notebook-container {
47 width: 90%;
48 }
49
50 #menubar-container {
51 width: 90%;
52 }
53
54 #header-container {
55 width: 90%;
56 }
57 """
58
59 def create(self):
60 """Create the styling widget."""
61 from IPython.core.display import HTML
62 html = HTML(f"<style>\n{self.css_string}\n</style>")
63 return html
64
65
67 """
68 Viewer Object used to print data to the IPython Notebook.
69 Do not use it on your own.
70 """
71
72 def __init__(self):
73 """
74 Create a new progress bar viewer.
75 """
76 from IPython.core.display import display
77 from ipywidgets import FloatProgress, VBox, Layout, Label
78
79
80 self.last_time = time.time()
81
83
84
85 self.progress_bar = FloatProgress(value=0, min=0, max=1,
86 layout=Layout(width="100%", height="40px"))
87
88 self.progress_label = Label()
89
90 self.progress_box = VBox([self.progress_bar, self.progress_label])
91
92 display(self.progress_box)
93
94 def update(self, text_or_percentage):
95 """
96 Update the widget with a new event number
97 """
98
99 if isinstance(text_or_percentage, numbers.Number):
100 # text_or_percentage is percentage fraction
101 current_percentage = float(text_or_percentage)
102 current_time = time.time()
103
104 remaining_percentage = 1.0 - current_percentage
105
106 time_delta = current_time - self.last_time
107 percentage_delta = current_percentage - self.last_percentage
108
109 if percentage_delta > 0:
110 time_delta_per_percentage = 1.0 * time_delta / percentage_delta
111
112 # creates a human-readable time delta like '3 minutes 34 seconds'
113 attrs = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']
114
115 def human_readable(delta): return [f'{int(getattr(delta, attr))} {getattr(delta, attr) > 1 and attr or attr[:-1]}'
116 for attr in attrs if getattr(delta, attr)]
117
118 times_list = human_readable(relativedelta(seconds=time_delta_per_percentage * remaining_percentage))
119 human_readable_str = " ".join(times_list)
120
121 display_text = f"{int(100 * current_percentage)} % Remaining time: {human_readable_str}"
122
123 self.progress_label.value = display_text
124 self.progress_bar.value = float(current_percentage)
125 else:
126 # text_or_percentage is status string
127 self.progress_label.value = f"Status: {text_or_percentage}"
128 if "finished" in str(text_or_percentage):
129 self.progress_bar.value = 1.0
130 self.progress_bar.bar_style = "success"
131 elif "failed" in str(text_or_percentage):
132 self.progress_bar.bar_style = "danger"
133 # color box red to see failure even when progress value is 0
134 self.progress_box.box_style = "danger"
135
136 def show(self):
137 """
138 Display the widget
139 """
140 from IPython.core.display import display
141
142 display(self.progress_box)
143
144
146 """
147 Viewer object for the store entries.
148 Do not use it on your own.
149 """
150
151 def __init__(self, collections):
152 """
153 Create a new collections viewer with the collections object from the process.
154 Collections must be a StoreContentList with a list of StoreContents.
155 """
156
157 self.collections = collections
158
159
160 self.table_row_html = """<tr><td style="padding: 10px 0;">{content.name}</td>
161 <td style="padding: 10px 0;">{content.number}</td></tr>"""
162
163 def create(self):
164 """
165 Create the widget
166 """
167
168 import ipywidgets as widgets
169
170 if self.collections is None:
171 return widgets.HTML("")
172
173 a = widgets.Tab()
174 children = []
175
176 for i, event in enumerate(self.collections):
177 html = widgets.HTML()
178 html.value = """<table style="border-collapse: separate; border-spacing: 50px 0;">"""
179 for store_content in event.content:
180 html.value += self.table_row_html.format(content=store_content)
181 html.value += "</table>"
182 children.append(html)
183 a.set_title(i, "Event " + str(event.event_number))
184
185 a.children = children
186
187 return a
188
189
191 """
192 A viewer widget for displaying the
193 statistics in a nicer way in ipython
194 """
195
196 def __init__(self, statistics):
197 """ Init the widget with the statistics from the process.
198 The statistics must be an instance of Statistics. """
199
200 self.statistics = statistics
201
202
203 self.table_column_html = """<td style="padding: 10px;">{content}</td>"""
204
205 self.table_column_3_html = """<td colspan="3" style="padding: 10px;">{content}</td>"""
206
207 self.table_cell_html = """<td style="padding: 10px; text-align: left">{content}</td>"""
208
209 self.table_cell_3_html = """<td style=\"text-align: right\">{content[0]}</td><td>{content[1]}</td><td>{content[2]}</td>"""
210
211 def create(self):
212 """
213 Create the widget
214 """
215 import ipywidgets as widgets
216
217 if self.statistics is None:
218 return widgets.HTML("")
219
220 html = widgets.HTML()
221 html.value = """<table style="border-collapse: collapsed; border: 1px solid black;">
222 <thead><tr style="background: #AAA; font-weight: bold">"""
223
224 for column in self.statistics.columns:
225 if column.three_column_format:
226 html.value += self.table_column_3_html.format(content=column.display_name)
227 else:
228 html.value += self.table_column_html.format(content=column.display_name)
229 html.value += "</tr></thead><tbody>"
230
231 for n, module in enumerate(self.statistics.modules):
232 if n % 2 == 1:
233 html.value += """<tr style="background: #EEE;">"""
234 else:
235 html.value += """<tr>"""
236
237 for column in self.statistics.columns:
238 if column.three_column_format:
239 html.value += self.table_cell_3_html.format(content=module[column.name])
240 else:
241 html.value += self.table_cell_html.format(content=module[column.name])
242
243 html.value += "</tr>"
244
245 # SUMMARY html.value += """<tr style="border: 1px solid black; font-weight: bold">"""
246
247 html.value += "</tbody></table>"
248 html.margin = "10px"
249 return html
250
251
253 """
254 A widget to summarize all the infromation from different processes.
255 Must be filled with the widgets of the single processes
256 """
257
258 def __init__(self, children):
259 """
260 Create a process viewer
261 """
262
263
264 self.children = children
265
266 def create(self):
267 """
268 Create the widget
269 """
270 import ipywidgets as widgets
271
272 a = widgets.Tab()
273 for i in range(len(self.children)):
274 a.set_title(i, "Process " + str(i))
275 a.children = [children.create() for children in self.children if children is not None]
276 return a
277
278 def show(self):
279 """
280 Show the widget
281 """
282
283 import ipywidgets as widgets
284 from IPython.core.display import display
285
286 if len(self.children) > 0:
287 a = self.create()
288
289 else:
290 a = widgets.HTML("<strong>Calculation list empty. Nothing to show.</strong>")
291
292 display(a)
293
294
296 """
297 A widget to show the log of a calculation.
298 """
299
300 def __init__(self, log_content):
301 """
302 Initialize the log viewer.
303 """
304
305
306 self.log_content = log_content
307
308
309 self.log_levels = ["DEBUG", "INFO", "RESULT", "WARNING", "ERROR", "FATAL", "DEFAULT"]
310
311
312 self.log_color_codes = {"DEBUG": "gray", "ERROR": "red", "FATAL": "red", "INFO": "black", "RESULT": "green",
313 "WARNING": "orange", "DEFAULT": "black"}
314
315
316 self.log_message = """<pre class="log-line-{type_lower}" title="{info}">[{level}] {message}{var_output}</pre>"""
317
318
319 self.toggle_button_line = """<a onclick="$('.log-line-{type_lower}').hide();
320 $('.log-line-{type_lower}-hide-button').hide();
321 $('.log-line-{type_lower}-show-button').show();"
322 style="cursor: pointer; margin: 0px 10px;"
323 class="log-line-{type_lower}-hide-button">Hide {type_upper}</a>
324 <a onclick="$('.log-line-{type_lower}').show();
325 $('.log-line-{type_lower}-hide-button').show();
326 $('.log-line-{type_lower}-show-button').hide();"
327 style="cursor: pointer; margin: 0px 10px; display: none;"
328 class="log-line-{type_lower}-show-button">Show {type_upper}</a>"""
329
330 def format_logmessage(self, buf, message, indent=4, base=0):
331 """
332 Format the json object of a logmessage as key: value list with recursion and indentation
333 Funnily this is faster than json.dumps() and looks a bit better in the title attribute
334 """
335 for key, val in message.items():
336 if not val:
337 continue
338 if isinstance(val, dict):
339 buf.write(f"{key}:\n")
340 self.format_logmessage(buf, val, indent=indent, base=base + indent)
341 else:
342 buf.write(base * " ")
343 buf.write(f"{key}: {val!r}\n")
344
345 def create(self):
346 """
347 Create the log viewer.
348 """
349 from ipywidgets import HTML, HBox, VBox
350
351 output = StringIO()
352 output.write("<style scoped>\n")
353 for level, color in self.log_color_codes.items():
354 level = level.lower()
355 output.write(f".log-line-{level} {{margin:0; padding:0; line-height:normal; color: {color} !important;}}\n")
356
357 output.write("""</style><div style="max-height: 400px; overflow-y: auto; width: 100%";>""")
358
359 for line in self.log_content.split("\n"):
360 if line.startswith('{"level"'):
361 try:
362 message = json.loads(line)
363 # ok, message is parsed. Prepare some info string which
364 # contains the info about the message in a indented
365 # format but don't completely json or pprint because it
366 # takes to much time
367 buf = StringIO()
368 self.format_logmessage(buf, message)
369 info = escape(buf.getvalue())
370 # add variables if necessary
371 variables = message.get("variables", "")
372 if variables:
373 variables = "\n".join([""] + [f"\t{k} = {v}" for k, v in variables.items()])
374 # and write out
375 level = message["level"].lower()
376 output.write(self.log_message.format(info=info, type_lower=level, var_output=variables, **message))
377 continue
378 except json.JSONDecodeError:
379 # any error: treat as default output, not a log line
380 pass
381
382 output.write('<pre class="log-line-default">')
383 output.write(line)
384 output.write('</pre>')
385
386 output.write("</div>")
387
388 html = HTML()
389 html.value = output.getvalue()
390 html.width = "100%"
391 html.margin = "5px"
392
393 buttons = []
394 for type in self.log_levels:
395 buttons.append(HTML(self.toggle_button_line.format(type_lower=type.lower(), type_upper=type.upper())))
396
397 buttons_view = HBox(buttons)
398 buttons_view.margin = "10px 0px"
399 result_vbox = VBox((buttons_view, html))
400
401 return result_vbox
collections
The collections to show.
Definition: viewer.py:157
def __init__(self, collections)
Definition: viewer.py:151
table_row_html
Template for a table row.
Definition: viewer.py:160
def format_logmessage(self, buf, message, indent=4, base=0)
Definition: viewer.py:330
toggle_button_line
The toggle button.
Definition: viewer.py:319
def __init__(self, log_content)
Definition: viewer.py:300
log_content
The log content to show.
Definition: viewer.py:306
log_message
A templated line in the log.
Definition: viewer.py:316
log_color_codes
The color codes for the log messages.
Definition: viewer.py:312
log_levels
The log levels of the framework.
Definition: viewer.py:309
def __init__(self, children)
Definition: viewer.py:258
children
The children for each process.
Definition: viewer.py:264
progress_label
Label for the progress bar, shows progress in percent or status.
Definition: viewer.py:88
last_percentage
The starting percentage (obviously 0)
Definition: viewer.py:82
def update(self, text_or_percentage)
Definition: viewer.py:94
progress_box
Box widget that will be displayed, contains progress bar and status label.
Definition: viewer.py:90
progress_bar
Widget of the progress bar itself.
Definition: viewer.py:85
last_time
The starting time of the process.
Definition: viewer.py:80
statistics
The statistics we want to show.
Definition: viewer.py:200
table_column_3_html
Template for a table cell spanning 3 columns.
Definition: viewer.py:205
table_cell_html
Template for a table cell with left alignment.
Definition: viewer.py:207
def __init__(self, statistics)
Definition: viewer.py:196
table_column_html
Template for a table cell.
Definition: viewer.py:203
table_cell_3_html
Template for a table cell with 3 columns.
Definition: viewer.py:209