siliconcompiler 0.34.1__py3-none-any.whl → 0.34.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.
- siliconcompiler/__init__.py +23 -4
- siliconcompiler/__main__.py +1 -7
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/_common.py +104 -23
- siliconcompiler/apps/sc.py +4 -8
- siliconcompiler/apps/sc_dashboard.py +6 -4
- siliconcompiler/apps/sc_install.py +10 -6
- siliconcompiler/apps/sc_issue.py +7 -5
- siliconcompiler/apps/sc_remote.py +1 -1
- siliconcompiler/apps/sc_server.py +9 -14
- siliconcompiler/apps/sc_show.py +7 -6
- siliconcompiler/apps/smake.py +130 -94
- siliconcompiler/apps/utils/replay.py +4 -7
- siliconcompiler/apps/utils/summarize.py +3 -5
- siliconcompiler/asic.py +420 -0
- siliconcompiler/checklist.py +25 -2
- siliconcompiler/cmdlineschema.py +534 -0
- siliconcompiler/constraints/__init__.py +17 -0
- siliconcompiler/constraints/asic_component.py +378 -0
- siliconcompiler/constraints/asic_floorplan.py +449 -0
- siliconcompiler/constraints/asic_pins.py +489 -0
- siliconcompiler/constraints/asic_timing.py +517 -0
- siliconcompiler/core.py +10 -35
- siliconcompiler/data/templates/tcl/manifest.tcl.j2 +8 -0
- siliconcompiler/dependencyschema.py +96 -202
- siliconcompiler/design.py +327 -241
- siliconcompiler/filesetschema.py +250 -0
- siliconcompiler/flowgraph.py +298 -106
- siliconcompiler/fpga.py +124 -1
- siliconcompiler/library.py +331 -0
- siliconcompiler/metric.py +327 -92
- siliconcompiler/metrics/__init__.py +7 -0
- siliconcompiler/metrics/asic.py +245 -0
- siliconcompiler/metrics/fpga.py +220 -0
- siliconcompiler/package/__init__.py +391 -67
- siliconcompiler/package/git.py +92 -16
- siliconcompiler/package/github.py +114 -22
- siliconcompiler/package/https.py +79 -16
- siliconcompiler/packageschema.py +341 -16
- siliconcompiler/pathschema.py +255 -0
- siliconcompiler/pdk.py +566 -1
- siliconcompiler/project.py +1460 -0
- siliconcompiler/record.py +38 -1
- siliconcompiler/remote/__init__.py +5 -2
- siliconcompiler/remote/client.py +11 -6
- siliconcompiler/remote/schema.py +5 -23
- siliconcompiler/remote/server.py +41 -54
- siliconcompiler/report/__init__.py +3 -3
- siliconcompiler/report/dashboard/__init__.py +48 -14
- siliconcompiler/report/dashboard/cli/__init__.py +99 -21
- siliconcompiler/report/dashboard/cli/board.py +364 -179
- siliconcompiler/report/dashboard/web/__init__.py +90 -12
- siliconcompiler/report/dashboard/web/components/__init__.py +219 -240
- siliconcompiler/report/dashboard/web/components/flowgraph.py +49 -26
- siliconcompiler/report/dashboard/web/components/graph.py +139 -100
- siliconcompiler/report/dashboard/web/layouts/__init__.py +29 -1
- siliconcompiler/report/dashboard/web/layouts/_common.py +38 -2
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph.py +39 -26
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_node_tab.py +50 -50
- siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_sac_tabs.py +49 -46
- siliconcompiler/report/dashboard/web/state.py +141 -14
- siliconcompiler/report/dashboard/web/utils/__init__.py +79 -16
- siliconcompiler/report/dashboard/web/utils/file_utils.py +74 -11
- siliconcompiler/report/dashboard/web/viewer.py +25 -1
- siliconcompiler/report/report.py +5 -2
- siliconcompiler/report/summary_image.py +29 -11
- siliconcompiler/scheduler/__init__.py +9 -1
- siliconcompiler/scheduler/docker.py +81 -4
- siliconcompiler/scheduler/run_node.py +37 -20
- siliconcompiler/scheduler/scheduler.py +211 -36
- siliconcompiler/scheduler/schedulernode.py +394 -60
- siliconcompiler/scheduler/send_messages.py +77 -29
- siliconcompiler/scheduler/slurm.py +76 -12
- siliconcompiler/scheduler/taskscheduler.py +142 -21
- siliconcompiler/schema/__init__.py +0 -4
- siliconcompiler/schema/baseschema.py +338 -59
- siliconcompiler/schema/editableschema.py +14 -6
- siliconcompiler/schema/journal.py +28 -17
- siliconcompiler/schema/namedschema.py +22 -14
- siliconcompiler/schema/parameter.py +89 -28
- siliconcompiler/schema/parametertype.py +2 -0
- siliconcompiler/schema/parametervalue.py +258 -15
- siliconcompiler/schema/safeschema.py +25 -2
- siliconcompiler/schema/schema_cfg.py +23 -19
- siliconcompiler/schema/utils.py +2 -2
- siliconcompiler/schema_obj.py +24 -5
- siliconcompiler/tool.py +1131 -265
- siliconcompiler/tools/bambu/__init__.py +41 -0
- siliconcompiler/tools/builtin/concatenate.py +2 -2
- siliconcompiler/tools/builtin/minimum.py +2 -1
- siliconcompiler/tools/builtin/mux.py +2 -1
- siliconcompiler/tools/builtin/nop.py +2 -1
- siliconcompiler/tools/builtin/verify.py +2 -1
- siliconcompiler/tools/klayout/__init__.py +95 -0
- siliconcompiler/tools/openroad/__init__.py +289 -0
- siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +3 -0
- siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +7 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +8 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +9 -5
- siliconcompiler/tools/openroad/scripts/common/write_images.tcl +5 -1
- siliconcompiler/tools/slang/__init__.py +1 -1
- siliconcompiler/tools/slang/elaborate.py +2 -1
- siliconcompiler/tools/vivado/scripts/sc_run.tcl +1 -1
- siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +8 -1
- siliconcompiler/tools/vivado/syn_fpga.py +6 -0
- siliconcompiler/tools/vivado/vivado.py +35 -2
- siliconcompiler/tools/vpr/__init__.py +150 -0
- siliconcompiler/tools/yosys/__init__.py +369 -1
- siliconcompiler/tools/yosys/scripts/procs.tcl +0 -1
- siliconcompiler/toolscripts/_tools.json +5 -10
- siliconcompiler/utils/__init__.py +66 -0
- siliconcompiler/utils/flowgraph.py +2 -2
- siliconcompiler/utils/issue.py +2 -1
- siliconcompiler/utils/logging.py +14 -0
- siliconcompiler/utils/multiprocessing.py +256 -0
- siliconcompiler/utils/showtools.py +10 -0
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/METADATA +6 -6
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/RECORD +122 -115
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/entry_points.txt +3 -0
- siliconcompiler/schema/cmdlineschema.py +0 -250
- siliconcompiler/schema/packageschema.py +0 -101
- siliconcompiler/toolscripts/rhel8/install-slang.sh +0 -40
- siliconcompiler/toolscripts/rhel9/install-slang.sh +0 -40
- siliconcompiler/toolscripts/ubuntu20/install-slang.sh +0 -47
- siliconcompiler/toolscripts/ubuntu22/install-slang.sh +0 -37
- siliconcompiler/toolscripts/ubuntu24/install-slang.sh +0 -37
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A collection of functions that create and manage UI components for the
|
|
3
|
+
SiliconCompiler web dashboard using Streamlit.
|
|
4
|
+
|
|
5
|
+
This module contains functions for rendering various parts of the dashboard,
|
|
6
|
+
such as headers, file viewers, metric tables, and the interactive flowgraph.
|
|
7
|
+
These components are composed by the layout functions to build the complete UI.
|
|
8
|
+
"""
|
|
1
9
|
import base64
|
|
2
10
|
import json
|
|
3
11
|
import os
|
|
@@ -19,6 +27,7 @@ from siliconcompiler.report.dashboard.web.utils import file_utils
|
|
|
19
27
|
from siliconcompiler.report.dashboard.web.components import flowgraph
|
|
20
28
|
|
|
21
29
|
|
|
30
|
+
# --- Constants for Page Configuration ---
|
|
22
31
|
SC_ABOUT = [
|
|
23
32
|
f"SiliconCompiler {sc_version}",
|
|
24
33
|
'''A compiler framework that automates translation from source code to
|
|
@@ -38,14 +47,13 @@ SC_FONT_PATH = os.path.join(SC_DATA_ROOT, 'RobotoMono', 'RobotoMono-Regular.ttf'
|
|
|
38
47
|
|
|
39
48
|
def page_header(title_col_width=0.7):
|
|
40
49
|
"""
|
|
41
|
-
Displays the
|
|
42
|
-
|
|
50
|
+
Displays the main page header, including the design title, a job selector,
|
|
51
|
+
and a settings popover for developers.
|
|
43
52
|
|
|
44
53
|
Args:
|
|
45
|
-
title_col_width (float)
|
|
46
|
-
|
|
54
|
+
title_col_width (float): The percentage of the page width to allocate
|
|
55
|
+
to the title and logo. The rest is divided among other components.
|
|
47
56
|
"""
|
|
48
|
-
|
|
49
57
|
if state.DEVELOPER:
|
|
50
58
|
col_width = (1 - title_col_width) / 2
|
|
51
59
|
title_col, job_select_col, settings_col = \
|
|
@@ -62,6 +70,7 @@ def page_header(title_col_width=0.7):
|
|
|
62
70
|
if state.DEVELOPER:
|
|
63
71
|
with settings_col:
|
|
64
72
|
with streamlit.popover("Settings", use_container_width=True):
|
|
73
|
+
# Layout selection
|
|
65
74
|
all_layouts = layouts.get_all_layouts()
|
|
66
75
|
layout_index = all_layouts.index(state.get_key(state.APP_LAYOUT))
|
|
67
76
|
if state.set_key(
|
|
@@ -69,98 +78,68 @@ def page_header(title_col_width=0.7):
|
|
|
69
78
|
streamlit.selectbox("Layout", all_layouts, index=layout_index)):
|
|
70
79
|
state.set_key(state.APP_RERUN, "Layout")
|
|
71
80
|
|
|
81
|
+
# Debugging and refresh rate settings
|
|
72
82
|
state._DEBUG = streamlit.checkbox("Debug", state._DEBUG)
|
|
73
|
-
|
|
74
83
|
state.set_key(
|
|
75
84
|
state.APP_RUNNING_REFRESH,
|
|
76
85
|
streamlit.slider(
|
|
77
86
|
"Running refresh rate (ms)",
|
|
78
|
-
min_value=1000,
|
|
79
|
-
max_value=10000,
|
|
80
|
-
step=500,
|
|
87
|
+
min_value=1000, max_value=10000, step=500,
|
|
81
88
|
value=state.get_key(state.APP_RUNNING_REFRESH)))
|
|
82
|
-
|
|
83
89
|
state.set_key(
|
|
84
90
|
state.APP_STOPPED_REFRESH,
|
|
85
91
|
streamlit.slider(
|
|
86
92
|
"Stopped refresh rate (ms)",
|
|
87
|
-
min_value=1000,
|
|
88
|
-
max_value=100000,
|
|
89
|
-
step=1000,
|
|
93
|
+
min_value=1000, max_value=100000, step=1000,
|
|
90
94
|
value=state.get_key(state.APP_STOPPED_REFRESH)))
|
|
91
95
|
|
|
96
|
+
# Content display settings
|
|
92
97
|
state.set_key(
|
|
93
98
|
state.MAX_DICT_ITEMS_TO_SHOW,
|
|
94
99
|
streamlit.number_input(
|
|
95
100
|
"Maximum dict item to show",
|
|
96
|
-
min_value=1,
|
|
97
|
-
max_value=10000,
|
|
101
|
+
min_value=1, max_value=10000,
|
|
98
102
|
value=state.get_key(state.MAX_DICT_ITEMS_TO_SHOW)))
|
|
99
|
-
|
|
100
103
|
state.set_key(
|
|
101
104
|
state.MAX_FILE_LINES_TO_SHOW,
|
|
102
105
|
streamlit.number_input(
|
|
103
106
|
"Maximum file lines to show",
|
|
104
|
-
min_value=100,
|
|
105
|
-
max_value=1000,
|
|
106
|
-
step=100,
|
|
107
|
+
min_value=100, max_value=1000, step=100,
|
|
107
108
|
value=state.get_key(state.MAX_FILE_LINES_TO_SHOW)))
|
|
108
109
|
|
|
109
110
|
|
|
110
111
|
def design_title(design=""):
|
|
112
|
+
"""
|
|
113
|
+
Renders the SiliconCompiler logo and design name using custom HTML and CSS.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
design (str): The name of the design to display.
|
|
117
|
+
"""
|
|
118
|
+
# Inject custom font and CSS for styling the title
|
|
111
119
|
font = base64.b64encode(open(SC_FONT_PATH, "rb").read()).decode()
|
|
112
|
-
streamlit.html(
|
|
113
|
-
f'''
|
|
120
|
+
streamlit.html(f'''
|
|
114
121
|
<head>
|
|
115
122
|
<style>
|
|
116
|
-
/* Define the @font-face rule */
|
|
117
123
|
@font-face {{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
font-weight: normal;
|
|
121
|
-
font-style: normal;
|
|
124
|
+
font-family: 'Roboto Mono';
|
|
125
|
+
src: url(data:font/truetype;charset=utf-8;base64,{font}) format('truetype');
|
|
122
126
|
}}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
.logo-
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
.logo-container {{ display: flex; align-items: flex-start; }}
|
|
128
|
+
.logo-image {{ margin-right: 10px; margin-top: -10px; }}
|
|
129
|
+
.logo-text {{ display: flex; flex-direction: column; margin-top: -20px; }}
|
|
130
|
+
.design-text, .dashboard-text {{
|
|
131
|
+
font-family: 'Roboto Mono', sans-serif;
|
|
132
|
+
font-weight: 700 !important;
|
|
133
|
+
font-size: 30px !important;
|
|
128
134
|
}}
|
|
129
|
-
|
|
130
|
-
.
|
|
131
|
-
margin-right: 10px;
|
|
132
|
-
margin-top: -10px;
|
|
133
|
-
}}
|
|
134
|
-
|
|
135
|
-
.logo-text {{
|
|
136
|
-
display: flex;
|
|
137
|
-
flex-direction: column;
|
|
138
|
-
margin-top: -20px;
|
|
139
|
-
}}
|
|
140
|
-
|
|
141
|
-
.design-text {{
|
|
142
|
-
color: #F1C437; /* Yellow color */
|
|
143
|
-
font-family: 'Roboto Mono', sans-serif;
|
|
144
|
-
font-weight: 700 !important;
|
|
145
|
-
font-size: 30px !important;
|
|
146
|
-
margin-bottom: -16px;
|
|
147
|
-
}}
|
|
148
|
-
|
|
149
|
-
.dashboard-text {{
|
|
150
|
-
color: #1D4482; /* Blue color */
|
|
151
|
-
font-family: 'Roboto Mono', sans-serif;
|
|
152
|
-
font-weight: 700 !important;
|
|
153
|
-
font-size: 30px !important;
|
|
154
|
-
}}
|
|
155
|
-
|
|
135
|
+
.design-text {{ color: #F1C437; margin-bottom: -16px; }}
|
|
136
|
+
.dashboard-text {{ color: #1D4482; }}
|
|
156
137
|
</style>
|
|
157
|
-
</head>
|
|
158
|
-
'''
|
|
159
|
-
)
|
|
138
|
+
</head>''')
|
|
160
139
|
|
|
140
|
+
# Render the logo and title HTML
|
|
161
141
|
logo = base64.b64encode(open(SC_LOGO_PATH, "rb").read()).decode()
|
|
162
|
-
streamlit.html(
|
|
163
|
-
f'''
|
|
142
|
+
streamlit.html(f'''
|
|
164
143
|
<body>
|
|
165
144
|
<div class="logo-container">
|
|
166
145
|
<img src="data:image/png;base64,{logo}" alt="SiliconCompiler logo"
|
|
@@ -170,23 +149,33 @@ def design_title(design=""):
|
|
|
170
149
|
<p class="dashboard-text">dashboard</p>
|
|
171
150
|
</div>
|
|
172
151
|
</div>
|
|
173
|
-
</body>
|
|
174
|
-
'''
|
|
175
|
-
)
|
|
152
|
+
</body>''')
|
|
176
153
|
|
|
177
154
|
|
|
178
155
|
def job_selector():
|
|
156
|
+
"""
|
|
157
|
+
Displays a selectbox for choosing a job/run to inspect.
|
|
158
|
+
|
|
159
|
+
The list of jobs includes the current run ('default') and any historical
|
|
160
|
+
runs found in the manifest.
|
|
161
|
+
"""
|
|
179
162
|
job = streamlit.selectbox(
|
|
180
163
|
'pick a job',
|
|
181
164
|
state.get_chips(),
|
|
182
165
|
label_visibility='collapsed')
|
|
183
166
|
|
|
184
167
|
if state.set_key(state.SELECTED_JOB, job):
|
|
185
|
-
#
|
|
168
|
+
# If the job changes, trigger a rerun to update the entire dashboard.
|
|
186
169
|
state.set_key(state.APP_RERUN, "Job")
|
|
187
170
|
|
|
188
171
|
|
|
189
172
|
def setup_page():
|
|
173
|
+
"""
|
|
174
|
+
Configures the global Streamlit page settings.
|
|
175
|
+
|
|
176
|
+
This should be one of the first Streamlit commands called. It sets the page
|
|
177
|
+
title, icon, layout, and custom menu items.
|
|
178
|
+
"""
|
|
190
179
|
streamlit.set_page_config(
|
|
191
180
|
page_title=f'{state.get_chip().design} dashboard',
|
|
192
181
|
page_icon=Image.open(SC_LOGO_PATH),
|
|
@@ -195,6 +184,19 @@ def setup_page():
|
|
|
195
184
|
|
|
196
185
|
|
|
197
186
|
def file_viewer(chip, path, page_key=None, header_col_width=0.89):
|
|
187
|
+
"""
|
|
188
|
+
Renders a viewer for a specified file.
|
|
189
|
+
|
|
190
|
+
Supports images, JSON, and plain text files with syntax highlighting
|
|
191
|
+
and pagination.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
chip (Chip): The chip object, used for context (e.g., workdir).
|
|
195
|
+
path (str): The absolute path to the file to display.
|
|
196
|
+
page_key (str, optional): The state key to use for storing the current
|
|
197
|
+
page number for paginated text files.
|
|
198
|
+
header_col_width (float): The percentage of width for the file header.
|
|
199
|
+
"""
|
|
198
200
|
if not path:
|
|
199
201
|
streamlit.error('Select a file')
|
|
200
202
|
return
|
|
@@ -203,90 +205,74 @@ def file_viewer(chip, path, page_key=None, header_col_width=0.89):
|
|
|
203
205
|
streamlit.error(f'{path} is not a file')
|
|
204
206
|
return
|
|
205
207
|
|
|
206
|
-
#
|
|
208
|
+
# --- File Header and Download Button ---
|
|
207
209
|
relative_path = os.path.relpath(path, chip.getworkdir())
|
|
208
210
|
filename = os.path.basename(path)
|
|
209
211
|
file_extension = utils.get_file_ext(path)
|
|
210
212
|
|
|
211
|
-
# Build streamlit module
|
|
212
213
|
header_col, download_col = \
|
|
213
214
|
streamlit.columns([header_col_width, 1 - header_col_width], gap='small')
|
|
214
|
-
|
|
215
215
|
with header_col:
|
|
216
216
|
streamlit.header(relative_path)
|
|
217
|
-
|
|
218
217
|
with download_col:
|
|
219
|
-
streamlit.markdown(' ') #
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
218
|
+
streamlit.markdown(' ') # Aligns download button with title
|
|
219
|
+
with open(path, "rb") as fp:
|
|
220
|
+
streamlit.download_button(
|
|
221
|
+
label="Download",
|
|
222
|
+
data=fp,
|
|
223
|
+
file_name=filename,
|
|
224
|
+
use_container_width=True)
|
|
225
|
+
|
|
226
|
+
# --- File Content Viewer ---
|
|
226
227
|
try:
|
|
227
228
|
if file_extension in ('jpg', 'jpeg', 'png'):
|
|
228
|
-
# Data is an image
|
|
229
229
|
streamlit.image(path)
|
|
230
230
|
elif file_extension == 'json':
|
|
231
|
-
# Data is a json file
|
|
232
231
|
data = json.loads(file_utils.read_file(path, None))
|
|
232
|
+
# Expand JSON if it's not too large
|
|
233
233
|
expand_keys = report.get_total_manifest_key_count(data) < \
|
|
234
234
|
state.get_key(state.MAX_DICT_ITEMS_TO_SHOW)
|
|
235
235
|
if not expand_keys:
|
|
236
|
-
#
|
|
237
|
-
expand_keys = 2
|
|
236
|
+
expand_keys = 2 # Default to expanding two levels
|
|
238
237
|
streamlit.json(data, expanded=expand_keys)
|
|
239
238
|
else:
|
|
239
|
+
# For text files, implement pagination
|
|
240
240
|
file_data = file_utils.read_file(path, None).splitlines()
|
|
241
|
-
max_pages = len(file_data)
|
|
242
241
|
page_size = state.get_key(state.MAX_FILE_LINES_TO_SHOW)
|
|
242
|
+
max_pages = (len(file_data) + page_size - 1) // page_size
|
|
243
243
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if page_key
|
|
247
|
-
if state.get_key(page_key) is None:
|
|
248
|
-
state.set_key(page_key, 1)
|
|
249
|
-
index = state.get_key(page_key)
|
|
250
|
-
else:
|
|
251
|
-
index = 1
|
|
244
|
+
if page_key and state.get_key(page_key) is None:
|
|
245
|
+
state.set_key(page_key, 1)
|
|
246
|
+
index = state.get_key(page_key) if page_key else 1
|
|
252
247
|
|
|
253
248
|
page = sac.pagination(
|
|
254
|
-
align='center',
|
|
255
|
-
|
|
256
|
-
jump=True,
|
|
257
|
-
show_total=True,
|
|
258
|
-
page_size=page_size,
|
|
259
|
-
total=max_pages,
|
|
260
|
-
disabled=max_pages < state.get_key(state.MAX_FILE_LINES_TO_SHOW))
|
|
249
|
+
align='center', index=index, total=max_pages,
|
|
250
|
+
disabled=max_pages <= 1)
|
|
261
251
|
|
|
262
252
|
if page_key:
|
|
263
253
|
state.set_key(page_key, page)
|
|
264
254
|
|
|
265
|
-
start_idx = (page - 1) *
|
|
266
|
-
end_idx = start_idx +
|
|
255
|
+
start_idx = (page - 1) * page_size
|
|
256
|
+
end_idx = start_idx + page_size
|
|
267
257
|
file_show = file_data[start_idx:end_idx]
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
line_numbers=True)
|
|
258
|
+
|
|
259
|
+
streamlit.code(
|
|
260
|
+
"\n".join(file_show),
|
|
261
|
+
language=file_utils.get_file_type(file_extension),
|
|
262
|
+
line_numbers=True)
|
|
274
263
|
except Exception as e:
|
|
275
|
-
streamlit.
|
|
264
|
+
streamlit.error(f'Error occurred reading file: {e}')
|
|
276
265
|
|
|
277
266
|
|
|
278
|
-
def manifest_viewer(
|
|
279
|
-
chip,
|
|
280
|
-
header_col_width=0.70):
|
|
267
|
+
def manifest_viewer(chip, header_col_width=0.70):
|
|
281
268
|
"""
|
|
282
|
-
Displays the manifest
|
|
269
|
+
Displays the chip's manifest with search and filtering options.
|
|
283
270
|
|
|
284
271
|
Args:
|
|
285
|
-
chip (Chip)
|
|
286
|
-
header_col_width (float)
|
|
287
|
-
percentage of the width of the screen given to the header. The rest
|
|
288
|
-
is given to the settings and download buttons.
|
|
272
|
+
chip (Chip): The chip object whose manifest will be displayed.
|
|
273
|
+
header_col_width (float): The percentage of width for the header.
|
|
289
274
|
"""
|
|
275
|
+
# --- Header and Settings ---
|
|
290
276
|
end_column_widths = (1 - header_col_width) / 2
|
|
291
277
|
header_col, settings_col, download_col = \
|
|
292
278
|
streamlit.columns(
|
|
@@ -294,102 +280,92 @@ def manifest_viewer(
|
|
|
294
280
|
gap='small')
|
|
295
281
|
with header_col:
|
|
296
282
|
streamlit.header('Manifest')
|
|
297
|
-
|
|
298
283
|
with settings_col:
|
|
299
|
-
streamlit.markdown(' ') #
|
|
284
|
+
streamlit.markdown(' ') # Aligns with title
|
|
300
285
|
with streamlit.popover("Settings", use_container_width=True):
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
help='Click here to see the manifest before it was made more readable'):
|
|
286
|
+
# Filtering options
|
|
287
|
+
if streamlit.checkbox('Raw manifest', help='View raw, unprocessed manifest'):
|
|
304
288
|
manifest_to_show = chip.getdict()
|
|
305
289
|
else:
|
|
306
290
|
manifest_to_show = report.make_manifest(chip)
|
|
307
291
|
|
|
308
|
-
if streamlit.checkbox(
|
|
309
|
-
|
|
310
|
-
help='Hide empty keypaths',
|
|
311
|
-
value=True):
|
|
312
|
-
manifest_to_show = report.search_manifest(
|
|
313
|
-
manifest_to_show,
|
|
314
|
-
value_search='*')
|
|
315
|
-
|
|
316
|
-
search_key = streamlit.text_input('Search Keys', '', placeholder="Keys")
|
|
317
|
-
search_value = streamlit.text_input('Search Values', '', placeholder="Values")
|
|
292
|
+
if streamlit.checkbox('Hide empty values', value=True):
|
|
293
|
+
manifest_to_show = report.search_manifest(manifest_to_show, value_search='*')
|
|
318
294
|
|
|
295
|
+
# Search functionality
|
|
296
|
+
search_key = streamlit.text_input('Search Keys', placeholder="Keys")
|
|
297
|
+
search_value = streamlit.text_input('Search Values', placeholder="Values")
|
|
319
298
|
manifest_to_show = report.search_manifest(
|
|
320
|
-
manifest_to_show,
|
|
321
|
-
key_search=search_key,
|
|
322
|
-
value_search=search_value)
|
|
323
|
-
|
|
299
|
+
manifest_to_show, key_search=search_key, value_search=search_value)
|
|
324
300
|
with download_col:
|
|
325
|
-
streamlit.markdown(' ') #
|
|
301
|
+
streamlit.markdown(' ') # Aligns with title
|
|
326
302
|
streamlit.download_button(
|
|
327
|
-
label='Download',
|
|
328
|
-
file_name='manifest.json',
|
|
303
|
+
label='Download', file_name='manifest.json',
|
|
329
304
|
data=json.dumps(chip.getdict(), indent=2),
|
|
330
|
-
mime="application/json",
|
|
331
|
-
use_container_width=True)
|
|
305
|
+
mime="application/json", use_container_width=True)
|
|
332
306
|
|
|
307
|
+
# --- Manifest Display ---
|
|
333
308
|
expand_keys = report.get_total_manifest_key_count(manifest_to_show) < \
|
|
334
309
|
state.get_key(state.MAX_DICT_ITEMS_TO_SHOW)
|
|
335
310
|
if not expand_keys:
|
|
336
|
-
#
|
|
337
|
-
expand_keys = 2
|
|
311
|
+
expand_keys = 2 # Default to expanding two levels
|
|
338
312
|
streamlit.json(manifest_to_show, expanded=expand_keys)
|
|
339
313
|
|
|
340
314
|
|
|
341
315
|
def metrics_viewer(metric_dataframe, metric_to_metric_unit_map, header_col_width=0.7, height=None):
|
|
342
316
|
"""
|
|
343
|
-
Displays
|
|
344
|
-
which nodes and metrics to view in the dataframe.
|
|
317
|
+
Displays a filterable and transposable table of metrics.
|
|
345
318
|
|
|
346
319
|
Args:
|
|
347
|
-
metric_dataframe (
|
|
348
|
-
metric_to_metric_unit_map (dict)
|
|
320
|
+
metric_dataframe (pandas.DataFrame): The dataframe containing all metrics.
|
|
321
|
+
metric_to_metric_unit_map (dict): A mapping from formatted metric names
|
|
322
|
+
(with units) to raw metric names.
|
|
323
|
+
header_col_width (float): The percentage of width for the header.
|
|
324
|
+
height (int, optional): The height of the dataframe in pixels.
|
|
349
325
|
"""
|
|
350
|
-
|
|
351
326
|
all_nodes = metric_dataframe.columns.tolist()
|
|
352
327
|
all_metrics = list(metric_to_metric_unit_map.values())
|
|
353
328
|
|
|
329
|
+
# --- Header and Settings ---
|
|
354
330
|
header_col, settings_col = streamlit.columns(
|
|
355
|
-
[header_col_width, 1 - header_col_width],
|
|
356
|
-
gap="large")
|
|
331
|
+
[header_col_width, 1 - header_col_width], gap="large")
|
|
357
332
|
with header_col:
|
|
358
333
|
streamlit.header('Metrics')
|
|
359
334
|
with settings_col:
|
|
360
|
-
# Align to header
|
|
361
|
-
streamlit.markdown('')
|
|
362
|
-
|
|
335
|
+
streamlit.markdown('') # Align to header
|
|
363
336
|
with streamlit.popover("Settings", use_container_width=True):
|
|
364
|
-
transpose = streamlit.checkbox(
|
|
365
|
-
'Transpose',
|
|
366
|
-
help='Transpose the metrics table')
|
|
367
|
-
|
|
337
|
+
transpose = streamlit.checkbox('Transpose', help='Transpose the metrics table')
|
|
368
338
|
display_nodes = streamlit.multiselect('Pick nodes to include', all_nodes, [])
|
|
369
339
|
display_metrics = streamlit.multiselect('Pick metrics to include?', all_metrics, [])
|
|
370
340
|
|
|
371
|
-
# Filter
|
|
341
|
+
# --- Filter and Display Dataframe ---
|
|
372
342
|
if not display_nodes:
|
|
373
343
|
display_nodes = all_nodes
|
|
374
|
-
|
|
375
344
|
if not display_metrics:
|
|
376
345
|
display_metrics = all_metrics
|
|
377
346
|
|
|
378
347
|
dataframe_nodes = list(display_nodes)
|
|
379
|
-
dataframe_metrics = [
|
|
380
|
-
|
|
381
|
-
if metric_to_metric_unit_map
|
|
382
|
-
|
|
348
|
+
dataframe_metrics = [
|
|
349
|
+
metric for metric in metric_dataframe.index.tolist()
|
|
350
|
+
if metric_to_metric_unit_map.get(metric) in display_metrics
|
|
351
|
+
]
|
|
383
352
|
|
|
384
|
-
|
|
353
|
+
filtered_df = metric_dataframe.loc[dataframe_metrics, dataframe_nodes]
|
|
385
354
|
if transpose:
|
|
386
|
-
|
|
355
|
+
filtered_df = filtered_df.transpose()
|
|
387
356
|
|
|
388
|
-
|
|
389
|
-
streamlit.dataframe(metric_dataframe, use_container_width=True, height=height)
|
|
357
|
+
streamlit.dataframe(filtered_df, use_container_width=True, height=height)
|
|
390
358
|
|
|
391
359
|
|
|
392
360
|
def node_image_viewer(chip, step, index):
|
|
361
|
+
"""
|
|
362
|
+
Displays a gallery of all image files associated with a given node.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
chip (Chip): The chip object.
|
|
366
|
+
step (str): The step of the node.
|
|
367
|
+
index (str): The index of the node.
|
|
368
|
+
"""
|
|
393
369
|
exts = ('png', 'jpg', 'jpeg')
|
|
394
370
|
images = []
|
|
395
371
|
for path, _, files in report.get_files(chip, step, index):
|
|
@@ -397,46 +373,53 @@ def node_image_viewer(chip, step, index):
|
|
|
397
373
|
|
|
398
374
|
if not images:
|
|
399
375
|
streamlit.markdown("No images to show")
|
|
376
|
+
return
|
|
400
377
|
|
|
401
378
|
work_dir = chip.getworkdir(step=step, index=index)
|
|
402
|
-
|
|
403
379
|
columns = streamlit.slider(
|
|
404
380
|
"Image columns",
|
|
405
|
-
min_value=1,
|
|
406
|
-
|
|
407
|
-
value=min(len(images), 4),
|
|
408
|
-
disabled=len(images) < 2)
|
|
409
|
-
|
|
410
|
-
column = 0
|
|
411
|
-
for image in sorted(images):
|
|
412
|
-
if column == 0:
|
|
413
|
-
cols = streamlit.columns(columns)
|
|
381
|
+
min_value=1, max_value=min(len(images), 10),
|
|
382
|
+
value=min(len(images), 4), disabled=len(images) < 2)
|
|
414
383
|
|
|
415
|
-
|
|
384
|
+
# Display images in a grid
|
|
385
|
+
for i, image in enumerate(sorted(images)):
|
|
386
|
+
if i % columns == 0:
|
|
387
|
+
cols = streamlit.columns(columns)
|
|
388
|
+
with cols[i % columns]:
|
|
416
389
|
streamlit.image(
|
|
417
390
|
image,
|
|
418
391
|
caption=os.path.relpath(image, work_dir),
|
|
419
392
|
use_column_width=True)
|
|
420
393
|
|
|
421
|
-
column += 1
|
|
422
|
-
if column == columns:
|
|
423
|
-
column = 0
|
|
424
|
-
|
|
425
394
|
|
|
426
395
|
def node_file_tree_viewer(chip, step, index):
|
|
396
|
+
"""
|
|
397
|
+
Displays an interactive file tree for a given node.
|
|
398
|
+
|
|
399
|
+
Files are decorated with tags indicating which metrics they are a source for.
|
|
400
|
+
Selecting a file in the tree updates the application state to display it
|
|
401
|
+
in the file viewer.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
chip (Chip): The chip object.
|
|
405
|
+
step (str): The step of the node.
|
|
406
|
+
index (str): The index of the node.
|
|
407
|
+
"""
|
|
427
408
|
logs_and_reports = file_utils.convert_filepaths_to_select_tree(
|
|
428
409
|
report.get_files(chip, step, index))
|
|
429
410
|
|
|
430
411
|
if not logs_and_reports:
|
|
431
412
|
streamlit.markdown("No files to show")
|
|
413
|
+
return
|
|
432
414
|
|
|
415
|
+
# --- Prepare data for the tree component ---
|
|
433
416
|
lookup = {}
|
|
434
417
|
tree_items = []
|
|
435
|
-
|
|
436
418
|
metrics_source, file_metrics = report.get_metrics_source(chip, step, index)
|
|
437
419
|
work_dir = chip.getworkdir(step=step, index=index)
|
|
438
420
|
|
|
439
421
|
def make_item(file):
|
|
422
|
+
"""Recursively builds a tree item for the antd component."""
|
|
440
423
|
lookup[file['value']] = file['label']
|
|
441
424
|
item = sac.TreeItem(
|
|
442
425
|
file['value'],
|
|
@@ -444,56 +427,55 @@ def node_file_tree_viewer(chip, step, index):
|
|
|
444
427
|
tag=[],
|
|
445
428
|
children=[])
|
|
446
429
|
|
|
430
|
+
# Add metric source tags
|
|
447
431
|
check_file = os.path.relpath(file['value'], work_dir)
|
|
448
432
|
if check_file in file_metrics:
|
|
449
433
|
metrics = set(file_metrics[check_file])
|
|
450
|
-
primary_source = set()
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
for metric in metric_set:
|
|
460
|
-
if len(item.tag) < 5:
|
|
461
|
-
item.tag.append(sac.Tag(metric, color=color))
|
|
462
|
-
else:
|
|
463
|
-
item.tag.append(sac.Tag('metrics...', color='geekblue'))
|
|
464
|
-
break
|
|
434
|
+
primary_source = set(metrics_source.get(check_file, []))
|
|
435
|
+
other_metrics = metrics - primary_source
|
|
436
|
+
|
|
437
|
+
tags = [sac.Tag(m, color='blue') for m in primary_source]
|
|
438
|
+
tags += [sac.Tag(m, color='green') for m in other_metrics]
|
|
439
|
+
|
|
440
|
+
item.tag = tags[:5]
|
|
441
|
+
if len(tags) > 5:
|
|
442
|
+
item.tag.append(sac.Tag('...', color='geekblue'))
|
|
465
443
|
item.tooltip = "metrics: " + ", ".join(file_metrics[check_file])
|
|
466
444
|
|
|
445
|
+
# Recursively add children for folders
|
|
467
446
|
if 'children' in file:
|
|
468
447
|
item.icon = 'folder'
|
|
469
|
-
for child in file['children']
|
|
470
|
-
item.children.append(make_item(child))
|
|
448
|
+
item.children = [make_item(child) for child in file['children']]
|
|
471
449
|
|
|
472
450
|
return item
|
|
473
451
|
|
|
474
|
-
for file in logs_and_reports
|
|
475
|
-
tree_items.append(make_item(file))
|
|
476
|
-
|
|
477
|
-
def format_label(value):
|
|
478
|
-
return lookup[value]
|
|
452
|
+
tree_items = [make_item(file) for file in logs_and_reports]
|
|
479
453
|
|
|
454
|
+
# --- Render the tree ---
|
|
480
455
|
selected = sac.tree(
|
|
481
456
|
items=tree_items,
|
|
482
|
-
format_func=
|
|
483
|
-
size='md',
|
|
484
|
-
icon='table',
|
|
485
|
-
open_all=True)
|
|
457
|
+
format_func=lambda v: lookup.get(v, v),
|
|
458
|
+
size='md', icon='table', open_all=True)
|
|
486
459
|
|
|
487
460
|
if selected and os.path.isfile(selected):
|
|
488
461
|
state.set_key(state.SELECTED_FILE, selected)
|
|
489
|
-
state.set_key(state.SELECTED_FILE_PAGE,
|
|
462
|
+
state.set_key(state.SELECTED_FILE_PAGE, 1)
|
|
490
463
|
|
|
491
464
|
|
|
492
465
|
def node_viewer(chip, step, index, metric_dataframe, height=None):
|
|
466
|
+
"""
|
|
467
|
+
Displays a summary view for a single node, including metrics, records, and files.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
chip (Chip): The chip object.
|
|
471
|
+
step (str): The step of the node.
|
|
472
|
+
index (str): The index of the node.
|
|
473
|
+
metric_dataframe (pandas.DataFrame): The dataframe of all metrics.
|
|
474
|
+
height (int, optional): The height for the dataframe components.
|
|
475
|
+
"""
|
|
493
476
|
from pandas import DataFrame
|
|
494
477
|
|
|
495
478
|
metrics_col, records_col, logs_and_reports_col = streamlit.columns(3, gap='small')
|
|
496
|
-
|
|
497
479
|
node_name = f'{step}/{index}'
|
|
498
480
|
|
|
499
481
|
with metrics_col:
|
|
@@ -501,58 +483,55 @@ def node_viewer(chip, step, index, metric_dataframe, height=None):
|
|
|
501
483
|
if node_name in metric_dataframe:
|
|
502
484
|
streamlit.dataframe(
|
|
503
485
|
metric_dataframe[node_name].dropna(),
|
|
504
|
-
use_container_width=True,
|
|
505
|
-
height=height)
|
|
486
|
+
use_container_width=True, height=height)
|
|
506
487
|
with records_col:
|
|
507
|
-
streamlit.subheader(f'{
|
|
508
|
-
nodes = {}
|
|
509
|
-
nodes[step + index] = report.get_flowgraph_nodes(chip, step, index)
|
|
488
|
+
streamlit.subheader(f'{node_name} details')
|
|
489
|
+
nodes = {step + index: report.get_flowgraph_nodes(chip, step, index)}
|
|
510
490
|
streamlit.dataframe(
|
|
511
491
|
DataFrame.from_dict(nodes),
|
|
512
|
-
use_container_width=True,
|
|
513
|
-
height=height)
|
|
492
|
+
use_container_width=True, height=height)
|
|
514
493
|
with logs_and_reports_col:
|
|
515
|
-
streamlit.subheader(f'{
|
|
494
|
+
streamlit.subheader(f'{node_name} files')
|
|
516
495
|
node_file_tree_viewer(chip, step, index)
|
|
517
496
|
|
|
518
497
|
|
|
519
498
|
def flowgraph_viewer(chip):
|
|
520
|
-
|
|
521
|
-
|
|
499
|
+
"""
|
|
500
|
+
Displays the interactive flowgraph for the current job.
|
|
522
501
|
|
|
523
|
-
|
|
524
|
-
chip (Chip) : The chip object that contains the schema read from.
|
|
525
|
-
'''
|
|
502
|
+
Selecting a node in the graph updates the application state.
|
|
526
503
|
|
|
504
|
+
Args:
|
|
505
|
+
chip (Chip): The chip object containing the flowgraph to display.
|
|
506
|
+
"""
|
|
527
507
|
nodes, edges = flowgraph.get_nodes_and_edges(chip)
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
508
|
+
selected_node = agraph(
|
|
509
|
+
nodes=nodes,
|
|
510
|
+
edges=edges,
|
|
511
|
+
config=flowgraph.get_graph_config())
|
|
512
|
+
|
|
513
|
+
if state.set_key(state.SELECTED_FLOWGRAPH_NODE, selected_node):
|
|
532
514
|
if state.get_key(state.SELECTED_FLOWGRAPH_NODE):
|
|
533
515
|
state.set_key(state.NODE_SOURCE, "flowgraph")
|
|
534
516
|
|
|
535
517
|
|
|
536
518
|
def node_selector(nodes):
|
|
537
519
|
"""
|
|
538
|
-
Displays
|
|
539
|
-
|
|
540
|
-
|
|
520
|
+
Displays a dropdown for selecting a node.
|
|
521
|
+
|
|
522
|
+
This provides an alternative to selecting a node by clicking on the flowgraph.
|
|
541
523
|
|
|
542
524
|
Args:
|
|
543
|
-
nodes (list)
|
|
525
|
+
nodes (list): A list of node name strings (e.g., ['import/0', 'syn/0']).
|
|
544
526
|
"""
|
|
545
527
|
prev_node = state.get_selected_node()
|
|
546
528
|
|
|
547
529
|
with streamlit.popover("Select Node", use_container_width=True):
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
'Pick a node to inspect',
|
|
556
|
-
nodes,
|
|
557
|
-
index=idx)):
|
|
530
|
+
idx = nodes.index(prev_node) if prev_node in nodes else 0
|
|
531
|
+
selected_node = streamlit.selectbox(
|
|
532
|
+
'Pick a node to inspect',
|
|
533
|
+
nodes,
|
|
534
|
+
index=idx)
|
|
535
|
+
|
|
536
|
+
if state.set_key(state.SELECTED_SELECTOR_NODE, selected_node):
|
|
558
537
|
state.set_key(state.NODE_SOURCE, "selector")
|