Belle II Software  light-2403-persian
viewer.py
1 #!/usr/bin/env python3
2 
3 
10 
11 import numbers
12 import json
13 import time
14 from io import StringIO
15 from html import escape
16 
17 from 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.createcreate()
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_timelast_time = time.time()
81 
82  self.last_percentagelast_percentage = 0
83 
84 
85  self.progress_barprogress_bar = FloatProgress(value=0, min=0, max=1,
86  layout=Layout(width="100%", height="40px"))
87 
88  self.progress_labelprogress_label = Label()
89 
90  self.progress_boxprogress_box = VBox([self.progress_barprogress_bar, self.progress_labelprogress_label])
91 
92  display(self.progress_boxprogress_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_timelast_time
107  percentage_delta = current_percentage - self.last_percentagelast_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_labelprogress_label.value = display_text
124  self.progress_barprogress_bar.value = float(current_percentage)
125  else:
126  # text_or_percentage is status string
127  self.progress_labelprogress_label.value = f"Status: {text_or_percentage}"
128  if "finished" in str(text_or_percentage):
129  self.progress_barprogress_bar.value = 1.0
130  self.progress_barprogress_bar.bar_style = "success"
131  elif "failed" in str(text_or_percentage):
132  self.progress_barprogress_bar.bar_style = "danger"
133  # color box red to see failure even when progress value is 0
134  self.progress_boxprogress_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_boxprogress_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.collectionscollections = collections
158 
159 
160  self.table_row_htmltable_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.collectionscollections is None:
171  return widgets.HTML("")
172 
173  a = widgets.Tab()
174  children = []
175 
176  for i, event in enumerate(self.collectionscollections):
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_htmltable_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.statisticsstatistics = statistics
201 
202 
203  self.table_column_htmltable_column_html = """<td style="padding: 10px;">{content}</td>"""
204 
205  self.table_column_3_htmltable_column_3_html = """<td colspan="3" style="padding: 10px;">{content}</td>"""
206 
207  self.table_cell_htmltable_cell_html = """<td style="padding: 10px; text-align: left">{content}</td>"""
208 
209  self.table_cell_3_htmltable_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.statisticsstatistics 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.statisticsstatistics.columns:
225  if column.three_column_format:
226  html.value += self.table_column_3_htmltable_column_3_html.format(content=column.display_name)
227  else:
228  html.value += self.table_column_htmltable_column_html.format(content=column.display_name)
229  html.value += "</tr></thead><tbody>"
230 
231  for n, module in enumerate(self.statisticsstatistics.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.statisticsstatistics.columns:
238  if column.three_column_format:
239  html.value += self.table_cell_3_htmltable_cell_3_html.format(content=module[column.name])
240  else:
241  html.value += self.table_cell_htmltable_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.childrenchildren = 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.childrenchildren)):
274  a.set_title(i, "Process " + str(i))
275  a.children = [children.create() for children in self.childrenchildren 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.childrenchildren) > 0:
287  a = self.createcreate()
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_contentlog_content = log_content
307 
308 
309  self.log_levelslog_levels = ["DEBUG", "INFO", "RESULT", "WARNING", "ERROR", "FATAL", "DEFAULT"]
310 
311 
312  self.log_color_codeslog_color_codes = {"DEBUG": "gray", "ERROR": "red", "FATAL": "red", "INFO": "black", "RESULT": "green",
313  "WARNING": "orange", "DEFAULT": "black"}
314 
315 
316  self.log_messagelog_message = """<pre class="log-line-{type_lower}" title="{info}">[{level}] {message}{var_output}</pre>"""
317 
318 
319  self.toggle_button_linetoggle_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_logmessageformat_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_codeslog_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_contentlog_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_logmessageformat_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_messagelog_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_levelslog_levels:
395  buttons.append(HTML(self.toggle_button_linetoggle_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