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,9 +1,9 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import threading
|
|
3
1
|
import logging
|
|
2
|
+
import os
|
|
3
|
+
import math
|
|
4
4
|
import queue
|
|
5
5
|
import time
|
|
6
|
-
import
|
|
6
|
+
import threading
|
|
7
7
|
|
|
8
8
|
from collections import deque
|
|
9
9
|
from dataclasses import dataclass, field
|
|
@@ -22,27 +22,116 @@ from siliconcompiler import SiliconCompilerError, NodeStatus
|
|
|
22
22
|
from siliconcompiler.utils.logging import SCColorLoggerFormatter
|
|
23
23
|
from siliconcompiler.flowgraph import RuntimeFlowgraph
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
class LogBuffer:
|
|
27
|
+
"""
|
|
28
|
+
A buffer for storing log messages, designed to be thread-safe and to work
|
|
29
|
+
in conjunction with a `LogBufferHandler` for dashboard or UI display.
|
|
30
|
+
It uses a `collections.deque` to maintain a fixed-size history of log lines.
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, queue: queue.Queue, n: int = 15, event: threading.Event = None):
|
|
33
|
+
"""
|
|
34
|
+
Initializes the LogBuffer.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
queue (queue.Queue): A thread-safe queue to push new log lines to.
|
|
38
|
+
n (int): The maximum number of log lines to retain in the buffer's history.
|
|
39
|
+
Defaults to 15.
|
|
40
|
+
event (threading.Event, optional): An optional `threading.Event` object.
|
|
41
|
+
If provided, this event is set whenever
|
|
42
|
+
a new log line is added, signaling
|
|
43
|
+
consumers that new data is available.
|
|
44
|
+
Defaults to None.
|
|
45
|
+
"""
|
|
46
|
+
self.queue = queue
|
|
47
|
+
self.buffer = deque(maxlen=n)
|
|
48
|
+
self.lock = threading.Lock()
|
|
49
|
+
if not event:
|
|
50
|
+
# Create dummy event
|
|
51
|
+
event = threading.Event()
|
|
52
|
+
self.event = event
|
|
53
|
+
|
|
54
|
+
def make_handler(self) -> logging.Handler:
|
|
55
|
+
"""
|
|
56
|
+
Creates and returns a `LogBufferHandler` instance associated with this `LogBuffer`.
|
|
57
|
+
|
|
58
|
+
This handler can then be added to a Python logger to direct log messages
|
|
59
|
+
to this buffer.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
logging.Handler: An instance of `LogBufferHandler`.
|
|
63
|
+
"""
|
|
64
|
+
return LogBufferHandler(self)
|
|
65
|
+
|
|
66
|
+
def add_line(self, line: str):
|
|
67
|
+
"""
|
|
68
|
+
Adds a new log line to the internal queue and signals the event.
|
|
69
|
+
|
|
70
|
+
This method is called by the `LogBufferHandler` (or directly) to
|
|
71
|
+
append a processed log line to the buffer. It also sets the internal
|
|
72
|
+
threading event to notify any waiting consumers.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
line (str): The log line (string) to add.
|
|
76
|
+
"""
|
|
77
|
+
self.queue.put(line)
|
|
78
|
+
self.event.set()
|
|
79
|
+
|
|
80
|
+
def get_lines(self, lines: int = None) -> List[str]:
|
|
81
|
+
"""
|
|
82
|
+
Retrieves the last logged lines from the buffer.
|
|
83
|
+
|
|
84
|
+
New lines are first moved from the internal queue to the buffer,
|
|
85
|
+
and then the requested number of lines are returned from the buffer's history.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
lines (int, optional): The maximum number of recent lines to retrieve.
|
|
89
|
+
If None, all lines currently in the buffer are returned.
|
|
90
|
+
Defaults to None.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
list[str]: A list of the last logged lines.
|
|
94
|
+
"""
|
|
95
|
+
new_lines = []
|
|
96
|
+
try:
|
|
97
|
+
for _ in range(self.queue.qsize()):
|
|
98
|
+
new_lines.append(self.queue.get_nowait())
|
|
99
|
+
except queue.Empty:
|
|
100
|
+
pass
|
|
101
|
+
if not self.queue.empty():
|
|
102
|
+
# Set event since queue is not empty
|
|
103
|
+
self.event.set()
|
|
104
|
+
|
|
105
|
+
with self.lock:
|
|
106
|
+
self.buffer.extend(new_lines)
|
|
107
|
+
buffer_list = list(self.buffer)
|
|
108
|
+
|
|
109
|
+
if lines is None or lines > len(buffer_list):
|
|
110
|
+
return buffer_list
|
|
111
|
+
return buffer_list[-lines:]
|
|
26
112
|
|
|
27
113
|
|
|
28
114
|
class LogBufferHandler(logging.Handler):
|
|
29
|
-
|
|
115
|
+
"""
|
|
116
|
+
A custom logging handler that buffers log records and processes them
|
|
117
|
+
for display in a dashboard or other UI, replacing console color codes
|
|
118
|
+
with a simplified markdown-like format.
|
|
119
|
+
"""
|
|
120
|
+
def __init__(self, parent: LogBuffer):
|
|
30
121
|
"""
|
|
31
|
-
Initializes the
|
|
122
|
+
Initializes the LogBufferHandler.
|
|
32
123
|
|
|
33
124
|
Args:
|
|
34
|
-
|
|
35
|
-
|
|
125
|
+
parent (LogBuffer): The parent `LogBuffer` instance to which processed
|
|
126
|
+
log lines will be added.
|
|
36
127
|
"""
|
|
37
128
|
super().__init__()
|
|
38
|
-
self.
|
|
39
|
-
self.buffer = deque(maxlen=n)
|
|
40
|
-
self.event = event
|
|
41
|
-
self._lock = threading.Lock()
|
|
129
|
+
self._parent = parent
|
|
42
130
|
|
|
43
131
|
def emit(self, record):
|
|
44
132
|
"""
|
|
45
|
-
Processes a log record
|
|
133
|
+
Processes a log record, formats it, replaces console color codes,
|
|
134
|
+
and adds the transformed line to the parent `LogBuffer`.
|
|
46
135
|
|
|
47
136
|
Args:
|
|
48
137
|
record (logging.LogRecord): The log record to process.
|
|
@@ -58,31 +147,27 @@ class LogBufferHandler(logging.Handler):
|
|
|
58
147
|
(SCColorLoggerFormatter.red.replace("[", "\\["), "[red]"),
|
|
59
148
|
(SCColorLoggerFormatter.bold_red.replace("[", "\\["), "[bold red]")):
|
|
60
149
|
log_entry = log_entry.replace(color, replacement)
|
|
61
|
-
self.
|
|
62
|
-
if self.event:
|
|
63
|
-
self.event.set()
|
|
64
|
-
|
|
65
|
-
def get_lines(self, lines=None):
|
|
66
|
-
"""
|
|
67
|
-
Retrieves the last logged lines.
|
|
68
|
-
|
|
69
|
-
Returns:
|
|
70
|
-
list: A list of the last logged lines.
|
|
71
|
-
"""
|
|
72
|
-
with self._lock:
|
|
73
|
-
while not self.queue.empty():
|
|
74
|
-
try:
|
|
75
|
-
self.buffer.append(self.queue.get_nowait())
|
|
76
|
-
except queue.Empty:
|
|
77
|
-
break
|
|
78
|
-
buffer_list = list(self.buffer)
|
|
79
|
-
if lines is None or lines > len(buffer_list):
|
|
80
|
-
return buffer_list
|
|
81
|
-
return buffer_list[-lines:]
|
|
150
|
+
self._parent.add_line(log_entry)
|
|
82
151
|
|
|
83
152
|
|
|
84
153
|
@dataclass
|
|
85
154
|
class JobData:
|
|
155
|
+
"""
|
|
156
|
+
A data class to hold information about a single job's progress and status.
|
|
157
|
+
|
|
158
|
+
Attributes:
|
|
159
|
+
total (int): The total number of nodes in the job.
|
|
160
|
+
success (int): The number of successfully completed nodes.
|
|
161
|
+
error (int): The number of nodes that resulted in an error.
|
|
162
|
+
skipped (int): The number of skipped nodes.
|
|
163
|
+
finished (int): The total number of finished nodes (success or error).
|
|
164
|
+
jobname (str): The name of the job.
|
|
165
|
+
design (str): The name of the design associated with the job.
|
|
166
|
+
runtime (float): The total runtime of the job so far.
|
|
167
|
+
complete (bool): A flag indicating if the job has completed.
|
|
168
|
+
nodes (List[dict]): A list of dictionaries, each containing detailed
|
|
169
|
+
information about a single node in the flowgraph.
|
|
170
|
+
"""
|
|
86
171
|
total: int = 0
|
|
87
172
|
success: int = 0
|
|
88
173
|
error: int = 0
|
|
@@ -97,6 +182,20 @@ class JobData:
|
|
|
97
182
|
|
|
98
183
|
@dataclass
|
|
99
184
|
class SessionData:
|
|
185
|
+
"""
|
|
186
|
+
A data class to hold aggregated information about the entire run session,
|
|
187
|
+
which may include multiple jobs.
|
|
188
|
+
|
|
189
|
+
Attributes:
|
|
190
|
+
total (int): The total number of nodes across all jobs.
|
|
191
|
+
success (int): The total number of successfully completed nodes across all jobs.
|
|
192
|
+
error (int): The total number of nodes that resulted in an error across all jobs.
|
|
193
|
+
skipped (int): The total number of skipped nodes across all jobs.
|
|
194
|
+
finished (int): The total number of finished nodes across all jobs.
|
|
195
|
+
runtime (float): The maximum runtime among all jobs.
|
|
196
|
+
jobs (Dict[str, JobData]): A dictionary mapping job identifiers to their
|
|
197
|
+
corresponding JobData objects.
|
|
198
|
+
"""
|
|
100
199
|
total: int = 0
|
|
101
200
|
success: int = 0
|
|
102
201
|
error: int = 0
|
|
@@ -109,69 +208,85 @@ class SessionData:
|
|
|
109
208
|
@dataclass
|
|
110
209
|
class Layout:
|
|
111
210
|
"""
|
|
112
|
-
|
|
211
|
+
Manages the dynamic layout of the dashboard, calculating the height
|
|
212
|
+
of different sections based on terminal size and content.
|
|
113
213
|
|
|
114
214
|
Attributes:
|
|
115
|
-
height (int): The total height of the
|
|
116
|
-
width (int): The total width of the
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
progress_bar_max (int): The maximum height allocated for the progress bar.
|
|
123
|
-
job_board_show_log (bool): A flag indicating whether to show the log in the job board.
|
|
124
|
-
|
|
125
|
-
__reserved (int): Reserved space for table headings and extra padding.
|
|
126
|
-
|
|
127
|
-
Methods:
|
|
128
|
-
available_height():
|
|
129
|
-
Calculates and returns the available height for other components in the layout
|
|
130
|
-
after accounting for reserved space, job board, and log sections.
|
|
131
|
-
Returns 0 if the total height is not set.
|
|
215
|
+
height (int): The total height of the terminal.
|
|
216
|
+
width (int): The total width of the terminal.
|
|
217
|
+
log_height (int): The calculated height for the log display area.
|
|
218
|
+
job_board_height (int): The calculated height for the job status board.
|
|
219
|
+
progress_bar_height (int): The calculated height for the progress bar section.
|
|
220
|
+
job_board_show_log (bool): Flag to determine if the log file column is shown.
|
|
221
|
+
job_board_v_limit (int): Width threshold to switch to a more compact view.
|
|
132
222
|
"""
|
|
133
223
|
|
|
134
224
|
height: int = 0
|
|
135
225
|
width: int = 0
|
|
136
226
|
|
|
137
|
-
log_height = 0
|
|
138
|
-
job_board_height = 0
|
|
139
|
-
progress_bar_height = 0
|
|
227
|
+
log_height: int = 0
|
|
228
|
+
job_board_height: int = 0
|
|
229
|
+
progress_bar_height: int = 0
|
|
140
230
|
|
|
141
231
|
job_board_show_log: bool = True
|
|
142
232
|
job_board_v_limit: int = 120
|
|
143
233
|
|
|
144
234
|
__progress_bar_height_default = 1
|
|
145
235
|
padding_log = 2
|
|
146
|
-
padding_progress_bar = 1
|
|
147
|
-
padding_job_board = 1
|
|
148
|
-
padding_job_board_header = 1
|
|
236
|
+
padding_progress_bar: int = 1
|
|
237
|
+
padding_job_board: int = 1
|
|
238
|
+
padding_job_board_header: int = 1
|
|
149
239
|
|
|
150
|
-
def update(self, height, width, visible_jobs, visible_bars):
|
|
240
|
+
def update(self, height: int, width: int, visible_jobs: int, visible_bars: int):
|
|
241
|
+
"""
|
|
242
|
+
Recalculates the layout dimensions based on the current terminal size and content.
|
|
243
|
+
|
|
244
|
+
This method implements the logic to intelligently allocate vertical space
|
|
245
|
+
to the progress bars, job board, and log view.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
height (int): The current terminal height.
|
|
249
|
+
width (int): The current terminal width.
|
|
250
|
+
visible_jobs (int): The number of job nodes to be displayed.
|
|
251
|
+
visible_bars (int): The number of progress bars to be displayed.
|
|
252
|
+
"""
|
|
151
253
|
self.height = height
|
|
152
254
|
self.width = width
|
|
153
255
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
+ self.padding_progress_bar
|
|
157
|
-
)
|
|
158
|
-
if self.height < min_required:
|
|
159
|
-
self.progress_bar_height = 0
|
|
256
|
+
if self.height < 3:
|
|
257
|
+
self.progress_bar_height = self.height - self.padding_progress_bar - 1
|
|
160
258
|
self.job_board_height = 0
|
|
161
259
|
self.log_height = 0
|
|
162
|
-
|
|
260
|
+
|
|
261
|
+
# target sizes
|
|
262
|
+
target_jobs = 0.25 * self.height
|
|
263
|
+
target_bars = 0.50 * self.height
|
|
264
|
+
# 25 % for log
|
|
265
|
+
|
|
266
|
+
# Adjust targets based on progress bars
|
|
267
|
+
if visible_bars < target_bars:
|
|
268
|
+
remainder = target_bars - visible_bars
|
|
269
|
+
target_bars = visible_bars
|
|
270
|
+
target_jobs += 0.75 * remainder
|
|
271
|
+
target_bars = int(math.ceil(target_bars))
|
|
272
|
+
|
|
273
|
+
# Adjust targets based on jobs
|
|
274
|
+
if visible_jobs < target_jobs:
|
|
275
|
+
target_jobs = visible_jobs
|
|
276
|
+
target_jobs = int(math.ceil(target_jobs))
|
|
163
277
|
|
|
164
278
|
remaining_height = self.height
|
|
165
279
|
|
|
166
280
|
# Allocate progress bar space (highest priority)
|
|
167
|
-
self.progress_bar_height = max(
|
|
281
|
+
self.progress_bar_height = max(min(target_bars, visible_bars),
|
|
282
|
+
self.__progress_bar_height_default)
|
|
168
283
|
if self.progress_bar_height > 0:
|
|
169
284
|
remaining_height -= self.progress_bar_height + self.padding_progress_bar
|
|
170
285
|
|
|
171
286
|
# Calculate job board requirements
|
|
172
287
|
job_board_min_space = self.padding_job_board_header + self.padding_job_board
|
|
173
288
|
job_board_max_nodes = remaining_height // 2
|
|
174
|
-
visible_jobs = min(visible_jobs, job_board_max_nodes)
|
|
289
|
+
visible_jobs = min(min(target_jobs, visible_jobs), job_board_max_nodes)
|
|
175
290
|
if visible_jobs > 0:
|
|
176
291
|
job_board_full_space = visible_jobs + job_board_min_space
|
|
177
292
|
else:
|
|
@@ -190,24 +305,21 @@ class Layout:
|
|
|
190
305
|
else:
|
|
191
306
|
self.job_board_height = visible_jobs
|
|
192
307
|
self.log_height = remaining_height - job_board_full_space - self.padding_log
|
|
308
|
+
if self.log_height < 0:
|
|
309
|
+
self.log_height = 0
|
|
193
310
|
|
|
194
311
|
if self.width < self.job_board_v_limit:
|
|
195
312
|
self.job_board_show_log = False
|
|
196
313
|
|
|
197
314
|
|
|
198
|
-
class
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def __call__(cls, *args, **kwargs):
|
|
203
|
-
with cls._lock:
|
|
204
|
-
if cls not in cls._instances:
|
|
205
|
-
cls._instances[cls] = super(BoardSingleton, cls).__call__(*args, **kwargs)
|
|
206
|
-
cls._instances[cls]._init_singleton()
|
|
207
|
-
return cls._instances[cls]
|
|
208
|
-
|
|
315
|
+
class Board:
|
|
316
|
+
"""
|
|
317
|
+
The main class for rendering the live dashboard UI.
|
|
209
318
|
|
|
210
|
-
class
|
|
319
|
+
This class orchestrates the display of job progress, logs, and status updates
|
|
320
|
+
in the terminal using the `rich` library. It runs in a separate thread to
|
|
321
|
+
provide a non-blocking UI.
|
|
322
|
+
"""
|
|
211
323
|
__status_color_map = {
|
|
212
324
|
NodeStatus.PENDING: "blue",
|
|
213
325
|
NodeStatus.QUEUED: "blue",
|
|
@@ -243,20 +355,22 @@ class Board(metaclass=BoardSingleton):
|
|
|
243
355
|
|
|
244
356
|
__JOB_BOARD_BOX = box.SIMPLE_HEAD
|
|
245
357
|
|
|
246
|
-
def __init__(self):
|
|
247
|
-
|
|
358
|
+
def __init__(self, manager):
|
|
359
|
+
"""
|
|
360
|
+
Initializes the Board.
|
|
248
361
|
|
|
249
|
-
|
|
362
|
+
Args:
|
|
363
|
+
manager: A multiprocessing.Manager object to create shared state
|
|
364
|
+
(events, dicts, locks) between processes.
|
|
365
|
+
"""
|
|
250
366
|
self._console = Console(theme=Board.__theme)
|
|
251
367
|
|
|
252
368
|
self.live = Live(
|
|
253
369
|
console=self._console,
|
|
254
|
-
screen=
|
|
255
|
-
auto_refresh=True
|
|
370
|
+
screen=True,
|
|
371
|
+
auto_refresh=True
|
|
256
372
|
)
|
|
257
373
|
|
|
258
|
-
atexit.register(self._stop_on_exit)
|
|
259
|
-
|
|
260
374
|
self._active = self._console.is_terminal
|
|
261
375
|
if not self._active:
|
|
262
376
|
self._console = None
|
|
@@ -264,34 +378,39 @@ class Board(metaclass=BoardSingleton):
|
|
|
264
378
|
|
|
265
379
|
self._layout = Layout()
|
|
266
380
|
|
|
267
|
-
|
|
268
|
-
self.
|
|
269
|
-
|
|
270
|
-
self._render_event = self._manager.Event()
|
|
271
|
-
self._render_stop_event = self._manager.Event()
|
|
381
|
+
self._render_event = manager.Event()
|
|
382
|
+
self._render_stop_event = manager.Event()
|
|
272
383
|
self._render_thread = None
|
|
273
384
|
|
|
274
385
|
# Holds thread job data
|
|
275
|
-
self._board_info =
|
|
386
|
+
self._board_info = manager.Namespace()
|
|
276
387
|
self._board_info.data_modified = False
|
|
277
|
-
self._job_data =
|
|
278
|
-
self._job_data_lock =
|
|
388
|
+
self._job_data = manager.dict()
|
|
389
|
+
self._job_data_lock = manager.Lock()
|
|
279
390
|
|
|
280
391
|
self._render_data = SessionData()
|
|
281
392
|
self._render_data_lock = threading.Lock()
|
|
282
393
|
|
|
283
|
-
self._log_handler_queue =
|
|
394
|
+
self._log_handler_queue = manager.Queue()
|
|
395
|
+
|
|
396
|
+
self._log_handler = LogBuffer(self._log_handler_queue, n=120, event=self._render_event)
|
|
284
397
|
|
|
285
|
-
|
|
286
|
-
|
|
398
|
+
# Sleep time for the dashboard
|
|
399
|
+
self._dwell = 0.1
|
|
287
400
|
|
|
288
401
|
if not self.__JOB_BOARD_HEADER:
|
|
289
402
|
self._layout.padding_job_board_header = 0
|
|
290
403
|
|
|
291
404
|
self._metrics = ("warnings", "errors")
|
|
292
405
|
|
|
293
|
-
def
|
|
294
|
-
|
|
406
|
+
def make_log_hander(self) -> logging.Handler:
|
|
407
|
+
"""
|
|
408
|
+
Creates and returns a logging handler that directs logs to this board.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
logging.Handler: The log handler instance.
|
|
412
|
+
"""
|
|
413
|
+
return self._log_handler.make_handler()
|
|
295
414
|
|
|
296
415
|
def open_dashboard(self):
|
|
297
416
|
"""Starts the dashboard rendering thread if it is not already running."""
|
|
@@ -312,8 +431,12 @@ class Board(metaclass=BoardSingleton):
|
|
|
312
431
|
|
|
313
432
|
def update_manifest(self, chip, starttimes=None):
|
|
314
433
|
"""
|
|
315
|
-
Updates the
|
|
316
|
-
|
|
434
|
+
Updates the dashboard with the latest data from a chip object's manifest.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
chip: The SiliconCompiler chip object.
|
|
438
|
+
starttimes (dict, optional): A dictionary mapping (step, index) tuples
|
|
439
|
+
to their start times. Defaults to None.
|
|
317
440
|
"""
|
|
318
441
|
|
|
319
442
|
if not self._active:
|
|
@@ -321,13 +444,24 @@ class Board(metaclass=BoardSingleton):
|
|
|
321
444
|
|
|
322
445
|
self._update_render_data(chip, starttimes=starttimes)
|
|
323
446
|
|
|
324
|
-
def is_running(self):
|
|
325
|
-
"""
|
|
447
|
+
def is_running(self) -> bool:
|
|
448
|
+
"""
|
|
449
|
+
Checks if the dashboard rendering thread is currently active.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
bool: True if the dashboard is running, False otherwise.
|
|
453
|
+
"""
|
|
326
454
|
|
|
327
455
|
if not self._active:
|
|
328
456
|
return False
|
|
329
457
|
|
|
330
|
-
|
|
458
|
+
try:
|
|
459
|
+
with self._job_data_lock:
|
|
460
|
+
if not self._render_thread:
|
|
461
|
+
return False
|
|
462
|
+
|
|
463
|
+
return self._render_thread.is_alive()
|
|
464
|
+
except BrokenPipeError:
|
|
331
465
|
if not self._render_thread:
|
|
332
466
|
return False
|
|
333
467
|
|
|
@@ -335,7 +469,10 @@ class Board(metaclass=BoardSingleton):
|
|
|
335
469
|
|
|
336
470
|
def end_of_run(self, chip):
|
|
337
471
|
"""
|
|
338
|
-
|
|
472
|
+
Signals that the run has completed, performing a final update.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
chip: The SiliconCompiler chip object at the end of the run.
|
|
339
476
|
"""
|
|
340
477
|
|
|
341
478
|
if not self._active:
|
|
@@ -345,7 +482,7 @@ class Board(metaclass=BoardSingleton):
|
|
|
345
482
|
|
|
346
483
|
def stop(self):
|
|
347
484
|
"""
|
|
348
|
-
Stops the dashboard rendering thread and
|
|
485
|
+
Stops the dashboard rendering thread and cleans up the terminal display.
|
|
349
486
|
"""
|
|
350
487
|
if not self.is_running():
|
|
351
488
|
return
|
|
@@ -362,6 +499,10 @@ class Board(metaclass=BoardSingleton):
|
|
|
362
499
|
|
|
363
500
|
# Restore terminal
|
|
364
501
|
self.live.stop()
|
|
502
|
+
|
|
503
|
+
# Print final render to avoid losing it
|
|
504
|
+
if self.live._screen:
|
|
505
|
+
self._console.print(self._get_rendable())
|
|
365
506
|
self._console.show_cursor()
|
|
366
507
|
|
|
367
508
|
def wait(self):
|
|
@@ -372,12 +513,12 @@ class Board(metaclass=BoardSingleton):
|
|
|
372
513
|
self._render_thread.join()
|
|
373
514
|
|
|
374
515
|
@staticmethod
|
|
375
|
-
def format_status(status: str):
|
|
516
|
+
def format_status(status: str) -> str:
|
|
376
517
|
"""
|
|
377
|
-
Formats
|
|
518
|
+
Formats a node status string with rich-compatible color markup.
|
|
378
519
|
|
|
379
520
|
Args:
|
|
380
|
-
status (str): The status of the node (e.g., '
|
|
521
|
+
status (str): The status of the node (e.g., 'RUNNING', 'SUCCESS').
|
|
381
522
|
|
|
382
523
|
Returns:
|
|
383
524
|
str: A formatted string with the status styled for display.
|
|
@@ -385,25 +526,37 @@ class Board(metaclass=BoardSingleton):
|
|
|
385
526
|
return f"[node.{status.lower()}]{status.upper()}[/]"
|
|
386
527
|
|
|
387
528
|
@staticmethod
|
|
388
|
-
def format_node(design, jobname, step, index, multi_job) -> str:
|
|
529
|
+
def format_node(design: str, jobname: str, step: str, index: int, multi_job: bool) -> str:
|
|
389
530
|
"""
|
|
390
|
-
Formats a node's
|
|
531
|
+
Formats a node's identifier for display in the dashboard.
|
|
391
532
|
|
|
392
533
|
Args:
|
|
393
534
|
design (str): The design name.
|
|
394
535
|
jobname (str): The job name.
|
|
395
536
|
step (str): The step name.
|
|
396
537
|
index (int): The step index.
|
|
538
|
+
multi_job (bool): Flag indicating if multiple jobs are running, which
|
|
539
|
+
determines the format of the identifier.
|
|
397
540
|
|
|
398
541
|
Returns:
|
|
399
|
-
str: A formatted string
|
|
542
|
+
str: A formatted string representing the node.
|
|
400
543
|
"""
|
|
401
544
|
if multi_job:
|
|
402
545
|
return f"{design}/{jobname}/{step}/{index}"
|
|
403
546
|
else:
|
|
404
547
|
return f"{step}/{index}"
|
|
405
548
|
|
|
406
|
-
def _render_log(self, layout):
|
|
549
|
+
def _render_log(self, layout: Layout):
|
|
550
|
+
"""
|
|
551
|
+
Renders the log message area of the dashboard.
|
|
552
|
+
|
|
553
|
+
Args:
|
|
554
|
+
layout (Layout): The current layout object containing dimensions.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
rich.group.Group or None: A renderable Group object for the log
|
|
558
|
+
area, or None if there's no space.
|
|
559
|
+
"""
|
|
407
560
|
if layout.log_height == 0:
|
|
408
561
|
return None
|
|
409
562
|
|
|
@@ -420,12 +573,16 @@ class Board(metaclass=BoardSingleton):
|
|
|
420
573
|
|
|
421
574
|
return Group(table, Padding("", (0, 0)))
|
|
422
575
|
|
|
423
|
-
def _render_job_dashboard(self, layout):
|
|
576
|
+
def _render_job_dashboard(self, layout: Layout):
|
|
424
577
|
"""
|
|
425
|
-
|
|
578
|
+
Renders the main job status board.
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
layout (Layout): The current layout object containing dimensions.
|
|
426
582
|
|
|
427
583
|
Returns:
|
|
428
|
-
Group: A
|
|
584
|
+
rich.group.Group or None: A renderable Group containing the job table,
|
|
585
|
+
or None if there is no space or no data.
|
|
429
586
|
"""
|
|
430
587
|
# Don't render anything if there is not enough space
|
|
431
588
|
if layout.job_board_height == 0:
|
|
@@ -477,13 +634,12 @@ class Board(metaclass=BoardSingleton):
|
|
|
477
634
|
job = job_data[chipid]
|
|
478
635
|
node = job.nodes[node_idx]
|
|
479
636
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
log_file = None
|
|
637
|
+
log_file = None
|
|
638
|
+
if layout.job_board_show_log:
|
|
639
|
+
for log in node["log"]:
|
|
640
|
+
if os.path.exists(log):
|
|
641
|
+
log_file = "[bright_black]{}[/]".format(log)
|
|
642
|
+
break
|
|
487
643
|
|
|
488
644
|
if node["time"]["duration"] is not None:
|
|
489
645
|
duration = f'{node["time"]["duration"]:.1f}s'
|
|
@@ -513,21 +669,19 @@ class Board(metaclass=BoardSingleton):
|
|
|
513
669
|
return Group(table, Padding("", (0, 0)))
|
|
514
670
|
return Group(Padding("", (0, 0)), table, Padding("", (0, 0)))
|
|
515
671
|
|
|
516
|
-
def _render_progress_bar(self, layout):
|
|
672
|
+
def _render_progress_bar(self, layout: Layout):
|
|
517
673
|
"""
|
|
518
|
-
|
|
674
|
+
Renders the progress bar section of the dashboard.
|
|
675
|
+
|
|
676
|
+
Args:
|
|
677
|
+
layout (Layout): The current layout object containing dimensions.
|
|
519
678
|
|
|
520
679
|
Returns:
|
|
521
|
-
Group: A
|
|
680
|
+
rich.group.Group or rich.padding.Padding: A renderable object for the
|
|
681
|
+
progress bars.
|
|
522
682
|
"""
|
|
523
683
|
with self._render_data_lock:
|
|
524
684
|
job_data = self._render_data.jobs.copy()
|
|
525
|
-
done = self._render_data.finished > 0 \
|
|
526
|
-
and self._render_data.total == self._render_data.finished \
|
|
527
|
-
and self._render_data.success == self._render_data.total
|
|
528
|
-
|
|
529
|
-
if done:
|
|
530
|
-
return None
|
|
531
685
|
|
|
532
686
|
ref_time = time.time()
|
|
533
687
|
runtimes = {}
|
|
@@ -545,6 +699,24 @@ class Board(metaclass=BoardSingleton):
|
|
|
545
699
|
|
|
546
700
|
runtime_width = len(f"{max([0, *runtimes.values()]):.1f}")
|
|
547
701
|
|
|
702
|
+
job_info = []
|
|
703
|
+
for name, job in job_data.items():
|
|
704
|
+
done = job.finished == job.total
|
|
705
|
+
job_info.append(
|
|
706
|
+
(done, f"{job.design}/{job.jobname}", job.total, job.success, runtimes[name]))
|
|
707
|
+
|
|
708
|
+
while len(job_info) > layout.progress_bar_height:
|
|
709
|
+
for job in job_info:
|
|
710
|
+
if job[0]:
|
|
711
|
+
# complete complete and can be removed
|
|
712
|
+
job_info.remove(job)
|
|
713
|
+
break
|
|
714
|
+
# remove first job
|
|
715
|
+
del job_info[0]
|
|
716
|
+
|
|
717
|
+
if not job_info:
|
|
718
|
+
return Padding("", (0, 0))
|
|
719
|
+
|
|
548
720
|
progress = Progress(
|
|
549
721
|
TextColumn("[progress.description]{task.description}"),
|
|
550
722
|
MofNCompleteColumn(),
|
|
@@ -552,48 +724,23 @@ class Board(metaclass=BoardSingleton):
|
|
|
552
724
|
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
553
725
|
TextColumn(f" {{task.fields[runtime]:>{runtime_width}.1f}}s")
|
|
554
726
|
)
|
|
555
|
-
|
|
556
|
-
for name, job in job_data.items():
|
|
557
|
-
nodes += len(job.nodes)
|
|
727
|
+
for _, name, total, success, runtime in job_info:
|
|
558
728
|
progress.add_task(
|
|
559
|
-
f"[text.primary]Progress ({
|
|
560
|
-
total=
|
|
561
|
-
completed=
|
|
562
|
-
runtime=
|
|
729
|
+
f"[text.primary]Progress ({name}):",
|
|
730
|
+
total=total,
|
|
731
|
+
completed=success,
|
|
732
|
+
runtime=runtime
|
|
563
733
|
)
|
|
564
734
|
|
|
565
|
-
if nodes == 0:
|
|
566
|
-
return Padding("", (0, 0))
|
|
567
|
-
|
|
568
735
|
return Group(progress, Padding("", (0, 0)))
|
|
569
736
|
|
|
570
|
-
def _render_final(self, layout):
|
|
571
|
-
"""
|
|
572
|
-
Creates a summary of the final results, including runtime, passed, and failed jobs.
|
|
573
|
-
|
|
574
|
-
Returns:
|
|
575
|
-
Padding: A Rich Padding object containing the summary text.
|
|
576
|
-
"""
|
|
577
|
-
with self._render_data_lock:
|
|
578
|
-
success = self._render_data.success
|
|
579
|
-
error = self._render_data.error
|
|
580
|
-
total = self._render_data.total
|
|
581
|
-
finished = self._render_data.finished
|
|
582
|
-
runtime = self._render_data.runtime
|
|
583
|
-
|
|
584
|
-
if finished != 0 and finished == total:
|
|
585
|
-
return Padding(
|
|
586
|
-
f"[text.primary]Results {runtime:.2f}s\n"
|
|
587
|
-
f" [success]{success} passed[/]\n"
|
|
588
|
-
f" [error]{error} failed[/]\n"
|
|
589
|
-
)
|
|
590
|
-
|
|
591
|
-
return self._render_log(layout)
|
|
592
|
-
|
|
593
737
|
def _render(self):
|
|
594
738
|
"""
|
|
595
|
-
Main rendering
|
|
596
|
-
|
|
739
|
+
Main rendering loop for the dashboard.
|
|
740
|
+
|
|
741
|
+
This method runs in a separate thread. It waits for update events,
|
|
742
|
+
fetches the latest data, re-renders the components, and updates the
|
|
743
|
+
live display.
|
|
597
744
|
"""
|
|
598
745
|
|
|
599
746
|
def update_data():
|
|
@@ -618,7 +765,7 @@ class Board(metaclass=BoardSingleton):
|
|
|
618
765
|
|
|
619
766
|
while not check_stop_event():
|
|
620
767
|
try:
|
|
621
|
-
if self._render_event.wait(timeout=
|
|
768
|
+
if self._render_event.wait(timeout=self._dwell):
|
|
622
769
|
self._render_event.clear()
|
|
623
770
|
except: # noqa E722
|
|
624
771
|
# Catch any multiprocessing errors
|
|
@@ -629,6 +776,7 @@ class Board(metaclass=BoardSingleton):
|
|
|
629
776
|
|
|
630
777
|
update_data()
|
|
631
778
|
self.live.update(self._get_rendable(), refresh=True)
|
|
779
|
+
time.sleep(self._dwell)
|
|
632
780
|
|
|
633
781
|
finally:
|
|
634
782
|
update_data()
|
|
@@ -637,7 +785,13 @@ class Board(metaclass=BoardSingleton):
|
|
|
637
785
|
else:
|
|
638
786
|
self._console.print(self._get_rendable())
|
|
639
787
|
|
|
640
|
-
def _update_layout(self):
|
|
788
|
+
def _update_layout(self) -> Layout:
|
|
789
|
+
"""
|
|
790
|
+
Updates the layout dimensions based on the current data and console size.
|
|
791
|
+
|
|
792
|
+
Returns:
|
|
793
|
+
Layout: The updated layout object.
|
|
794
|
+
"""
|
|
641
795
|
with self._render_data_lock:
|
|
642
796
|
visible_progress_bars = len(self._render_data.jobs)
|
|
643
797
|
visible_jobs_count = self._render_data.total - self._render_data.skipped
|
|
@@ -652,6 +806,10 @@ class Board(metaclass=BoardSingleton):
|
|
|
652
806
|
return self._layout
|
|
653
807
|
|
|
654
808
|
def _update_rendable_data(self):
|
|
809
|
+
"""
|
|
810
|
+
Transfers job data from the shared multiprocessing dictionary to the
|
|
811
|
+
local render data object, aggregating session-wide statistics.
|
|
812
|
+
"""
|
|
655
813
|
jobs = {}
|
|
656
814
|
with self._job_data_lock:
|
|
657
815
|
if self._board_info.data_modified:
|
|
@@ -688,18 +846,20 @@ class Board(metaclass=BoardSingleton):
|
|
|
688
846
|
|
|
689
847
|
def _get_rendable(self):
|
|
690
848
|
"""
|
|
691
|
-
|
|
692
|
-
|
|
849
|
+
Assembles the final renderable object for the `rich.live` display.
|
|
850
|
+
|
|
851
|
+
It gets the latest layout, renders each component (job board, progress bars, log),
|
|
852
|
+
and combines them into a single `rich.group.Group`.
|
|
693
853
|
|
|
694
854
|
Returns:
|
|
695
|
-
Group:
|
|
855
|
+
rich.group.Group: The complete, renderable dashboard layout.
|
|
696
856
|
"""
|
|
697
857
|
|
|
698
858
|
layout = self._update_layout()
|
|
699
859
|
|
|
700
860
|
new_table = self._render_job_dashboard(layout)
|
|
701
861
|
new_bar = self._render_progress_bar(layout)
|
|
702
|
-
footer = self.
|
|
862
|
+
footer = self._render_log(layout)
|
|
703
863
|
|
|
704
864
|
items = []
|
|
705
865
|
if new_table:
|
|
@@ -715,8 +875,13 @@ class Board(metaclass=BoardSingleton):
|
|
|
715
875
|
|
|
716
876
|
def _update_render_data(self, chip, starttimes=None, complete=False):
|
|
717
877
|
"""
|
|
718
|
-
|
|
719
|
-
|
|
878
|
+
Extracts job and node information from a chip object and updates the
|
|
879
|
+
shared job data dictionary, triggering a render event.
|
|
880
|
+
|
|
881
|
+
Args:
|
|
882
|
+
chip: The SiliconCompiler chip object.
|
|
883
|
+
starttimes (dict, optional): Dictionary of node start times. Defaults to None.
|
|
884
|
+
complete (bool, optional): Flag indicating if the job is complete. Defaults to False.
|
|
720
885
|
"""
|
|
721
886
|
|
|
722
887
|
if not chip:
|
|
@@ -735,6 +900,21 @@ class Board(metaclass=BoardSingleton):
|
|
|
735
900
|
self._render_event.set()
|
|
736
901
|
|
|
737
902
|
def _get_job(self, chip, starttimes=None) -> JobData:
|
|
903
|
+
"""
|
|
904
|
+
Parses a chip object to extract detailed information about the flowgraph,
|
|
905
|
+
node statuses, timings, and metrics.
|
|
906
|
+
|
|
907
|
+
This method calculates node display priority based on run status and
|
|
908
|
+
dependencies.
|
|
909
|
+
|
|
910
|
+
Args:
|
|
911
|
+
chip: The SiliconCompiler chip object to parse.
|
|
912
|
+
starttimes (dict, optional): A dictionary of node start times.
|
|
913
|
+
Defaults to None.
|
|
914
|
+
|
|
915
|
+
Returns:
|
|
916
|
+
JobData: A data object populated with the extracted information.
|
|
917
|
+
"""
|
|
738
918
|
if not starttimes:
|
|
739
919
|
starttimes = {}
|
|
740
920
|
|
|
@@ -881,13 +1061,18 @@ class Board(metaclass=BoardSingleton):
|
|
|
881
1061
|
"duration": duration
|
|
882
1062
|
},
|
|
883
1063
|
"metrics": node_metrics,
|
|
884
|
-
"log": os.path.join(
|
|
1064
|
+
"log": [os.path.join(
|
|
885
1065
|
os.path.relpath(
|
|
886
1066
|
chip.getworkdir(step=step, index=index),
|
|
887
1067
|
chip.cwd,
|
|
888
1068
|
),
|
|
889
|
-
f"{step}.log",
|
|
890
|
-
|
|
1069
|
+
f"{step}.log"),
|
|
1070
|
+
os.path.join(
|
|
1071
|
+
os.path.relpath(
|
|
1072
|
+
chip.getworkdir(step=step, index=index),
|
|
1073
|
+
chip.cwd,
|
|
1074
|
+
),
|
|
1075
|
+
f"sc_{step}_{index}.log")],
|
|
891
1076
|
"print": {
|
|
892
1077
|
"order": nodeorder[(step, index)],
|
|
893
1078
|
"priority": node_priority[(step, index)]
|