siliconcompiler 0.32.3__py3-none-any.whl → 0.33.0__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 +19 -2
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/sc.py +2 -2
- siliconcompiler/apps/sc_install.py +3 -3
- siliconcompiler/apps/sc_issue.py +1 -1
- siliconcompiler/apps/sc_remote.py +4 -4
- siliconcompiler/apps/sc_show.py +2 -2
- siliconcompiler/apps/utils/replay.py +5 -3
- siliconcompiler/asic.py +120 -0
- siliconcompiler/checklist.py +150 -0
- siliconcompiler/core.py +267 -289
- siliconcompiler/flowgraph.py +803 -515
- siliconcompiler/fpga.py +84 -0
- siliconcompiler/metric.py +420 -0
- siliconcompiler/optimizer/vizier.py +2 -3
- siliconcompiler/package/__init__.py +29 -6
- siliconcompiler/pdk.py +415 -0
- siliconcompiler/record.py +449 -0
- siliconcompiler/remote/client.py +6 -3
- siliconcompiler/remote/schema.py +116 -112
- siliconcompiler/remote/server.py +3 -5
- siliconcompiler/report/dashboard/cli/__init__.py +13 -722
- siliconcompiler/report/dashboard/cli/board.py +895 -0
- siliconcompiler/report/dashboard/web/__init__.py +10 -10
- siliconcompiler/report/dashboard/web/components/__init__.py +5 -4
- siliconcompiler/report/dashboard/web/components/flowgraph.py +3 -3
- siliconcompiler/report/dashboard/web/components/graph.py +6 -3
- siliconcompiler/report/dashboard/web/state.py +1 -1
- siliconcompiler/report/dashboard/web/utils/__init__.py +4 -3
- siliconcompiler/report/html_report.py +2 -3
- siliconcompiler/report/report.py +13 -7
- siliconcompiler/report/summary_image.py +1 -1
- siliconcompiler/report/summary_table.py +3 -3
- siliconcompiler/report/utils.py +11 -10
- siliconcompiler/scheduler/__init__.py +145 -280
- siliconcompiler/scheduler/run_node.py +2 -1
- siliconcompiler/scheduler/send_messages.py +4 -4
- siliconcompiler/scheduler/slurm.py +2 -2
- siliconcompiler/schema/__init__.py +19 -2
- siliconcompiler/schema/baseschema.py +493 -0
- siliconcompiler/schema/cmdlineschema.py +250 -0
- siliconcompiler/{sphinx_ext → schema/docs}/__init__.py +3 -1
- siliconcompiler/{sphinx_ext → schema/docs}/dynamicgen.py +63 -81
- siliconcompiler/{sphinx_ext → schema/docs}/schemagen.py +73 -85
- siliconcompiler/{sphinx_ext → schema/docs}/utils.py +12 -13
- siliconcompiler/schema/editableschema.py +136 -0
- siliconcompiler/schema/journalingschema.py +238 -0
- siliconcompiler/schema/namedschema.py +41 -0
- siliconcompiler/schema/packageschema.py +101 -0
- siliconcompiler/schema/parameter.py +791 -0
- siliconcompiler/schema/parametertype.py +323 -0
- siliconcompiler/schema/parametervalue.py +736 -0
- siliconcompiler/schema/safeschema.py +37 -0
- siliconcompiler/schema/schema_cfg.py +109 -1789
- siliconcompiler/schema/utils.py +5 -68
- siliconcompiler/schema_obj.py +119 -0
- siliconcompiler/tool.py +1308 -0
- siliconcompiler/tools/_common/__init__.py +6 -10
- siliconcompiler/tools/_common/sdc/sc_constraints.sdc +1 -1
- siliconcompiler/tools/bluespec/convert.py +7 -7
- siliconcompiler/tools/builtin/_common.py +1 -1
- siliconcompiler/tools/builtin/concatenate.py +2 -2
- siliconcompiler/tools/builtin/minimum.py +1 -1
- siliconcompiler/tools/builtin/mux.py +2 -1
- siliconcompiler/tools/builtin/nop.py +1 -1
- siliconcompiler/tools/builtin/verify.py +6 -4
- siliconcompiler/tools/chisel/convert.py +4 -4
- siliconcompiler/tools/genfasm/bitstream.py +3 -3
- siliconcompiler/tools/ghdl/convert.py +1 -1
- siliconcompiler/tools/icarus/compile.py +4 -4
- siliconcompiler/tools/icepack/bitstream.py +6 -1
- siliconcompiler/tools/klayout/convert_drc_db.py +5 -0
- siliconcompiler/tools/klayout/klayout_export.py +0 -1
- siliconcompiler/tools/klayout/klayout_utils.py +3 -10
- siliconcompiler/tools/nextpnr/apr.py +6 -1
- siliconcompiler/tools/nextpnr/nextpnr.py +4 -4
- siliconcompiler/tools/openroad/_apr.py +13 -0
- siliconcompiler/tools/openroad/rdlroute.py +3 -3
- siliconcompiler/tools/openroad/scripts/apr/postamble.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +5 -5
- siliconcompiler/tools/openroad/scripts/apr/sc_antenna_repair.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_clock_tree_synthesis.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_detailed_placement.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_endcap_tapcell_insertion.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_fillercell_insertion.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_fillmetal_insertion.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_global_placement.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_macro_placement.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_metrics.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_pin_placement.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_power_grid.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_repair_timing.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +57 -1
- siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +2 -2
- siliconcompiler/tools/openroad/scripts/common/write_images.tcl +28 -3
- siliconcompiler/tools/openroad/scripts/sc_rcx.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/sc_show.tcl +6 -6
- siliconcompiler/tools/slang/__init__.py +10 -10
- siliconcompiler/tools/surelog/parse.py +4 -4
- siliconcompiler/tools/sv2v/convert.py +20 -3
- siliconcompiler/tools/verilator/compile.py +2 -2
- siliconcompiler/tools/verilator/verilator.py +3 -3
- siliconcompiler/tools/vpr/place.py +1 -1
- siliconcompiler/tools/vpr/route.py +4 -4
- siliconcompiler/tools/vpr/screenshot.py +1 -1
- siliconcompiler/tools/vpr/show.py +5 -5
- siliconcompiler/tools/vpr/vpr.py +24 -24
- siliconcompiler/tools/xdm/convert.py +2 -2
- siliconcompiler/tools/xyce/simulate.py +1 -1
- siliconcompiler/tools/yosys/sc_synth_asic.tcl +74 -68
- siliconcompiler/tools/yosys/syn_asic.py +2 -2
- siliconcompiler/toolscripts/_tools.json +7 -7
- siliconcompiler/toolscripts/ubuntu22/install-vpr.sh +0 -2
- siliconcompiler/toolscripts/ubuntu24/install-vpr.sh +0 -2
- siliconcompiler/utils/__init__.py +8 -112
- siliconcompiler/utils/flowgraph.py +339 -0
- siliconcompiler/{issue.py → utils/issue.py} +4 -3
- siliconcompiler/utils/logging.py +1 -2
- {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/METADATA +9 -8
- {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/RECORD +151 -134
- {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/WHEEL +1 -1
- {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/entry_points.txt +8 -8
- siliconcompiler/schema/schema_obj.py +0 -1936
- siliconcompiler/toolscripts/ubuntu20/install-vpr.sh +0 -29
- siliconcompiler/toolscripts/ubuntu20/install-yosys-parmys.sh +0 -61
- /siliconcompiler/{templates → data/templates}/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/email/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/email/general.j2 +0 -0
- /siliconcompiler/{templates → data/templates}/email/summary.j2 +0 -0
- /siliconcompiler/{templates → data/templates}/issue/README.txt +0 -0
- /siliconcompiler/{templates → data/templates}/issue/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/issue/run.sh +0 -0
- /siliconcompiler/{templates → data/templates}/replay/replay.py.j2 +0 -0
- /siliconcompiler/{templates → data/templates}/replay/replay.sh.j2 +0 -0
- /siliconcompiler/{templates → data/templates}/replay/requirements.txt +0 -0
- /siliconcompiler/{templates → data/templates}/replay/setup.sh +0 -0
- /siliconcompiler/{templates → data/templates}/report/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/report/bootstrap.min.css +0 -0
- /siliconcompiler/{templates → data/templates}/report/bootstrap.min.js +0 -0
- /siliconcompiler/{templates → data/templates}/report/bootstrap_LICENSE.md +0 -0
- /siliconcompiler/{templates → data/templates}/report/sc_report.j2 +0 -0
- /siliconcompiler/{templates → data/templates}/slurm/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/slurm/run.sh +0 -0
- /siliconcompiler/{templates → data/templates}/tcl/__init__.py +0 -0
- /siliconcompiler/{templates → data/templates}/tcl/manifest.tcl.j2 +0 -0
- /siliconcompiler/{units.py → utils/units.py} +0 -0
- {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.0.dist-info}/top_level.txt +0 -0
|
@@ -1,239 +1,18 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import threading
|
|
3
|
-
import logging
|
|
4
|
-
import time
|
|
5
|
-
|
|
6
|
-
from collections import deque
|
|
7
|
-
from dataclasses import dataclass, field
|
|
8
|
-
from typing import List, Dict
|
|
9
|
-
|
|
10
|
-
from rich import box
|
|
11
|
-
from rich.theme import Theme
|
|
12
|
-
from rich.live import Live
|
|
13
|
-
from rich.table import Table
|
|
14
|
-
from rich.progress import Progress, BarColumn, TextColumn, MofNCompleteColumn
|
|
15
|
-
from rich.console import Console
|
|
16
|
-
from rich.console import Group
|
|
17
|
-
from rich.padding import Padding
|
|
18
|
-
|
|
19
|
-
from siliconcompiler import SiliconCompilerError, NodeStatus
|
|
20
1
|
from siliconcompiler.report.dashboard import AbstractDashboard
|
|
21
|
-
from siliconcompiler.
|
|
22
|
-
_get_flowgraph_node_inputs, _get_flowgraph_entry_nodes
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class LogBufferHandler(logging.Handler):
|
|
26
|
-
def __init__(self, n=50, event=None):
|
|
27
|
-
"""
|
|
28
|
-
Initializes the handler.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
n (int): Maximum number of lines to keep.
|
|
32
|
-
event (threading.Event): Optional event to trigger on every log line.
|
|
33
|
-
"""
|
|
34
|
-
super().__init__()
|
|
35
|
-
self.buffer = deque(maxlen=n)
|
|
36
|
-
self.event = event
|
|
37
|
-
self._lock = threading.Lock()
|
|
38
|
-
|
|
39
|
-
def emit(self, record):
|
|
40
|
-
"""
|
|
41
|
-
Processes a log record.
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
record (logging.LogRecord): The log record to process.
|
|
45
|
-
"""
|
|
46
|
-
log_entry = self.format(record)
|
|
47
|
-
with self._lock:
|
|
48
|
-
self.buffer.append(log_entry)
|
|
49
|
-
if self.event:
|
|
50
|
-
self.event.set()
|
|
51
|
-
|
|
52
|
-
def get_lines(self, lines=None):
|
|
53
|
-
"""
|
|
54
|
-
Retrieves the last logged lines.
|
|
55
|
-
|
|
56
|
-
Returns:
|
|
57
|
-
list: A list of the last logged lines.
|
|
58
|
-
"""
|
|
59
|
-
with self._lock:
|
|
60
|
-
buffer_list = list(self.buffer)
|
|
61
|
-
if lines is None or lines > len(buffer_list):
|
|
62
|
-
return buffer_list
|
|
63
|
-
return buffer_list[-lines:]
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@dataclass
|
|
67
|
-
class JobData:
|
|
68
|
-
total: int = 0
|
|
69
|
-
success: int = 0
|
|
70
|
-
error: int = 0
|
|
71
|
-
skipped: int = 0
|
|
72
|
-
finished: int = 0
|
|
73
|
-
jobname: str = ""
|
|
74
|
-
design: str = ""
|
|
75
|
-
runtime: float = 0.0
|
|
76
|
-
nodes: List[dict] = field(default_factory=list)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
@dataclass
|
|
80
|
-
class SessionData:
|
|
81
|
-
total: int = 0
|
|
82
|
-
success: int = 0
|
|
83
|
-
error: int = 0
|
|
84
|
-
skipped: int = 0
|
|
85
|
-
finished: int = 0
|
|
86
|
-
runtime: float = 0.0
|
|
87
|
-
jobs: Dict[str, JobData] = field(default_factory=dict)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@dataclass
|
|
91
|
-
class Layout:
|
|
92
|
-
"""
|
|
93
|
-
Layout class represents the configuration for a dashboard layout.
|
|
94
|
-
|
|
95
|
-
Attributes:
|
|
96
|
-
height (int): The total height of the layout.
|
|
97
|
-
width (int): The total width of the layout.
|
|
98
|
-
job_board_min (int): The minimum height allocated for the job board.
|
|
99
|
-
job_board_max (int): The maximum height allocated for the job board.
|
|
100
|
-
log_max (int): The maximum height allocated for the log section.
|
|
101
|
-
log_min (int): The minimum height allocated for the log section.
|
|
102
|
-
progress_bar_min (int): The minimum height allocated for the progress bar.
|
|
103
|
-
progress_bar_max (int): The maximum height allocated for the progress bar.
|
|
104
|
-
job_board_show_log (bool): A flag indicating whether to show the log in the job board.
|
|
105
|
-
|
|
106
|
-
__reserved (int): Reserved space for table headings and extra padding.
|
|
107
|
-
|
|
108
|
-
Methods:
|
|
109
|
-
available_height():
|
|
110
|
-
Calculates and returns the available height for other components in the layout
|
|
111
|
-
after accounting for reserved space, job board, and log sections.
|
|
112
|
-
Returns 0 if the total height is not set.
|
|
113
|
-
"""
|
|
114
|
-
|
|
115
|
-
height: int = 0
|
|
116
|
-
width: int = 0
|
|
117
|
-
|
|
118
|
-
log_height = 0
|
|
119
|
-
job_board_height = 0
|
|
120
|
-
progress_bar_height = 0
|
|
121
|
-
|
|
122
|
-
job_board_show_log: bool = True
|
|
123
|
-
job_board_v_limit: int = 120
|
|
124
|
-
|
|
125
|
-
__progress_bar_height_default = 1
|
|
126
|
-
padding_log = 2
|
|
127
|
-
padding_progress_bar = 1
|
|
128
|
-
padding_job_board = 1
|
|
129
|
-
padding_job_board_header = 1
|
|
130
|
-
|
|
131
|
-
def update(self, height, width, visible_jobs, visible_bars):
|
|
132
|
-
self.height = height
|
|
133
|
-
self.width = width
|
|
134
|
-
|
|
135
|
-
min_required = (
|
|
136
|
-
max(visible_bars, self.__progress_bar_height_default)
|
|
137
|
-
+ self.padding_progress_bar
|
|
138
|
-
)
|
|
139
|
-
if self.height < min_required:
|
|
140
|
-
self.progress_bar_height = 0
|
|
141
|
-
self.job_board_height = 0
|
|
142
|
-
self.log_height = 0
|
|
143
|
-
return
|
|
144
|
-
|
|
145
|
-
remaining_height = self.height
|
|
146
|
-
|
|
147
|
-
# Allocate progress bar space (highest priority)
|
|
148
|
-
self.progress_bar_height = max(visible_bars, self.__progress_bar_height_default)
|
|
149
|
-
if self.progress_bar_height > 0:
|
|
150
|
-
remaining_height -= self.progress_bar_height + self.padding_progress_bar
|
|
151
|
-
|
|
152
|
-
# Calculate job board requirements
|
|
153
|
-
job_board_min_space = self.padding_job_board_header + self.padding_job_board
|
|
154
|
-
job_board_max_nodes = remaining_height // 2
|
|
155
|
-
visible_jobs = min(visible_jobs, job_board_max_nodes)
|
|
156
|
-
if visible_jobs > 0:
|
|
157
|
-
job_board_full_space = visible_jobs + job_board_min_space
|
|
158
|
-
else:
|
|
159
|
-
job_board_full_space = 0
|
|
160
|
-
|
|
161
|
-
# Allocate job board space (second priority)
|
|
162
|
-
if remaining_height <= job_board_min_space:
|
|
163
|
-
self.job_board_height = 0
|
|
164
|
-
self.log_height = 0
|
|
165
|
-
elif remaining_height <= job_board_full_space:
|
|
166
|
-
self.job_board_height = remaining_height - job_board_min_space
|
|
167
|
-
self.log_height = 0
|
|
168
|
-
elif visible_jobs == 0:
|
|
169
|
-
self.job_board_height = 0
|
|
170
|
-
self.log_height = remaining_height
|
|
171
|
-
else:
|
|
172
|
-
self.job_board_height = visible_jobs
|
|
173
|
-
self.log_height = remaining_height - job_board_full_space - self.padding_log
|
|
174
|
-
|
|
175
|
-
if self.width < self.job_board_v_limit:
|
|
176
|
-
self.job_board_show_log = False
|
|
2
|
+
from siliconcompiler.report.dashboard.cli.board import Board
|
|
177
3
|
|
|
178
4
|
|
|
179
5
|
class CliDashboard(AbstractDashboard):
|
|
180
|
-
__status_color_map = {
|
|
181
|
-
NodeStatus.PENDING: "blue",
|
|
182
|
-
NodeStatus.QUEUED: "blue",
|
|
183
|
-
NodeStatus.RUNNING: "orange4",
|
|
184
|
-
NodeStatus.SUCCESS: "green",
|
|
185
|
-
NodeStatus.ERROR: "red",
|
|
186
|
-
NodeStatus.SKIPPED: "bright_black",
|
|
187
|
-
NodeStatus.TIMEOUT: "red",
|
|
188
|
-
}
|
|
189
|
-
__theme = Theme(
|
|
190
|
-
{
|
|
191
|
-
# Text colors
|
|
192
|
-
"text.primary": "white",
|
|
193
|
-
"text.secondary": "cyan",
|
|
194
|
-
# Background colors
|
|
195
|
-
"background.primary": "grey15",
|
|
196
|
-
"background.secondary": "dark_blue",
|
|
197
|
-
# Highlight and accent colors
|
|
198
|
-
"highlight": "green",
|
|
199
|
-
"accent": " cyan",
|
|
200
|
-
# Status colors
|
|
201
|
-
"error": "red",
|
|
202
|
-
"warning": "yellow",
|
|
203
|
-
"success": "green",
|
|
204
|
-
# Node status colors
|
|
205
|
-
**{f"node.{status}": color for status, color in __status_color_map.items()},
|
|
206
|
-
# Custom style for headers
|
|
207
|
-
"header": "bold underline cyan",
|
|
208
|
-
}
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
__JOB_BOARD_HEADER = True
|
|
212
|
-
|
|
213
|
-
__JOB_BOARD_BOX = box.SIMPLE_HEAD
|
|
214
6
|
|
|
215
7
|
def __init__(self, chip):
|
|
216
8
|
super().__init__(chip)
|
|
217
9
|
|
|
218
|
-
self.
|
|
219
|
-
self._render_stop_event = threading.Event()
|
|
220
|
-
self._render_thread = None
|
|
221
|
-
|
|
222
|
-
self._render_data = SessionData()
|
|
223
|
-
self._render_data_lock = threading.Lock()
|
|
224
|
-
|
|
225
|
-
self._console = Console(theme=CliDashboard.__theme)
|
|
226
|
-
self._layout = Layout()
|
|
10
|
+
self._dashboard = Board()
|
|
227
11
|
|
|
228
12
|
self.__logger_console = None
|
|
229
13
|
|
|
230
|
-
if not self.__JOB_BOARD_HEADER:
|
|
231
|
-
self._layout.padding_job_board_header = 0
|
|
232
|
-
|
|
233
14
|
self._logger = chip.logger
|
|
234
15
|
|
|
235
|
-
self._metrics = ("warnings", "errors")
|
|
236
|
-
|
|
237
16
|
def set_logger(self, logger):
|
|
238
17
|
"""
|
|
239
18
|
Sets the logger for the dashboard.
|
|
@@ -242,27 +21,20 @@ class CliDashboard(AbstractDashboard):
|
|
|
242
21
|
logger (logging.Logger): The logger to set.
|
|
243
22
|
"""
|
|
244
23
|
self._logger = logger
|
|
245
|
-
if self._logger:
|
|
246
|
-
self.__log_handler = LogBufferHandler(n=120, event=self._render_event)
|
|
24
|
+
if self._logger and self._dashboard._active:
|
|
247
25
|
# Hijack the console
|
|
248
26
|
self._logger.removeHandler(self._chip.logger._console)
|
|
249
27
|
self.__logger_console = self._chip.logger._console
|
|
250
|
-
self._chip.logger._console = self.
|
|
251
|
-
self._logger.addHandler(self.
|
|
28
|
+
self._chip.logger._console = self._dashboard._log_handler
|
|
29
|
+
self._logger.addHandler(self._dashboard._log_handler)
|
|
252
30
|
self._chip._init_logger_formats()
|
|
253
31
|
|
|
254
32
|
def open_dashboard(self):
|
|
255
33
|
"""Starts the dashboard rendering thread if it is not already running."""
|
|
256
34
|
|
|
257
|
-
|
|
258
|
-
self.set_logger(self._logger)
|
|
259
|
-
self._update_render_data()
|
|
260
|
-
|
|
261
|
-
self._render_thread = threading.Thread(target=self._render, daemon=True)
|
|
262
|
-
self._render_event.clear()
|
|
263
|
-
self._render_stop_event.clear()
|
|
35
|
+
self.set_logger(self._logger)
|
|
264
36
|
|
|
265
|
-
|
|
37
|
+
self._dashboard.open_dashboard()
|
|
266
38
|
|
|
267
39
|
def update_manifest(self, payload=None):
|
|
268
40
|
"""
|
|
@@ -272,8 +44,7 @@ class CliDashboard(AbstractDashboard):
|
|
|
272
44
|
starttimes = None
|
|
273
45
|
if payload and "starttimes" in payload:
|
|
274
46
|
starttimes = payload["starttimes"]
|
|
275
|
-
self.
|
|
276
|
-
self._render_event.set()
|
|
47
|
+
self._dashboard.update_manifest(self._chip, starttimes=starttimes)
|
|
277
48
|
|
|
278
49
|
def update_graph_manifests(self):
|
|
279
50
|
"""Placeholder method for updating graph manifests. Currently not implemented."""
|
|
@@ -281,32 +52,23 @@ class CliDashboard(AbstractDashboard):
|
|
|
281
52
|
|
|
282
53
|
def is_running(self):
|
|
283
54
|
"""Returns True to indicate that the dashboard is running."""
|
|
284
|
-
|
|
285
|
-
return False
|
|
286
|
-
|
|
287
|
-
return self._render_thread.is_alive()
|
|
55
|
+
return self._dashboard.is_running()
|
|
288
56
|
|
|
289
57
|
def end_of_run(self):
|
|
290
58
|
"""
|
|
291
59
|
Stops the dashboard rendering thread and ensures all rendering operations are completed.
|
|
292
60
|
"""
|
|
293
|
-
self.
|
|
61
|
+
self._dashboard.end_of_run(self._chip)
|
|
294
62
|
|
|
295
63
|
def stop(self):
|
|
296
64
|
"""
|
|
297
65
|
Stops the dashboard rendering thread and ensures all rendering operations are completed.
|
|
298
66
|
"""
|
|
299
|
-
|
|
300
|
-
return
|
|
301
|
-
|
|
302
|
-
self._render_stop_event.set()
|
|
303
|
-
self._render_event.set()
|
|
304
|
-
# Wait for rendering to finish
|
|
305
|
-
self.wait()
|
|
67
|
+
self._dashboard.end_of_run(self._chip)
|
|
306
68
|
|
|
307
69
|
# Restore logger
|
|
308
70
|
if self.__logger_console:
|
|
309
|
-
self._logger.removeHandler(self.
|
|
71
|
+
self._logger.removeHandler(self._dashboard._log_handler)
|
|
310
72
|
self._chip.logger._console = self.__logger_console
|
|
311
73
|
self._logger.addHandler(self.__logger_console)
|
|
312
74
|
self._chip._init_logger_formats()
|
|
@@ -314,475 +76,4 @@ class CliDashboard(AbstractDashboard):
|
|
|
314
76
|
|
|
315
77
|
def wait(self):
|
|
316
78
|
"""Waits for the dashboard rendering thread to finish."""
|
|
317
|
-
|
|
318
|
-
return
|
|
319
|
-
|
|
320
|
-
self._render_thread.join()
|
|
321
|
-
|
|
322
|
-
@staticmethod
|
|
323
|
-
def format_status(status: str):
|
|
324
|
-
"""
|
|
325
|
-
Formats the status of a node for display in the dashboard.
|
|
326
|
-
|
|
327
|
-
Args:
|
|
328
|
-
status (str): The status of the node (e.g., 'running', 'success', 'error').
|
|
329
|
-
|
|
330
|
-
Returns:
|
|
331
|
-
str: A formatted string with the status styled for display.
|
|
332
|
-
"""
|
|
333
|
-
return f"[node.{status.lower()}]{status.upper()}[/]"
|
|
334
|
-
|
|
335
|
-
@staticmethod
|
|
336
|
-
def format_node(design, jobname, step, index) -> str:
|
|
337
|
-
"""
|
|
338
|
-
Formats a node's information for display in the dashboard.
|
|
339
|
-
|
|
340
|
-
Args:
|
|
341
|
-
design (str): The design name.
|
|
342
|
-
jobname (str): The job name.
|
|
343
|
-
step (str): The step name.
|
|
344
|
-
index (int): The step index.
|
|
345
|
-
|
|
346
|
-
Returns:
|
|
347
|
-
str: A formatted string with the node's information styled for display.
|
|
348
|
-
"""
|
|
349
|
-
return f"{design}/{jobname}/{step}/{index}"
|
|
350
|
-
|
|
351
|
-
def _render_log(self, layout):
|
|
352
|
-
if not self._logger or layout.log_height == 0:
|
|
353
|
-
return None
|
|
354
|
-
|
|
355
|
-
table = Table(box=None)
|
|
356
|
-
table.add_column(overflow="ellipsis", no_wrap=True, vertical="bottom")
|
|
357
|
-
table.show_edge = False
|
|
358
|
-
table.show_lines = False
|
|
359
|
-
table.show_footer = False
|
|
360
|
-
table.show_header = False
|
|
361
|
-
for line in self.__log_handler.get_lines(layout.log_height):
|
|
362
|
-
table.add_row(f"[bright_black]{line}[/]")
|
|
363
|
-
while table.row_count < layout.log_height:
|
|
364
|
-
table.add_row("")
|
|
365
|
-
|
|
366
|
-
return Group(table, Padding("", (0, 0)))
|
|
367
|
-
|
|
368
|
-
def _render_job_dashboard(self, layout):
|
|
369
|
-
"""
|
|
370
|
-
Creates a table of jobs and their statuses for display in the dashboard.
|
|
371
|
-
|
|
372
|
-
Returns:
|
|
373
|
-
Group: A Rich Group object containing tables for each job.
|
|
374
|
-
"""
|
|
375
|
-
# Don't render anything if there is not enough space
|
|
376
|
-
if layout.job_board_height == 0:
|
|
377
|
-
return None
|
|
378
|
-
|
|
379
|
-
with self._render_data_lock:
|
|
380
|
-
job_data = self._render_data.jobs.copy() # Access jobs from SessionData
|
|
381
|
-
|
|
382
|
-
if self.__JOB_BOARD_HEADER:
|
|
383
|
-
table_box = self.__JOB_BOARD_BOX
|
|
384
|
-
else:
|
|
385
|
-
table_box = None
|
|
386
|
-
|
|
387
|
-
table = Table(box=table_box, pad_edge=False)
|
|
388
|
-
table.show_edge = False
|
|
389
|
-
table.show_lines = False
|
|
390
|
-
table.show_footer = False
|
|
391
|
-
table.show_header = self.__JOB_BOARD_HEADER
|
|
392
|
-
table.add_column("Status")
|
|
393
|
-
table.add_column("Node")
|
|
394
|
-
table.add_column("Time", justify="right")
|
|
395
|
-
for metric in self._metrics:
|
|
396
|
-
table.add_column(metric.capitalize(), justify="right")
|
|
397
|
-
if layout.job_board_show_log:
|
|
398
|
-
table.add_column("Log")
|
|
399
|
-
|
|
400
|
-
table_data = []
|
|
401
|
-
|
|
402
|
-
for jobname, job in job_data.items():
|
|
403
|
-
if not job.nodes:
|
|
404
|
-
continue
|
|
405
|
-
|
|
406
|
-
for node in job.nodes:
|
|
407
|
-
if (
|
|
408
|
-
layout.job_board_show_log
|
|
409
|
-
and os.path.exists(node["log"])
|
|
410
|
-
):
|
|
411
|
-
log_file = "[bright_black]{}[/]".format(node['log'])
|
|
412
|
-
else:
|
|
413
|
-
log_file = None
|
|
414
|
-
|
|
415
|
-
if node["time"]["duration"] is not None:
|
|
416
|
-
duration = f'{node["time"]["duration"]:.1f}s'
|
|
417
|
-
elif node["time"]["start"] is not None:
|
|
418
|
-
duration = f'{time.time() - node["time"]["start"]:.1f}s'
|
|
419
|
-
else:
|
|
420
|
-
duration = ""
|
|
421
|
-
|
|
422
|
-
table_data.append((node["status"], node["step"], node["index"], (
|
|
423
|
-
CliDashboard.format_status(node["status"]),
|
|
424
|
-
CliDashboard.format_node(
|
|
425
|
-
job.design, job.jobname, node["step"], node["index"]
|
|
426
|
-
),
|
|
427
|
-
duration,
|
|
428
|
-
*node["metrics"],
|
|
429
|
-
log_file
|
|
430
|
-
)))
|
|
431
|
-
|
|
432
|
-
def job_data_fits(table_data):
|
|
433
|
-
return len(table_data) <= layout.job_board_height
|
|
434
|
-
|
|
435
|
-
def remove_pending(table_data, keep_nodes):
|
|
436
|
-
if not keep_nodes:
|
|
437
|
-
keep_nodes = set()
|
|
438
|
-
for n in range(len(table_data)):
|
|
439
|
-
if NodeStatus.is_waiting(table_data[-n][0]):
|
|
440
|
-
if (table_data[-n][1], table_data[-n][2]) not in keep_nodes:
|
|
441
|
-
table_data.pop(-n)
|
|
442
|
-
return table_data
|
|
443
|
-
return table_data
|
|
444
|
-
|
|
445
|
-
def remove_success(table_data, keep_nodes):
|
|
446
|
-
if not keep_nodes:
|
|
447
|
-
keep_nodes = set()
|
|
448
|
-
for n in range(len(table_data)):
|
|
449
|
-
if NodeStatus.is_success(table_data[n][0]):
|
|
450
|
-
if (table_data[n][1], table_data[n][2]) not in keep_nodes:
|
|
451
|
-
table_data.pop(n)
|
|
452
|
-
return table_data
|
|
453
|
-
return table_data
|
|
454
|
-
|
|
455
|
-
def count_table_data(table_data):
|
|
456
|
-
pending = 0
|
|
457
|
-
done = 0
|
|
458
|
-
for status, _, _, _ in table_data:
|
|
459
|
-
if NodeStatus.is_done(status):
|
|
460
|
-
done += 1
|
|
461
|
-
elif NodeStatus.is_waiting(status):
|
|
462
|
-
pending += 1
|
|
463
|
-
return pending, done
|
|
464
|
-
|
|
465
|
-
running_nodes = set([
|
|
466
|
-
(step, index) for status, step, index, _ in table_data
|
|
467
|
-
if NodeStatus.is_running(status)])
|
|
468
|
-
|
|
469
|
-
done_nodes = set([
|
|
470
|
-
(step, index) for status, step, index, _ in table_data
|
|
471
|
-
if NodeStatus.is_done(status)])
|
|
472
|
-
|
|
473
|
-
flow = self._chip.get('option', 'flow')
|
|
474
|
-
entry_nodes = set([node for node in _get_flowgraph_entry_nodes(self._chip, flow)
|
|
475
|
-
if node not in running_nodes and node not in done_nodes])
|
|
476
|
-
|
|
477
|
-
input_nodes = set()
|
|
478
|
-
for step, index in running_nodes:
|
|
479
|
-
input_nodes.update(_get_flowgraph_node_inputs(self._chip, flow, (step, index)))
|
|
480
|
-
input_nodes.update(entry_nodes)
|
|
481
|
-
|
|
482
|
-
output_nodes = set()
|
|
483
|
-
for step, index in nodes_to_execute(self._chip):
|
|
484
|
-
for in_node in self._chip.get('flowgraph', flow, step, index, 'input'):
|
|
485
|
-
if in_node in running_nodes:
|
|
486
|
-
output_nodes.add((step, index))
|
|
487
|
-
output_nodes.update(entry_nodes)
|
|
488
|
-
|
|
489
|
-
# order:
|
|
490
|
-
# loop:
|
|
491
|
-
# remove pending, 1+ removed from running
|
|
492
|
-
# remove success, 1+ removed from running
|
|
493
|
-
# loop:
|
|
494
|
-
# remove pending, any
|
|
495
|
-
# remove success, any
|
|
496
|
-
# trim running to fit
|
|
497
|
-
|
|
498
|
-
while True:
|
|
499
|
-
if job_data_fits(table_data):
|
|
500
|
-
break
|
|
501
|
-
start_len = len(table_data)
|
|
502
|
-
pending, done = count_table_data(table_data)
|
|
503
|
-
if pending >= done:
|
|
504
|
-
table_data = remove_pending(table_data, output_nodes)
|
|
505
|
-
else:
|
|
506
|
-
table_data = remove_success(table_data, input_nodes)
|
|
507
|
-
if len(table_data) == start_len:
|
|
508
|
-
break
|
|
509
|
-
|
|
510
|
-
while True:
|
|
511
|
-
if job_data_fits(table_data):
|
|
512
|
-
break
|
|
513
|
-
start_len = len(table_data)
|
|
514
|
-
pending, done = count_table_data(table_data)
|
|
515
|
-
if pending >= done:
|
|
516
|
-
table_data = remove_pending(table_data, None)
|
|
517
|
-
else:
|
|
518
|
-
table_data = remove_success(table_data, None)
|
|
519
|
-
if len(table_data) == start_len:
|
|
520
|
-
break
|
|
521
|
-
|
|
522
|
-
if not job_data_fits(table_data):
|
|
523
|
-
# trim to fit
|
|
524
|
-
table_data = table_data[0:layout.job_board_height]
|
|
525
|
-
|
|
526
|
-
for _, _, _, row_data in table_data:
|
|
527
|
-
table.add_row(*row_data)
|
|
528
|
-
|
|
529
|
-
if table.row_count == 0:
|
|
530
|
-
return None
|
|
531
|
-
|
|
532
|
-
if self.__JOB_BOARD_HEADER:
|
|
533
|
-
return Group(table, Padding("", (0, 0)))
|
|
534
|
-
return Group(Padding("", (0, 0)), table, Padding("", (0, 0)))
|
|
535
|
-
|
|
536
|
-
def _render_progress_bar(self, layout):
|
|
537
|
-
"""
|
|
538
|
-
Creates progress bars showing job completion for display in the dashboard.
|
|
539
|
-
|
|
540
|
-
Returns:
|
|
541
|
-
Group: A Rich Group object containing progress bars for each job.
|
|
542
|
-
"""
|
|
543
|
-
with self._render_data_lock:
|
|
544
|
-
job_data = self._render_data.jobs.copy()
|
|
545
|
-
done = self._render_data.finished > 0 \
|
|
546
|
-
and self._render_data.total == self._render_data.finished \
|
|
547
|
-
and self._render_data.success == self._render_data.total
|
|
548
|
-
|
|
549
|
-
if done:
|
|
550
|
-
return None
|
|
551
|
-
|
|
552
|
-
progress = Progress(
|
|
553
|
-
TextColumn("[progress.description]{task.description}"),
|
|
554
|
-
MofNCompleteColumn(),
|
|
555
|
-
BarColumn(bar_width=60),
|
|
556
|
-
TextColumn("[progress.percentage]{task.percentage:>3.0f}%")
|
|
557
|
-
)
|
|
558
|
-
nodes = 0
|
|
559
|
-
for _, job in job_data.items():
|
|
560
|
-
nodes += len(job.nodes)
|
|
561
|
-
progress.add_task(
|
|
562
|
-
f"[text.primary]Progress ({job.design}/{job.jobname}):",
|
|
563
|
-
total=job.total,
|
|
564
|
-
completed=job.success,
|
|
565
|
-
)
|
|
566
|
-
|
|
567
|
-
if nodes == 0:
|
|
568
|
-
return Padding("", (0, 0))
|
|
569
|
-
|
|
570
|
-
return Group(progress, Padding("", (0, 0)))
|
|
571
|
-
|
|
572
|
-
def _render_final(self, layout):
|
|
573
|
-
"""
|
|
574
|
-
Creates a summary of the final results, including runtime, passed, and failed jobs.
|
|
575
|
-
|
|
576
|
-
Returns:
|
|
577
|
-
Padding: A Rich Padding object containing the summary text.
|
|
578
|
-
"""
|
|
579
|
-
with self._render_data_lock:
|
|
580
|
-
success = self._render_data.success
|
|
581
|
-
error = self._render_data.error
|
|
582
|
-
total = self._render_data.total
|
|
583
|
-
finished = self._render_data.finished
|
|
584
|
-
runtime = self._render_data.runtime
|
|
585
|
-
|
|
586
|
-
if finished != 0 and finished == total:
|
|
587
|
-
return Padding(
|
|
588
|
-
f"[text.primary]Results {runtime:.2f}s\n"
|
|
589
|
-
f" [success]{success} passed[/]\n"
|
|
590
|
-
f" [error]{error} failed[/]\n"
|
|
591
|
-
)
|
|
592
|
-
|
|
593
|
-
return self._render_log(layout)
|
|
594
|
-
|
|
595
|
-
def _render(self):
|
|
596
|
-
"""
|
|
597
|
-
Main rendering method for the TUI. Continuously updates the dashboard
|
|
598
|
-
with the latest data until the stop event is set.
|
|
599
|
-
"""
|
|
600
|
-
live = None
|
|
601
|
-
try:
|
|
602
|
-
live = Live(
|
|
603
|
-
self._get_rendable(),
|
|
604
|
-
console=self._console,
|
|
605
|
-
screen=False,
|
|
606
|
-
# transient=True,
|
|
607
|
-
auto_refresh=True,
|
|
608
|
-
# refresh_per_second=60,
|
|
609
|
-
)
|
|
610
|
-
live.start()
|
|
611
|
-
|
|
612
|
-
while not self._render_stop_event.is_set():
|
|
613
|
-
if self._render_event.wait(timeout=0.2):
|
|
614
|
-
self._render_event.clear()
|
|
615
|
-
|
|
616
|
-
if self._render_stop_event.is_set():
|
|
617
|
-
break
|
|
618
|
-
|
|
619
|
-
live.update(self._get_rendable(), refresh=True)
|
|
620
|
-
finally:
|
|
621
|
-
try:
|
|
622
|
-
if live:
|
|
623
|
-
live.update(self._get_rendable(), refresh=True)
|
|
624
|
-
live.stop()
|
|
625
|
-
else:
|
|
626
|
-
self._console.print(self._get_rendable())
|
|
627
|
-
finally:
|
|
628
|
-
# Restore the prompt
|
|
629
|
-
print("\033[?25h", end="")
|
|
630
|
-
|
|
631
|
-
def _update_layout(self):
|
|
632
|
-
with self._render_data_lock:
|
|
633
|
-
visible_progress_bars = len(self._render_data.jobs)
|
|
634
|
-
visible_jobs_count = self._render_data.total - self._render_data.skipped
|
|
635
|
-
|
|
636
|
-
self._layout.update(
|
|
637
|
-
self._console.height,
|
|
638
|
-
self._console.width,
|
|
639
|
-
visible_jobs_count,
|
|
640
|
-
visible_progress_bars,
|
|
641
|
-
)
|
|
642
|
-
|
|
643
|
-
return self._layout
|
|
644
|
-
|
|
645
|
-
def _get_rendable(self):
|
|
646
|
-
"""
|
|
647
|
-
Combines all dashboard components (job table, progress bars, final summary)
|
|
648
|
-
into a single renderable group.
|
|
649
|
-
|
|
650
|
-
Returns:
|
|
651
|
-
Group: A Rich Group object containing all dashboard components.
|
|
652
|
-
"""
|
|
653
|
-
|
|
654
|
-
layout = self._update_layout()
|
|
655
|
-
|
|
656
|
-
new_table = self._render_job_dashboard(layout)
|
|
657
|
-
new_bar = self._render_progress_bar(layout)
|
|
658
|
-
footer = self._render_final(layout)
|
|
659
|
-
|
|
660
|
-
items = []
|
|
661
|
-
if new_table:
|
|
662
|
-
items.extend([new_table])
|
|
663
|
-
|
|
664
|
-
if new_bar:
|
|
665
|
-
items.extend([new_bar])
|
|
666
|
-
|
|
667
|
-
if footer:
|
|
668
|
-
items.extend([footer])
|
|
669
|
-
|
|
670
|
-
return Group(*items)
|
|
671
|
-
|
|
672
|
-
def _update_render_data(self, starttimes=None):
|
|
673
|
-
"""
|
|
674
|
-
Updates the render data with the latest job and node information from the chip object.
|
|
675
|
-
This data is used to populate the dashboard.
|
|
676
|
-
"""
|
|
677
|
-
|
|
678
|
-
job_data = self._get_job(starttimes=starttimes)
|
|
679
|
-
|
|
680
|
-
with self._render_data_lock:
|
|
681
|
-
self._render_data.jobs[self._chip] = job_data
|
|
682
|
-
self._render_data.total = sum(
|
|
683
|
-
job.total for job in self._render_data.jobs.values()
|
|
684
|
-
)
|
|
685
|
-
self._render_data.success = sum(
|
|
686
|
-
job.success for job in self._render_data.jobs.values()
|
|
687
|
-
)
|
|
688
|
-
self._render_data.error = sum(
|
|
689
|
-
job.error for job in self._render_data.jobs.values()
|
|
690
|
-
)
|
|
691
|
-
self._render_data.skipped = sum(
|
|
692
|
-
job.skipped for job in self._render_data.jobs.values()
|
|
693
|
-
)
|
|
694
|
-
self._render_data.finished = sum(
|
|
695
|
-
job.finished for job in self._render_data.jobs.values()
|
|
696
|
-
)
|
|
697
|
-
self._render_data.runtime = max(
|
|
698
|
-
job.runtime for job in self._render_data.jobs.values()
|
|
699
|
-
)
|
|
700
|
-
|
|
701
|
-
def _get_job(self, chip=None, starttimes=None) -> JobData:
|
|
702
|
-
chip = chip or self._chip
|
|
703
|
-
|
|
704
|
-
if not starttimes:
|
|
705
|
-
starttimes = {}
|
|
706
|
-
|
|
707
|
-
nodes = []
|
|
708
|
-
try:
|
|
709
|
-
flow = chip.get("option", "flow")
|
|
710
|
-
if not flow:
|
|
711
|
-
raise SiliconCompilerError("dummy error")
|
|
712
|
-
execnodes = nodes_to_execute(chip)
|
|
713
|
-
for nodeset in _get_flowgraph_execution_order(chip, flow):
|
|
714
|
-
for node in nodeset:
|
|
715
|
-
if node not in execnodes:
|
|
716
|
-
continue
|
|
717
|
-
nodes.append(node)
|
|
718
|
-
except SiliconCompilerError:
|
|
719
|
-
pass
|
|
720
|
-
|
|
721
|
-
design = chip.get("design")
|
|
722
|
-
jobname = chip.get("option", "jobname")
|
|
723
|
-
|
|
724
|
-
job_data = JobData()
|
|
725
|
-
job_data.jobname = jobname
|
|
726
|
-
job_data.design = design
|
|
727
|
-
totaltimes = [
|
|
728
|
-
self._chip.get("metric", "totaltime", step=step, index=index) or 0
|
|
729
|
-
for step, index in nodes
|
|
730
|
-
]
|
|
731
|
-
if not totaltimes:
|
|
732
|
-
totaltimes = [0]
|
|
733
|
-
job_data.runtime = max(totaltimes)
|
|
734
|
-
|
|
735
|
-
for step, index in nodes:
|
|
736
|
-
status = self._chip.get("record", "status", step=step, index=index)
|
|
737
|
-
if not status:
|
|
738
|
-
status = NodeStatus.PENDING
|
|
739
|
-
|
|
740
|
-
job_data.total += 1
|
|
741
|
-
if NodeStatus.is_error(status):
|
|
742
|
-
job_data.error += 1
|
|
743
|
-
if NodeStatus.is_success(status):
|
|
744
|
-
job_data.success += 1
|
|
745
|
-
if NodeStatus.is_done(status):
|
|
746
|
-
job_data.finished += 1
|
|
747
|
-
|
|
748
|
-
if status == NodeStatus.SKIPPED:
|
|
749
|
-
job_data.skipped += 1
|
|
750
|
-
continue
|
|
751
|
-
|
|
752
|
-
starttime = None
|
|
753
|
-
duration = None
|
|
754
|
-
if NodeStatus.is_done(status):
|
|
755
|
-
duration = self._chip.get("metric", "tasktime", step=step, index=index)
|
|
756
|
-
if (step, index) in starttimes:
|
|
757
|
-
starttime = starttimes[(step, index)]
|
|
758
|
-
|
|
759
|
-
node_metrics = []
|
|
760
|
-
for metric in self._metrics:
|
|
761
|
-
value = self._chip.get('metric', metric,
|
|
762
|
-
step=step, index=index)
|
|
763
|
-
if value is None:
|
|
764
|
-
node_metrics.append("")
|
|
765
|
-
else:
|
|
766
|
-
node_metrics.append(str(value))
|
|
767
|
-
|
|
768
|
-
job_data.nodes.append(
|
|
769
|
-
{
|
|
770
|
-
"step": step,
|
|
771
|
-
"index": index,
|
|
772
|
-
"status": status,
|
|
773
|
-
"time": {
|
|
774
|
-
"start": starttime,
|
|
775
|
-
"duration": duration
|
|
776
|
-
},
|
|
777
|
-
"metrics": node_metrics,
|
|
778
|
-
"log": os.path.join(
|
|
779
|
-
os.path.relpath(
|
|
780
|
-
self._chip.getworkdir(step=step, index=index),
|
|
781
|
-
self._chip.cwd,
|
|
782
|
-
),
|
|
783
|
-
f"{step}.log",
|
|
784
|
-
),
|
|
785
|
-
}
|
|
786
|
-
)
|
|
787
|
-
|
|
788
|
-
return job_data
|
|
79
|
+
self._dashboard.wait()
|