siliconcompiler 0.28.2__py3-none-any.whl → 0.28.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. siliconcompiler/_common.py +12 -0
  2. siliconcompiler/_metadata.py +1 -1
  3. siliconcompiler/apps/sc_dashboard.py +5 -1
  4. siliconcompiler/apps/sc_install.py +61 -13
  5. siliconcompiler/apps/sc_remote.py +1 -1
  6. siliconcompiler/core.py +39 -13
  7. siliconcompiler/remote/client.py +41 -10
  8. siliconcompiler/report/__init__.py +1 -1
  9. siliconcompiler/report/{streamlit_report.py → dashboard/__init__.py} +47 -10
  10. siliconcompiler/report/dashboard/components/__init__.py +534 -0
  11. siliconcompiler/report/dashboard/components/flowgraph.py +114 -0
  12. siliconcompiler/report/dashboard/components/graph.py +208 -0
  13. siliconcompiler/report/dashboard/layouts/__init__.py +20 -0
  14. siliconcompiler/report/dashboard/layouts/_common.py +43 -0
  15. siliconcompiler/report/dashboard/layouts/vertical_flowgraph.py +95 -0
  16. siliconcompiler/report/dashboard/layouts/vertical_flowgraph_node_tab.py +114 -0
  17. siliconcompiler/report/dashboard/layouts/vertical_flowgraph_sac_tabs.py +107 -0
  18. siliconcompiler/report/dashboard/state.py +215 -0
  19. siliconcompiler/report/dashboard/utils/__init__.py +73 -0
  20. siliconcompiler/report/dashboard/utils/file_utils.py +120 -0
  21. siliconcompiler/report/dashboard/viewer.py +36 -0
  22. siliconcompiler/report/report.py +22 -4
  23. siliconcompiler/scheduler/__init__.py +43 -6
  24. siliconcompiler/schema/schema_obj.py +4 -2
  25. siliconcompiler/tools/openroad/floorplan.py +5 -0
  26. siliconcompiler/tools/openroad/openroad.py +12 -3
  27. siliconcompiler/tools/openroad/scripts/sc_cts.tcl +18 -13
  28. siliconcompiler/tools/openroad/scripts/sc_floorplan.tcl +6 -1
  29. siliconcompiler/tools/openroad/scripts/sc_procs.tcl +28 -0
  30. siliconcompiler/toolscripts/_tools.json +8 -3
  31. siliconcompiler/toolscripts/rhel8/install-chisel.sh +26 -0
  32. siliconcompiler/toolscripts/rhel8/install-ghdl.sh +25 -0
  33. siliconcompiler/toolscripts/rhel8/install-icarus.sh +40 -0
  34. siliconcompiler/toolscripts/rhel8/install-klayout.sh +17 -0
  35. siliconcompiler/toolscripts/rhel8/install-magic.sh +26 -0
  36. siliconcompiler/toolscripts/rhel8/install-montage.sh +5 -0
  37. siliconcompiler/toolscripts/rhel8/install-netgen.sh +25 -0
  38. siliconcompiler/toolscripts/rhel8/install-openroad.sh +31 -0
  39. siliconcompiler/toolscripts/rhel8/install-slang.sh +31 -0
  40. siliconcompiler/toolscripts/rhel8/install-surelog.sh +32 -0
  41. siliconcompiler/toolscripts/rhel8/install-sv2v.sh +27 -0
  42. siliconcompiler/toolscripts/rhel8/install-verible.sh +24 -0
  43. siliconcompiler/toolscripts/rhel8/install-verilator.sh +40 -0
  44. siliconcompiler/toolscripts/rhel8/install-xyce.sh +64 -0
  45. siliconcompiler/toolscripts/rhel8/install-yosys.sh +23 -0
  46. siliconcompiler/toolscripts/rhel9/install-chisel.sh +26 -0
  47. siliconcompiler/toolscripts/rhel9/install-ghdl.sh +25 -0
  48. siliconcompiler/toolscripts/rhel9/install-icarus.sh +40 -0
  49. siliconcompiler/toolscripts/rhel9/install-klayout.sh +17 -0
  50. siliconcompiler/toolscripts/rhel9/install-magic.sh +26 -0
  51. siliconcompiler/toolscripts/rhel9/install-montage.sh +5 -0
  52. siliconcompiler/toolscripts/rhel9/install-netgen.sh +25 -0
  53. siliconcompiler/toolscripts/rhel9/install-slang.sh +31 -0
  54. siliconcompiler/toolscripts/rhel9/install-surelog.sh +32 -0
  55. siliconcompiler/toolscripts/rhel9/install-sv2v.sh +27 -0
  56. siliconcompiler/toolscripts/rhel9/install-verible.sh +24 -0
  57. siliconcompiler/toolscripts/rhel9/install-verilator.sh +40 -0
  58. siliconcompiler/toolscripts/rhel9/install-xdm.sh +43 -0
  59. siliconcompiler/toolscripts/rhel9/install-xyce.sh +64 -0
  60. siliconcompiler/toolscripts/rhel9/install-yosys.sh +23 -0
  61. siliconcompiler/toolscripts/ubuntu20/install-icepack.sh +1 -1
  62. siliconcompiler/toolscripts/ubuntu20/install-xdm.sh +40 -0
  63. siliconcompiler/toolscripts/ubuntu20/install-yosys.sh +2 -2
  64. siliconcompiler/toolscripts/ubuntu22/install-icepack.sh +1 -1
  65. siliconcompiler/toolscripts/ubuntu22/install-xdm.sh +40 -0
  66. siliconcompiler/toolscripts/ubuntu22/install-yosys.sh +2 -2
  67. siliconcompiler/toolscripts/ubuntu24/install-icepack.sh +1 -1
  68. siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +2 -0
  69. siliconcompiler/toolscripts/ubuntu24/install-xdm.sh +40 -0
  70. siliconcompiler/toolscripts/ubuntu24/install-yosys.sh +2 -2
  71. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/METADATA +7 -6
  72. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/RECORD +76 -32
  73. siliconcompiler/report/streamlit_viewer.py +0 -944
  74. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/LICENSE +0 -0
  75. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/WHEEL +0 -0
  76. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/entry_points.txt +0 -0
  77. {siliconcompiler-0.28.2.dist-info → siliconcompiler-0.28.3.dist-info}/top_level.txt +0 -0
