Belle II Software  release-08-01-10
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("<style>\n%s\n</style>" % self.css_stringcss_string)
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 ['%d %s' % (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 = "%d %% Remaining time: %s" % (
122  100 * current_percentage, human_readable_str)
123 
124  self.progress_labelprogress_label.value = display_text
125  self.progress_barprogress_bar.value = float(current_percentage)
126  else:
127  # text_or_percentage is status string
128  self.progress_labelprogress_label.value = f"Status: {text_or_percentage}"
129  if "finished" in str(text_or_percentage):
130  self.progress_barprogress_bar.value = 1.0
131  self.progress_barprogress_bar.bar_style = "success"
132  elif "failed" in str(text_or_percentage):
133  self.progress_barprogress_bar.bar_style = "danger"
134  # color box red to see failure even when progress value is 0
135  self.progress_boxprogress_box.box_style = "danger"
136 
137  def show(self):
138  """
139  Display the widget
140  """
141  from IPython.core.display import display
142 
143  display(self.progress_boxprogress_box)
144 
145 
147  """
148  Viewer object for the store entries.
149  Do not use it on your own.
150  """
151 
152  def __init__(self, collections):
153  """
154  Create a new collections viewer with the collections object from the process.
155  Collections must be a StoreContentList with a list of StoreContents.
156  """
157 
158  self.collectionscollections = collections
159 
160 
161  self.table_row_htmltable_row_html = """<tr><td style="padding: 10px 0;">{content.name}</td>
162  <td style="padding: 10px 0;">{content.number}</td></tr>"""
163 
164  def create(self):
165  """
166  Create the widget
167  """
168 
169  import ipywidgets as widgets
170 
171  if self.collectionscollections is None:
172  return widgets.HTML("")
173 
174  a = widgets.Tab()
175  children = []
176 
177  for i, event in enumerate(self.collectionscollections):
178  html = widgets.HTML()
179  html.value = """<table style="border-collapse: separate; border-spacing: 50px 0;">"""
180  for store_content in event.content:
181  html.value += self.table_row_htmltable_row_html.format(content=store_content)
182  html.value += "</table>"
183  children.append(html)
184  a.set_title(i, "Event " + str(event.event_number))
185 
186  a.children = children
187 
188  return a
189 
190 
192  """
193  A viewer widget for displaying the
194  statistics in a nicer way in ipython
195  """
196 
197  def __init__(self, statistics):
198  """ Init the widget with the statistics from the process.
199  The statistics must be an instance of Statistics. """
200 
201  self.statisticsstatistics = statistics
202 
203 
204  self.table_column_htmltable_column_html = """<td style="padding: 10px;">{content}</td>"""
205 
206  self.table_column_3_htmltable_column_3_html = """<td colspan="3" style="padding: 10px;">{content}</td>"""
207 
208  self.table_cell_htmltable_cell_html = """<td style="padding: 10px; text-align: left">{content}</td>"""
209 
210  self.table_cell_3_htmltable_cell_3_html = """<td style=\"text-align: right\">{content[0]}</td><td>{content[1]}</td><td>{content[2]}</td>"""
211 
212  def create(self):
213  """
214  Create the widget
215  """
216  import ipywidgets as widgets
217 
218  if self.statisticsstatistics is None:
219  return widgets.HTML("")
220 
221  html = widgets.HTML()
222  html.value = """<table style="border-collapse: collapsed; border: 1px solid black;">
223  <thead><tr style="background: #AAA; font-weight: bold">"""
224 
225  for column in self.statisticsstatistics.columns:
226  if column.three_column_format:
227  html.value += self.table_column_3_htmltable_column_3_html.format(content=column.display_name)
228  else:
229  html.value += self.table_column_htmltable_column_html.format(content=column.display_name)
230  html.value += "</tr></thead><tbody>"
231 
232  for n, module in enumerate(self.statisticsstatistics.modules):
233  if n % 2 == 1:
234  html.value += """<tr style="background: #EEE;">"""
235  else:
236  html.value += """<tr>"""
237 
238  for column in self.statisticsstatistics.columns:
239  if column.three_column_format:
240  html.value += self.table_cell_3_htmltable_cell_3_html.format(content=module[column.name])
241  else:
242  html.value += self.table_cell_htmltable_cell_html.format(content=module[column.name])
243 
244  html.value += "</tr>"
245 
246  # SUMMARY html.value += """<tr style="border: 1px solid black; font-weight: bold">"""
247 
248  html.value += "</tbody></table>"
249  html.margin = "10px"
250  return html
251 
252 
254  """
255  A widget to summarize all the infromation from different processes.
256  Must be filled with the widgets of the single processes
257  """
258 
259  def __init__(self, children):
260  """
261  Create a process viewer
262  """
263 
264 
265  self.childrenchildren = children
266 
267  def create(self):
268  """
269  Create the widget
270  """
271  import ipywidgets as widgets
272 
273  a = widgets.Tab()
274  for i in range(len(self.childrenchildren)):
275  a.set_title(i, "Process " + str(i))
276  a.children = [children.create() for children in self.childrenchildren if children is not None]
277  return a
278 
279  def show(self):
280  """
281  Show the widget
282  """
283 
284  import ipywidgets as widgets
285  from IPython.core.display import display
286 
287  if len(self.childrenchildren) > 0:
288  a = self.createcreate()
289 
290  else:
291  a = widgets.HTML("<strong>Calculation list empty. Nothing to show.</strong>")
292 
293  display(a)
294 
295 
297  """
298  A widget to show the log of a calculation.
299  """
300 
301  def __init__(self, log_content):
302  """
303  Initialize the log viewer.
304  """
305 
306 
307  self.log_contentlog_content = log_content
308 
309 
310  self.log_levelslog_levels = ["DEBUG", "INFO", "RESULT", "WARNING", "ERROR", "FATAL", "DEFAULT"]
311 
312 
313  self.log_color_codeslog_color_codes = {"DEBUG": "gray", "ERROR": "red", "FATAL": "red", "INFO": "black", "RESULT": "green",
314  "WARNING": "orange", "DEFAULT": "black"}
315 
316 
317  self.log_messagelog_message = """<pre class="log-line-{type_lower}" title="{info}">[{level}] {message}{var_output}</pre>"""
318 
319 
320  self.toggle_button_linetoggle_button_line = """<a onclick="$('.log-line-{type_lower}').hide();
321  $('.log-line-{type_lower}-hide-button').hide();
322  $('.log-line-{type_lower}-show-button').show();"
323  style="cursor: pointer; margin: 0px 10px;"
324  class="log-line-{type_lower}-hide-button">Hide {type_upper}</a>
325  <a onclick="$('.log-line-{type_lower}').show();
326  $('.log-line-{type_lower}-hide-button').show();
327  $('.log-line-{type_lower}-show-button').hide();"
328  style="cursor: pointer; margin: 0px 10px; display: none;"
329  class="log-line-{type_lower}-show-button">Show {type_upper}</a>"""
330 
331  def format_logmessage(self, buf, message, indent=4, base=0):
332  """
333  Format the json object of a logmessage as key: value list with recursion and indentation
334  Funnily this is faster than json.dumps() and looks a bit better in the title attribute
335  """
336  for key, val in message.items():
337  if not val:
338  continue
339  if isinstance(val, dict):
340  buf.write(f"{key}:\n")
341  self.format_logmessageformat_logmessage(buf, val, indent=indent, base=base + indent)
342  else:
343  buf.write(base * " ")
344  buf.write(f"{key}: {val!r}\n")
345 
346  def create(self):
347  """
348  Create the log viewer.
349  """
350  from ipywidgets import HTML, HBox, VBox
351 
352  output = StringIO()
353  output.write("<style scoped>\n")
354  for level, color in self.log_color_codeslog_color_codes.items():
355  level = level.lower()
356  output.write(f".log-line-{level} {{margin:0; padding:0; line-height:normal; color: {color} !important;}}\n")
357 
358  output.write("""</style><div style="max-height: 400px; overflow-y: auto; width: 100%";>""")
359 
360  for line in self.log_contentlog_content.split("\n"):
361  if line.startswith('{"level"'):
362  try:
363  message = json.loads(line)
364  # ok, message is parsed. Prepare some info string which
365  # contains the info about the message in a indented
366  # format but don't completely json or pprint because it
367  # takes to much time
368  buf = StringIO()
369  self.format_logmessageformat_logmessage(buf, message)
370  info = escape(buf.getvalue())
371  # add variables if necessary
372  variables = message.get("variables", "")
373  if variables:
374  variables = "\n".join([""] + [f"\t{k} = {v}" for k, v in variables.items()])
375  # and write out
376  level = message["level"].lower()
377  output.write(self.log_messagelog_message.format(info=info, type_lower=level, var_output=variables, **message))
378  continue
379  except json.JSONDecodeError:
380  # any error: treat as default output, not a log line
381  pass
382 
383  output.write('<pre class="log-line-default">')
384  output.write(line)
385  output.write('</pre>')
386 
387  output.write("</div>")
388 
389  html = HTML()
390  html.value = output.getvalue()
391  html.width = "100%"
392  html.margin = "5px"
393 
394  buttons = []
395  for type in self.log_levelslog_levels:
396  buttons.append(HTML(self.toggle_button_linetoggle_button_line.format(type_lower=type.lower(), type_upper=type.upper())))
397 
398  buttons_view = HBox(buttons)
399  buttons_view.margin = "10px 0px"
400  result_vbox = VBox((buttons_view, html))
401 
402  return result_vbox
collections
The collections to show.
Definition: viewer.py:158
def __init__(self, collections)
Definition: viewer.py:152
table_row_html
Template for a table row.
Definition: viewer.py:161
def format_logmessage(self, buf, message, indent=4, base=0)
Definition: viewer.py:331
toggle_button_line
The toggle button.
Definition: viewer.py:320
def __init__(self, log_content)
Definition: viewer.py:301
log_content
The log content to show.
Definition: viewer.py:307
log_message
A templated line in the log.
Definition: viewer.py:317
log_color_codes
The color codes for the log messages.
Definition: viewer.py:313
log_levels
The log levels of the framework.
Definition: viewer.py:310
def __init__(self, children)
Definition: viewer.py:259
children
The children for each process.
Definition: viewer.py:265
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:201
table_column_3_html
Template for a table cell spanning 3 columns.
Definition: viewer.py:206
table_cell_html
Template for a table cell with left alignment.
Definition: viewer.py:208
def __init__(self, statistics)
Definition: viewer.py:197
table_column_html
Template for a table cell.
Definition: viewer.py:204
table_cell_3_html
Template for a table cell with 3 columns.
Definition: viewer.py:210
string css_string
The css string for styling the notebook.
Definition: viewer.py:45