@@ -1,944 +0,0 @@
1
- import streamlit
2
- from streamlit_agraph import agraph, Node, Edge, Config
3
- from streamlit_tree_select import tree_select
4
- import streamlit_javascript
5
- from PIL import Image
6
- import os
7
- import argparse
8
- import json
9
- import pandas
10
- import altair
11
- import gzip
12
- import base64
13
- from siliconcompiler.report import report
14
- from siliconcompiler import Chip, NodeStatus, utils
15
- from siliconcompiler import __version__ as sc_version
16
- from siliconcompiler import sc_open
17
- from siliconcompiler.flowgraph import _get_flowgraph_exit_nodes, _get_flowgraph_entry_nodes
18
- from siliconcompiler.tools._common import get_tool_task
19
-
20
- # Streamlit.session_state
21
-
22
- # key: node_from_flowgraph
23
- # value: the node selected from the flowgraph or None.
24
- # purpose: if the flowgraph is hidden, we still want to return the selected node.
25
-
26
- # key: job
27
- # value: the run or added chip to inspect.
28
- # purpose: selecting a different job will reload the entire screen.
29
-
30
- # key: cfg
31
- # value: the chip loaded in with the -cfg flag.
32
- # purpose: to keep track of the original chip (default chip) without having to reload it.
33
-
34
- # key: transpose
35
- # value: whether or not the data table is transposed.
36
- # purpose: to know the state of the transpose button.
37
-
38
- # key: selected
39
- # value: a list of the selected files.
40
- # purpose: know the order in which files are selected by comparing states.
41
-
42
- # key: expanded
43
- # value: a list of the expanded folders in the file viewer.
44
- # purpose: know which folders are expanded after a reload.
45
-
46
- # key: flowgraph
47
- # value: if the flowgraph is shown or not shown
48
- # purpose: need to keep track of the state of the flowgraph
49
-
50
-
51
- # for flowgraph
52
- SUCCESS_COLOR = '#8EA604' # green
53
- PENDING_COLOR = '#F5BB00' # yellow, could use: #EC9F05
54
- FAILURE_COLOR = '#FF4E00' # red
55
- SC_LOGO_PATH = os.path.join(os.path.dirname(__file__), '..', 'data', 'logo.png')
56
- SC_FONT_PATH = \
57
- os.path.join(os.path.dirname(__file__), '..', 'data', 'RobotoMono', 'RobotoMono-Regular.ttf')
58
- SC_ABOUT = [
59
- f"SiliconCompiler {sc_version}",
60
- '''A compiler framework that automates translation from source code to
61
- silicon.''',
62
- "https://www.siliconcompiler.com/",
63
- "https://github.com/siliconcompiler/siliconcompiler/"
64
- ]
65
- SC_MENU = {"Get help": "https://docs.siliconcompiler.com/",
66
- "Report a Bug":
67
- '''https://github.com/siliconcompiler/siliconcompiler/issues''',
68
- "About": "\n\n".join(SC_ABOUT)}
69
-
70
- # opened by running command in siliconcompiler/apps/sc_dashboard.py
71
- parser = argparse.ArgumentParser('dashboard')
72
- parser.add_argument('cfg', nargs='?')
73
- args = parser.parse_args()
74
-
75
- if not args.cfg:
76
- raise ValueError('configuration not provided')
77
-
78
- if 'job' not in streamlit.session_state:
79
- with open(args.cfg, 'r') as f:
80
- config = json.load(f)
81
- chip = Chip(design='')
82
- chip.read_manifest(config["manifest"])
83
- for file_path in config['graph_chips']:
84
- graph_chip = Chip(design='')
85
- graph_chip.read_manifest(file_path)
86
- chip.schema.cfg['history'][os.path.basename(file_path)] = graph_chip.schema.cfg
87
- streamlit.set_page_config(page_title=f'{chip.design} dashboard',
88
- page_icon=Image.open(SC_LOGO_PATH), layout="wide",
89
- menu_items=SC_MENU)
90
- streamlit.session_state['cfg'] = chip
91
- streamlit.session_state['job'] = 'default'
92
- selected_chip = chip
93
- streamlit.session_state['transpose'] = False
94
- else:
95
- chip = streamlit.session_state['cfg']
96
- streamlit.set_page_config(page_title=f'{chip.design} dashboard',
97
- page_icon=Image.open(SC_LOGO_PATH), layout="wide",
98
- menu_items=SC_MENU)
99
- selected_chip = Chip(design='')
100
- job = streamlit.session_state['job']
101
- if job == 'default':
102
- selected_chip = chip
103
- else:
104
- selected_chip.schema = chip.schema.history(job)
105
- selected_chip.set('design', chip.design)
106
-
107
-
108
- def _convert_filepaths(logs_and_reports):
109
- """
110
- Converts the logs_and_reports found to the structure
111
- required by streamlit_tree_select. Success is predicated on the order of
112
- logs_and_reports outlined in report.get_files.
113
-
114
- Args:
115
- logs_and_reports (list) : A list of 3-tuples with order of a path name,
116
- folder in the..., and files in the....
117
- """
118
- if not logs_and_reports:
119
- return []
120
-
121
- all_files = {}
122
- for path_name, folders, files in logs_and_reports:
123
- all_files[path_name] = {
124
- 'files': list(files),
125
- 'folders': list(folders)
126
- }
127
-
128
- def organize_node(base_folder):
129
- nodes = []
130
-
131
- for folder in all_files[base_folder]['folders']:
132
- path = os.path.join(base_folder, folder)
133
- nodes.append({
134
- 'value': path,
135
- 'label': folder,
136
- 'children': organize_node(path)
137
- })
138
- for file in all_files[base_folder]['files']:
139
- nodes.append({
140
- 'value': os.path.join(base_folder, file),
141
- 'label': file
142
- })
143
-
144
- return nodes
145
-
146
- starting_path_name = logs_and_reports[0][0]
147
- return organize_node(starting_path_name)
148
-
149
-
150
- def get_nodes_and_edges(chip, node_dependencies, successful_path,
151
- successful_path_node_opacity=1, successful_path_node_width=3,
152
- successful_path_edge_width=5, default_node_opacity=0.2,
153
- default_node_border_width=1, default_edge_width=3):
154
- """
155
- Returns the nodes and edges required to make a streamlit_agraph.
156
-
157
- Args:
158
- chip (Chip) : The chip object that contains the schema read from.
159
- node_dependencies (dict) : Dictionary mapping nodes
160
- (as tuples of step/index) to their input nodes.
161
- successful_path (set) : Contains all the nodes that are part of the 'winning' path.
162
- succesful_path_node_opacity (float) : A number between 0 and 1 (inclusive) which
163
- represents the opacity for nodes on a successful path.
164
- succesful_path_node_border_width (int) : A number between 0 or greater
165
- which represents the width for nodes on a successful path.
166
- succesful_path_edge_width (int) : A number between 0 or greater which
167
- represents the width for edges on a successful path.
168
- default_node_opacity (float) : A number between 0 and 1 (inclusive)
169
- which represents the opacity for nodes of a node not on a successful path.
170
- default_node_border_width (int) : A number between 0 or greater
171
- which represents the width for nodes not on a successful path.
172
- default_edge_width (int) : A number between 0 or greater which
173
- represents the width for edges not on a successful path.
174
- """
175
- nodes = []
176
- edges = []
177
- for step, index in node_dependencies:
178
- node_opacity = default_node_opacity
179
- node_border_width = default_node_border_width
180
- if (step, index) in successful_path:
181
- node_opacity = successful_path_node_opacity
182
- if (step, index) in _get_flowgraph_exit_nodes(chip, chip.get('option', 'flow')) or \
183
- (step, index) in _get_flowgraph_entry_nodes(chip, chip.get('option', 'flow')):
184
- node_border_width = successful_path_node_width
185
- node_status = chip.get('record', 'status', step=step, index=index)
186
- if node_status == NodeStatus.SUCCESS:
187
- node_color = SUCCESS_COLOR
188
- elif node_status == NodeStatus.ERROR:
189
- node_color = FAILURE_COLOR
190
- else:
191
- node_color = PENDING_COLOR
192
- tool, task = get_tool_task(chip, step, index)
193
- node_name = f'{step}{index}'
194
- label = node_name + "\n" + tool + "/" + task
195
- if tool == 'builtin':
196
- label = node_name + "\n" + tool
197
- nodes.append(Node(id=node_name, label=label, color=node_color, opacity=node_opacity,
198
- borderWidth=node_border_width, shape='oval'))
199
- for source_step, source_index in node_dependencies[step, index]:
200
- edge_width = default_edge_width
201
- if (source_step, source_index) in successful_path and \
202
- (source_step, source_index) in successful_path:
203
- edge_width = successful_path_edge_width
204
- edges.append(Edge(source=source_step + source_index, dir='up', target=node_name,
205
- width=edge_width, color='black', curve=True))
206
- return nodes, edges
207
-
208
-
209
- def file_viewer_module(display_file_content, chip, step, index, header_col_width=0.89):
210
- """
211
- Displays the file if present. If not, displays an error message.
212
-
213
- Args:
214
- display_file_content (bool) : True if there is a file selected to display
215
- header_col_width (float) : A number between 0 and 1 which is the
216
- percentage of the width of the screen given to the header. The rest
217
- is given to the download button.
218
- """
219
- if not display_file_content:
220
- streamlit.error('Select a file in the metrics tab first!')
221
- return
222
- path = streamlit.session_state['selected'][0]
223
- # This file extension may be '.gz', if it is, it is compressed.
224
- _, compressed_file_extension = os.path.splitext(path)
225
- # This is the true file_extension of the file, regardless of if it is
226
- # compressed or not.
227
- file_extension = utils.get_file_ext(path)
228
- header_col, download_col = \
229
- streamlit.columns([header_col_width, 1 - header_col_width], gap='small')
230
- relative_path = os.path.relpath(path, chip.getworkdir(step=step, index=index))
231
- with header_col:
232
- streamlit.header(relative_path)
233
- with download_col:
234
- streamlit.markdown(' ') # aligns download button with title
235
- streamlit.download_button(label="Download file",
236
- data=path,
237
- file_name=relative_path)
238
-
239
- if file_extension.lower() in ("png", "jpg"):
240
- streamlit.image(path)
241
- else:
242
- try:
243
- open_func, open_args = sc_open, []
244
- if compressed_file_extension == '.gz':
245
- open_func, open_args = gzip.open, ['rt']
246
-
247
- with open_func(path, *open_args) as fid:
248
- content = fid.read()
249
- if file_extension.lower() == "json":
250
- streamlit.json(content)
251
- else:
252
- streamlit.code(content, language='markdown', line_numbers=True)
253
- except UnicodeDecodeError: # might be OSError, not sure yet
254
- streamlit.markdown('Cannot read file')
255
-
256
-
257
- def show_files(chip, step, index):
258
- """
259
- Displays the logs and reports using streamlit_tree_select.
260
-
261
- Args:
262
- chip (Chip) : the chip object that contains the schema read from.
263
- step (string) : step of node.
264
- index (string) : index of node.
265
- """
266
- streamlit.subheader(f'{step}{index} Files')
267
- logs_and_reports = report.get_files(chip, step, index)
268
- logs_and_reports = _convert_filepaths(logs_and_reports)
269
- if logs_and_reports == []:
270
- streamlit.markdown('No files to show')
271
- return False
272
- # kinda janky at the moment, does not always flip immediately
273
- # TODO make so that selection changes on first click
274
- if "selected" not in streamlit.session_state:
275
- streamlit.session_state['selected'] = []
276
- if "expanded" not in streamlit.session_state:
277
- streamlit.session_state['expanded'] = []
278
-
279
- selected = tree_select(logs_and_reports,
280
- expand_on_click=True,
281
- checked=streamlit.session_state['selected'],
282
- expanded=streamlit.session_state['expanded'],
283
- only_leaf_checkboxes=True)
284
- # only include files in 'checked' (folders are also included when they are opened)
285
- selected['checked'] = [x for x in selected['checked'] if os.path.isfile(x)]
286
- if len(selected['checked']) == 0:
287
- streamlit.session_state['selected'] = []
288
- elif len(selected["checked"]) == 1:
289
- streamlit.session_state['selected'] = [*selected["checked"]]
290
- elif len(selected["checked"]) > 1:
291
- newly_selected = selected["checked"][0]
292
- if len(streamlit.session_state['selected']) != 0:
293
- for x in selected["checked"]:
294
- if x != streamlit.session_state['selected'][0]:
295
- newly_selected = x
296
- break
297
- streamlit.session_state['selected'] = [newly_selected]
298
- streamlit.session_state['expanded'] = [*selected["expanded"]]
299
- streamlit.rerun()
300
- if streamlit.session_state['selected'] != []:
301
- return True
302
- return False
303
-
304
-
305
- def show_metrics_for_file(chip, step, index):
306
- """
307
- Displays the metrics that are included in each file.
308
-
309
- Args:
310
- chip (Chip) : The chip object that contains the schema read from.
311
- step (string) : Step of node.
312
- index (string) : Index of node.
313
- """
314
- if 'selected' in streamlit.session_state and \
315
- len(streamlit.session_state['selected']) == 1:
316
- file = streamlit.session_state['selected'][0]
317
- metrics_of_file = report.get_metrics_source(chip, step, index)
318
- file = os.path.relpath(file, f"/{step}/{index}")
319
- if file in metrics_of_file:
320
- metrics = ", ".join(metrics_of_file[file]) + "."
321
- streamlit.success("This file includes the metrics of " + metrics)
322
- else:
323
- streamlit.warning("This file does not include any metrics.")
324
-
325
-
326
- def manifest_module(chip, manifest, ui_width, max_num_of_keys_to_show=20,
327
- default_toggle_width_in_percent=0.2,
328
- default_toggle_width_in_pixels=200, header_col_width=0.85):
329
- """
330
- Displays the manifest and a way to search through the manifest.
331
-
332
- Args:
333
- chip (Chip) : The chip object that contains the schema read from.
334
- manifest (dict) : Layered dictionary containing a filtered version of the
335
- chip.schema.cfg
336
- ui_width (int) : The width of the screen of the web browser in pixels.
337
- max_num_of_keys_to_show (int) : The maximum number of keys that the
338
- manifest may have in order to be automatically expanded.
339
- default_toggle_width_in_percent (float) : A number between 0 and 1
340
- which is the maximum percentage of the width of the screen given to
341
- the checkbox. The rest is given to the search bars.
342
- default_toggle_width_in_pixels (int) : A number greater than 0 which
343
- is the maximum pixel width of the screen given to the checkbox. The
344
- rest is given to the search bars.
345
- header_col_width (float) : A number between 0 and 1 which is the maximum
346
- percentage of the width of the screen given to the header. The rest
347
- is given to the download button.
348
- """
349
- # TODO include toggle to expand the tree, find less redundant header name
350
- header_col, download_col = streamlit.columns([header_col_width, 1 - header_col_width],
351
- gap='small')
352
- with header_col:
353
- streamlit.header('Manifest Tree')
354
- if ui_width > 0:
355
- toggle_col_width_in_percent = \
356
- min(default_toggle_width_in_pixels / ui_width, default_toggle_width_in_percent)
357
- else:
358
- toggle_col_width_in_percent = default_toggle_width_in_percent
359
- search_col_width = (1 - toggle_col_width_in_percent) / 2
360
- key_search_col, value_search_col, toggle_col = \
361
- streamlit.columns([search_col_width, search_col_width,
362
- toggle_col_width_in_percent], gap="large")
363
- with toggle_col:
364
- # to align the checkbox with the search bars
365
- streamlit.markdown('')
366
- streamlit.markdown('')
367
- if streamlit.checkbox('Raw manifest',
368
- help='Click here to see the JSON before it was made more readable'):
369
- manifest_to_show = chip.schema.cfg
370
- else:
371
- manifest_to_show = manifest
372
- with key_search_col:
373
- key = streamlit.text_input('Search Keys', '', placeholder="Keys")
374
- if key != '':
375
- manifest_to_show = report.search_manifest(manifest_to_show, key_search=key)
376
- with value_search_col:
377
- value = streamlit.text_input('Search Values', '', placeholder="Values")
378
- if value != '':
379
- manifest_to_show = report.search_manifest(manifest_to_show, value_search=value)
380
- with download_col:
381
- streamlit.markdown(' ') # aligns download button with title
382
- streamlit.download_button(label='Download manifest', file_name='manifest.json',
383
- data=json.dumps(manifest_to_show, indent=4),
384
- mime="application/json")
385
- numOfKeys = report.get_total_manifest_key_count(manifest_to_show)
386
- streamlit.json(manifest_to_show, expanded=(numOfKeys < max_num_of_keys_to_show))
387
-
388
-
389
- def select_nodes(metric_dataframe, node_from_flowgraph):
390
- """
391
- Displays selectbox for nodes to show in the node information panel. Since
392
- both the flowgraph and selectbox show which node's information is
393
- displayed, the one clicked more recently will be displayed.
394
-
395
- Args:
396
- metric_dataframe (Pandas.DataFrame) : Contains the metrics of all nodes.
397
- node_from_flowgraph (string/None) : Contains a string of the node to
398
- display or None if none exists.
399
- """
400
- option = metric_dataframe.columns.tolist()[0]
401
- with streamlit.expander("Select Node"):
402
- with streamlit.form("nodes"):
403
- option = streamlit.selectbox('Pick a node to inspect',
404
- metric_dataframe.columns.tolist())
405
- params_submitted = streamlit.form_submit_button('Apply')
406
- if not params_submitted and node_from_flowgraph is not None:
407
- option = node_from_flowgraph
408
- streamlit.session_state['selected'] = []
409
- if params_submitted:
410
- streamlit.session_state['selected'] = []
411
- return option
412
-
413
-
414
- def metrics_dataframe_module(metric_dataframe, metric_to_metric_unit_map):
415
- """
416
- Displays multi-select check box to the users which allows them to select
417
- which nodes and metrics to view in the dataframe.
418
-
419
- Args:
420
- metric_dataframe (Pandas.DataFrame) : Contains the metrics of all nodes.
421
- metric_to_metric_unit_map (dict) : Maps the metric to the associated metric unit.
422
- """
423
- show_dataframe_header()
424
- container = streamlit.container()
425
- transpose = streamlit.session_state['transpose']
426
- if transpose:
427
- metric_dataframe = metric_dataframe.transpose()
428
- metrics_list = metric_dataframe.columns.tolist()
429
- node_list = metric_dataframe.index.tolist()
430
- else:
431
- metrics_list = metric_dataframe.index.tolist()
432
- node_list = metric_dataframe.columns.tolist()
433
- display_to_data = {}
434
- display_options = []
435
- for metric_unit in metrics_list:
436
- metric = metric_to_metric_unit_map[metric_unit]
437
- display_to_data[metric] = metric_unit
438
- display_options.append(metric)
439
- options = {'metrics': [], 'nodes': []}
440
- # pick parameters
441
- with streamlit.expander("Select Parameters"):
442
- with streamlit.form("params"):
443
- nodes = streamlit.multiselect('Pick nodes to include', node_list, [])
444
- options['nodes'] = nodes
445
- metrics = streamlit.multiselect('Pick metrics to include?', display_options, [])
446
- options['metrics'] = []
447
- for metric in metrics:
448
- options['metrics'].append(display_to_data[metric])
449
- streamlit.form_submit_button('Apply')
450
- if not options['nodes']:
451
- options['nodes'] = node_list
452
- if not options['metrics']:
453
- options['metrics'] = metrics_list
454
- # showing the dataframe
455
- # TODO By July 2024, Streamlit will let catch click events on the dataframe
456
- if transpose:
457
- container.dataframe((metric_dataframe.loc[options['nodes'], options['metrics']]),
458
- use_container_width=True)
459
- else:
460
- container.dataframe((metric_dataframe.loc[options['metrics'], options['nodes']]),
461
- use_container_width=True)
462
-
463
-
464
- def show_dataframe_header(header_col_width=0.7):
465
- """
466
- Displays the header and toggle for the dataframe. If the toggle is flipped,
467
- it will update the view of the dataframe accordingly.
468
-
469
- Args:
470
- header_col_width (float) : A number between 0 and 1 which is the
471
- percentage of the width of the screen given to the header. The rest
472
- is given to the transpose toggle.
473
- """
474
- header_col, transpose_col = streamlit.columns([header_col_width, 1 - header_col_width],
475
- gap="large")
476
- with header_col:
477
- streamlit.header('Metrics')
478
- with transpose_col:
479
- streamlit.markdown('')
480
- streamlit.markdown('')
481
- streamlit.session_state['transpose'] = \
482
- streamlit.checkbox('Transpose', help='Click here to see the table transposed')
483
-
484
-
485
- def display_flowgraph_toggle(label_after, vertical_layout_collapsed=False):
486
- """
487
- Displays the toggle for the flowgraph.
488
-
489
- Args:
490
- label_after (bool) : the default label for the toggle
491
- vertical_layout_collapsed (bool) : If vertical_layout_collapsed,
492
- there is no need to align the toggle with the header
493
- """
494
- if not vertical_layout_collapsed:
495
- # this horizontally aligns the toggle with the header
496
- streamlit.markdown("")
497
- streamlit.markdown("")
498
- fg_toggle = not streamlit.checkbox('Hide flowgraph', help='Click here to hide the flowgraph')
499
- streamlit.session_state['flowgraph'] = fg_toggle
500
- if streamlit.session_state['flowgraph'] != label_after:
501
- streamlit.rerun()
502
-
503
-
504
- def show_flowgraph(chip):
505
- '''
506
- This function creates, displays, and returns the selected node of the flowgraph.
507
-
508
- Args:
509
- chip (Chip) : The chip object that contains the schema read from.
510
- '''
511
- nodes, edges = get_nodes_and_edges(chip, report.get_flowgraph_edges(chip),
512
- report.get_flowgraph_path(chip))
513
- config = Config(width='100%', directed=True, physics=False, hierarchical=True,
514
- clickToUse=True, nodeSpacing=150, levelSeparation=100,
515
- sortMethod='directed')
516
- node_from_flowgraph = agraph(nodes=nodes, edges=edges, config=config)
517
- return node_from_flowgraph
518
-
519
-
520
- def show_title_and_runs(title_col_width=0.7):
521
- """
522
- Displays the title and a selectbox that allows you to select a given run
523
- to inspect.
524
-
525
- Args:
526
- title_col_width (float) : A number between 0 and 1 which is the percentage of the
527
- width of the screen given to the title and logo. The rest is given to selectbox.
528
- """
529
- title_col, job_select_col = \
530
- streamlit.columns([title_col_width, 1 - title_col_width], gap="large")
531
- with title_col:
532
- streamlit.markdown(
533
- '''
534
- <head>
535
- <style>
536
- /* Define the @font-face rule */
537
- @font-face {
538
- font-family: 'Roboto Mono';
539
- src: url(SC_FONT_PATH) format('truetype');
540
- font-weight: normal;
541
- font-style: normal;
542
- }
543
-
544
- /* Styles for the logo and text */
545
- .logo-container {
546
- display: flex;
547
- align-items: flex-start;
548
- }
549
-
550
- .logo-image {
551
- margin-right: 10px;
552
- margin-top: -10px;
553
- }
554
-
555
- .logo-text {
556
- display: flex;
557
- flex-direction: column;
558
- margin-top: -20px;
559
- }
560
-
561
- .text1 {
562
- color: #F1C437; /* Yellow color */
563
- font-family: 'Roboto Mono', sans-serif;
564
- font-weight: 700 !important;
565
- font-size: 30px !important;
566
- margin-bottom: -16px;
567
- }
568
-
569
- .text2 {
570
- color: #1D4482; /* Blue color */
571
- font-family: 'Roboto Mono', sans-serif;
572
- font-weight: 700 !important;
573
- font-size: 30px !important;
574
- }
575
-
576
- </style>
577
- </head>''',
578
- unsafe_allow_html=True
579
- )
580
-
581
- streamlit.markdown(
582
- f'''
583
- <body>
584
- <div class="logo-container">
585
- <img src="data:image/png;base64,{base64.b64encode(open(SC_LOGO_PATH,
586
- "rb").read()).decode()}" alt="Logo Image" class="logo-image" height="61">
587
- <div class="logo-text">
588
- <p class="text1">{streamlit.session_state['cfg'].design}</p>
589
- <p class="text2">dashboard</p>
590
- </div>
591
- </div>
592
- </body>
593
- ''',
594
- unsafe_allow_html=True
595
- )
596
- with job_select_col:
597
- all_jobs = streamlit.session_state['cfg'].getkeys('history')
598
- all_jobs.insert(0, 'default')
599
- job = streamlit.selectbox('pick a job', all_jobs,
600
- label_visibility='collapsed')
601
- previous_job = streamlit.session_state['job']
602
- streamlit.session_state['job'] = job
603
- if previous_job != job:
604
- streamlit.rerun()
605
-
606
-
607
- def show_metric_and_node_selection_for_graph(metrics, nodes, graph_number):
608
- """
609
- Displays selectbox for metrics and nodes which informs the graph on what
610
- to display.
611
-
612
- Args:
613
- metrics (list) : A list of metrics that are set for all chips given in chips.
614
- nodes (list) : A list of nodes given in the form f'{step}{index}'
615
- graph_number (int) : The number of graphs there are. Used to create
616
- keys to distinguish selectboxes from each other.
617
- """
618
- metric_selector_col, node_selector_col = streamlit.columns(2, gap='small')
619
- with metric_selector_col:
620
- with streamlit.expander('Select a Metric'):
621
- with streamlit.form(f'metrics{graph_number}'):
622
- selected_metric = streamlit.selectbox('Select a Metric', metrics,
623
- label_visibility='collapsed',
624
- key=f'metric selection {graph_number}')
625
- streamlit.form_submit_button('Apply')
626
- with node_selector_col:
627
- with streamlit.expander('Select Nodes'):
628
- with streamlit.form(f'nodes{graph_number}'):
629
- selected_nodes = \
630
- streamlit.multiselect('Select a Node', nodes, label_visibility='collapsed',
631
- key=f'node selection {graph_number}', default=nodes[0])
632
- streamlit.form_submit_button('Apply')
633
- return selected_metric, selected_nodes
634
-
635
-
636
- def show_graph(data, x_axis_label, y_axis_label, color_label, height=300):
637
- """
638
- Displays a graph with the given "data" on the y-axis and "jobs" on the x-axis.
639
-
640
- Args:
641
- data (Pandas.DataFrame) : A dataframe containing all the graphing data.
642
- x_axis_label (string) : The name of the runs column.
643
- y_axis_label (string) : The name of the jobs column.
644
- color_label (string) : The name of the nodes column.
645
- height (int) : The height of one graph.
646
- """
647
- x_axis = altair.X(x_axis_label, axis=altair.Axis(labelAngle=-75))
648
- y_axis = y_axis_label
649
- color = color_label
650
- chart = altair.Chart(data, height=height).mark_line(point=True).encode(x=x_axis, y=y_axis,
651
- color=color)
652
- streamlit.altair_chart(chart, use_container_width=True, theme='streamlit')
653
-
654
-
655
- def select_runs(jobs):
656
- """
657
- Displays a dataframe that can be edited to select specific jobs to include
658
- in the analysis.
659
-
660
- Args:
661
- jobs (list) : A list of job names.
662
- """
663
- all_jobs = pandas.DataFrame({'job names': jobs, 'selected jobs': [True] * len(jobs)})
664
- configuration = {'selected jobs': streamlit.column_config.CheckboxColumn('Select runs',
665
- default=True)}
666
- filtered_jobs = streamlit.data_editor(all_jobs, disabled=['job names'],
667
- use_container_width=True, hide_index=True,
668
- column_config=configuration)
669
- return filtered_jobs
670
-
671
-
672
- def structure_graph_data(chips, metric, selected_jobs, nodes):
673
- """
674
- Displays a graph and it's corresponding metric and node selection.
675
-
676
- Args:
677
- chips (list) : A list of tuples in the form (chip, chip name) where
678
- the chip name is a string.
679
- metric (string) : The metric to be inspected.
680
- selected_jobs (pandas.DataFrame) : A dataframe with a column called
681
- 'selected jobs' which identifies which jobs the user wants to see
682
- and a corresponding column called 'job names'.
683
- nodes (list) : A list of dictionaries with the form (step, index).
684
- """
685
- x_axis_label = 'runs'
686
- y_axis_label = metric
687
- color_label = 'nodes'
688
- if not nodes:
689
- show_graph(pandas.DataFrame({x_axis_label: [], y_axis_label: [], color_label: []}),
690
- x_axis_label, y_axis_label, color_label)
691
- return
692
- data, metric_unit = report.get_chart_data(chips, metric, nodes)
693
- if metric_unit:
694
- y_axis_label = f'{metric}({metric_unit})'
695
- filtered_data = {x_axis_label: [], y_axis_label: [], color_label: []}
696
- # filtering through data
697
- for is_selected, job_name in zip(selected_jobs['selected jobs'].tolist(),
698
- selected_jobs['job names'].tolist()):
699
- if is_selected:
700
- for step, index in data:
701
- filtered_data[x_axis_label].append(job_name)
702
- filtered_data[color_label].append(step + index)
703
- if job_name not in data[(step, index)].keys():
704
- filtered_data[y_axis_label].append(None)
705
- else:
706
- filtered_data[y_axis_label].append(data[(step, index)][job_name])
707
- show_graph(pandas.DataFrame(filtered_data).dropna(), x_axis_label, y_axis_label, color_label)
708
-
709
-
710
- def flowgraph_layout_vertical_flowgraph(chip, ui_width):
711
- '''
712
- This function dynamically calculates what the flowgraph's width should be.
713
- This function is specific to the vertical_flowgraph layout. It returns the
714
- node selected from the flowgraph and the column for the metric section and
715
- node information section.
716
-
717
- Args:
718
- chip (Chip) : The chip object that contains the schema read from.
719
- ui_width (int) : The width of the browser in terms of pixels.
720
- '''
721
- if streamlit.session_state['flowgraph']:
722
- default_flowgraph_width_in_percent = 0.4
723
- flowgraph_col_width_in_pixels = 520
724
- if ui_width > 0:
725
- flowgraph_col_width_in_percent = \
726
- min(flowgraph_col_width_in_pixels / ui_width, default_flowgraph_width_in_percent)
727
- else:
728
- flowgraph_col_width_in_percent = default_flowgraph_width_in_percent
729
- flowgraph_col, dataframe_and_node_info_col = \
730
- streamlit.columns([flowgraph_col_width_in_percent,
731
- 1 - flowgraph_col_width_in_percent], gap="large")
732
- with flowgraph_col:
733
- header_col, toggle_col = streamlit.columns(2, gap="large")
734
- with header_col:
735
- streamlit.header('Flowgraph')
736
- with toggle_col:
737
- display_flowgraph_toggle(True)
738
- node_from_flowgraph = show_flowgraph(chip)
739
- else:
740
- display_flowgraph_toggle(False)
741
- dataframe_and_node_info_col = streamlit.container()
742
- node_from_flowgraph = streamlit.session_state['node_from_flowgraph']
743
- return node_from_flowgraph, dataframe_and_node_info_col
744
-
745
-
746
- def node_metric_dataframe(node_name, metrics, height=None):
747
- '''
748
- Displays the node metric dataframe.
749
-
750
- Args:
751
- node_name (string) : The name of the node selected.
752
- metrics (pandas.DataFrame) : The metrics for the node corresponding with
753
- node_name with the Null values removed.
754
- height (int or None) : The height of the dataframe. If None, streamlit
755
- automatically calculates it.
756
- '''
757
- streamlit.subheader(f'{node_name} Metrics')
758
- streamlit.dataframe(metrics, use_container_width=True, height=height)
759
-
760
-
761
- def node_details_dataframe(chip, step, index, height=None):
762
- '''
763
- Displays the node details dataframe.
764
-
765
- Args:
766
- chip (Chip) : the chip object that contains the schema read from.
767
- step (string) : step of node.
768
- index (string) : index of node.
769
- height (int or None) : The height of the dataframe. If None, streamlit
770
- automatically calculates it.
771
- '''
772
- streamlit.subheader(f'{step}{index} Details')
773
- nodes = {}
774
- nodes[step + index] = report.get_flowgraph_nodes(chip, step, index)
775
- node_reports = pandas.DataFrame.from_dict(nodes)
776
- streamlit.dataframe(node_reports, use_container_width=True, height=height)
777
-
778
-
779
- def design_preview_module(chip):
780
- '''
781
- Displays the design preview.
782
-
783
- Args:
784
- chip (Chip) : the chip object that contains the schema read from.
785
- '''
786
- streamlit.header('Design Preview')
787
- streamlit.image(f'{chip.getworkdir()}/{chip.design}.png')
788
-
789
-
790
- def make_node_to_step_index_map(metric_dataframe):
791
- '''
792
- Returns a map from the name of a node to the associated step, index pair.
793
-
794
- Args:
795
- metric_dataframe (pandas.DataFrame) : A dataframe full of all metrics and all
796
- nodes of the selected chip
797
- '''
798
- node_to_step_index_map = {}
799
- for step, index in metric_dataframe.columns.tolist():
800
- node_to_step_index_map[step + index] = (step, index)
801
- # concatenate step and index
802
- metric_dataframe.columns = metric_dataframe.columns.map(lambda x: f'{x[0]}{x[1]}')
803
- return node_to_step_index_map, metric_dataframe
804
-
805
-
806
- def make_metric_to_metric_unit_map(metric_dataframe):
807
- '''
808
- Returns a map from the name of a metric to the associated metric and unit in
809
- the form f'{x[0]} ({x[1]})'
810
-
811
- Args:
812
- metric_dataframe (pandas.DataFrame) : A dataframe full of all metrics and all
813
- nodes of the selected chip.
814
- '''
815
- metric_to_metric_unit_map = {}
816
- for metric, unit in metric_dataframe.index.tolist():
817
- if unit != '':
818
- metric_to_metric_unit_map[f'{metric} ({unit})'] = metric
819
- else:
820
- metric_to_metric_unit_map[metric] = metric
821
- # concatenate metric and unit
822
- metric_dataframe.index = metric_dataframe.index.map(lambda x: f'{x[0]} ({x[1]})'
823
- if x[1] else x[0])
824
- return metric_to_metric_unit_map, metric_dataframe
825
-
826
-
827
- def graphs_module(metric_dataframe, node_to_step_index_map, metric_to_metric_unit_map):
828
- '''
829
- This displays the graph module.
830
-
831
- Args:
832
- metric_dataframe (pandas.DataFrame) : A dataframe full of all metrics and all
833
- nodes of the selected chip.
834
- node_to_step_index_map (dict) : Maps the node to the associated step, index pair.
835
- metric_to_metric_unit_map (dict) : Maps the metric to the associated metric unit.
836
- '''
837
- metrics = metric_dataframe.index.map(lambda x: metric_to_metric_unit_map[x])
838
- nodes = metric_dataframe.columns
839
- chips = []
840
- jobs = []
841
- for job in streamlit.session_state['cfg'].getkeys('history'):
842
- selected_chip = Chip(design='')
843
- selected_chip.schema = chip.schema.history(job)
844
- selected_chip.set('design', chip.design)
845
- chips.append({'chip_object': selected_chip, 'chip_name': job})
846
- jobs.append(job)
847
- job_selector_col, graph_adder_col = streamlit.columns(2, gap='large')
848
- with job_selector_col:
849
- with streamlit.expander('Select Jobs'):
850
- selected_jobs = select_runs(jobs)
851
- with graph_adder_col:
852
- graphs = streamlit.slider('pick the number of graphs you want', 1, 10,
853
- 1, label_visibility='collapsed')
854
- graph_number = 1
855
- left_graph_col, right_graph_col = streamlit.columns(2, gap='large')
856
- while graph_number <= graphs:
857
- if graph_number % 2 == 1:
858
- graph_col = left_graph_col
859
- else:
860
- graph_col = right_graph_col
861
- with graph_col:
862
- metric, selected_nodes = \
863
- show_metric_and_node_selection_for_graph(metrics, nodes, graph_number)
864
- nodes_as_step_and_index = []
865
- for selected_node in selected_nodes:
866
- step, index = node_to_step_index_map[selected_node]
867
- nodes_as_step_and_index.append((step, index))
868
- structure_graph_data(chips, metric, selected_jobs, nodes_as_step_and_index)
869
- if not (graph_number == graphs or graph_number == graphs - 1):
870
- streamlit.divider()
871
- graph_number += 1
872
-
873
-
874
- def make_tabs(metric_dataframe, chip, node_to_step_index_map):
875
- '''
876
- Creates all the tabs. Displays the modules for the tabs that may or may not exist
877
- which include the graphs tab and design preview tab. Returns the rest of the tabs.
878
-
879
- Args:
880
- metric_dataframe (pandas.DataFrame) : A dataframe full of all metrics and all
881
- nodes of the selected chip
882
- chip (Chip) : The chip object that contains the schema read from.
883
- node_to_step_index_map (dict) : Maps the node to the associated step, index pair
884
- '''
885
- if 'flowgraph' not in streamlit.session_state:
886
- streamlit.session_state['flowgraph'] = True
887
- tabs = ["Metrics", "Manifest", "File Viewer"]
888
- num_of_chips = len(streamlit.session_state['cfg'].getkeys('history'))
889
- if os.path.isfile(f'{chip.getworkdir()}/{chip.design}.png') & num_of_chips > 1:
890
- metrics_tab, manifest_tab, file_viewer_tab, design_preview_tab, graphs_tab = \
891
- streamlit.tabs(tabs + ["Graphs", "Design Preview"])
892
- with graphs_tab:
893
- graphs_module(metric_dataframe, node_to_step_index_map, metric_to_metric_unit_map)
894
- with design_preview_tab:
895
- design_preview_module(chip)
896
- elif os.path.isfile(f'{chip.getworkdir()}/{chip.design}.png'):
897
- metrics_tab, manifest_tab, file_viewer_tab, design_preview_tab = \
898
- streamlit.tabs(tabs + ["Design Preview"])
899
- with design_preview_tab:
900
- design_preview_module(chip)
901
- elif num_of_chips > 1:
902
- metrics_tab, manifest_tab, file_viewer_tab, graphs_tab = streamlit.tabs(tabs + ["Graphs"])
903
- with graphs_tab:
904
- graphs_module(metric_dataframe, node_to_step_index_map, metric_to_metric_unit_map)
905
- else:
906
- metrics_tab, manifest_tab, file_viewer_tab = streamlit.tabs(tabs)
907
- return metrics_tab, manifest_tab, file_viewer_tab
908
-
909
-
910
- if 'node_from_flowgraph' not in streamlit.session_state:
911
- streamlit.session_state['node_from_flowgraph'] = None
912
- # TODO find more descriptive way to describe layouts
913
- layout = 'vertical_flowgraph'
914
- # gathering data
915
- metric_dataframe = report.make_metric_dataframe(selected_chip)
916
- node_to_step_index_map, metric_dataframe = make_node_to_step_index_map(metric_dataframe)
917
- metric_to_metric_unit_map, metric_dataframe = make_metric_to_metric_unit_map(metric_dataframe)
918
- manifest = report.make_manifest(selected_chip)
919
- if layout == 'vertical_flowgraph':
920
- show_title_and_runs()
921
- metrics_tab, manifest_tab, file_viewer_tab = make_tabs(metric_dataframe, selected_chip,
922
- node_to_step_index_map)
923
- ui_width = streamlit_javascript.st_javascript("window.innerWidth")
924
- with metrics_tab:
925
- node_from_flowgraph, datafram_and_node_info_col = \
926
- flowgraph_layout_vertical_flowgraph(selected_chip, ui_width)
927
- streamlit.session_state['node_from_flowgraph'] = node_from_flowgraph
928
- with datafram_and_node_info_col:
929
- metrics_dataframe_module(metric_dataframe, metric_to_metric_unit_map)
930
- streamlit.header('Node Information')
931
- metrics_col, records_col, logs_and_reports_col = streamlit.columns(3, gap='small')
932
- selected_node = select_nodes(metric_dataframe, node_from_flowgraph)
933
- step, index = node_to_step_index_map[selected_node]
934
- with metrics_col:
935
- node_metric_dataframe(selected_node, metric_dataframe[selected_node].dropna())
936
- with records_col:
937
- node_details_dataframe(selected_chip, step, index)
938
- with logs_and_reports_col:
939
- display_file_content = show_files(selected_chip, step, index)
940
- show_metrics_for_file(selected_chip, step, index)
941
- with manifest_tab:
942
- manifest_module(selected_chip, manifest, ui_width)
943
- with file_viewer_tab:
944
- file_viewer_module(display_file_content, selected_chip, step, index)