siliconcompiler 0.32.3__py3-none-any.whl → 0.33.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. siliconcompiler/__init__.py +19 -2
  2. siliconcompiler/_common.py +5 -0
  3. siliconcompiler/_metadata.py +1 -1
  4. siliconcompiler/apps/sc.py +2 -2
  5. siliconcompiler/apps/sc_install.py +10 -3
  6. siliconcompiler/apps/sc_issue.py +1 -1
  7. siliconcompiler/apps/sc_remote.py +10 -5
  8. siliconcompiler/apps/sc_show.py +2 -2
  9. siliconcompiler/apps/utils/replay.py +5 -3
  10. siliconcompiler/asic.py +120 -0
  11. siliconcompiler/checklist.py +150 -0
  12. siliconcompiler/core.py +299 -299
  13. siliconcompiler/flowgraph.py +803 -515
  14. siliconcompiler/fpga.py +84 -0
  15. siliconcompiler/metric.py +479 -0
  16. siliconcompiler/optimizer/vizier.py +2 -3
  17. siliconcompiler/package/__init__.py +29 -6
  18. siliconcompiler/pdk.py +415 -0
  19. siliconcompiler/record.py +453 -0
  20. siliconcompiler/remote/client.py +15 -5
  21. siliconcompiler/remote/schema.py +116 -112
  22. siliconcompiler/remote/server.py +9 -6
  23. siliconcompiler/report/dashboard/cli/__init__.py +14 -721
  24. siliconcompiler/report/dashboard/cli/board.py +899 -0
  25. siliconcompiler/report/dashboard/web/__init__.py +10 -10
  26. siliconcompiler/report/dashboard/web/components/__init__.py +5 -4
  27. siliconcompiler/report/dashboard/web/components/flowgraph.py +3 -3
  28. siliconcompiler/report/dashboard/web/components/graph.py +6 -3
  29. siliconcompiler/report/dashboard/web/state.py +1 -1
  30. siliconcompiler/report/dashboard/web/utils/__init__.py +4 -3
  31. siliconcompiler/report/html_report.py +2 -3
  32. siliconcompiler/report/report.py +22 -11
  33. siliconcompiler/report/summary_image.py +1 -1
  34. siliconcompiler/report/summary_table.py +3 -3
  35. siliconcompiler/report/utils.py +21 -14
  36. siliconcompiler/scheduler/__init__.py +234 -1206
  37. siliconcompiler/scheduler/run_node.py +2 -1
  38. siliconcompiler/scheduler/send_messages.py +11 -5
  39. siliconcompiler/scheduler/slurm.py +11 -44
  40. siliconcompiler/scheduler/taskscheduler.py +320 -0
  41. siliconcompiler/schema/__init__.py +19 -2
  42. siliconcompiler/schema/baseschema.py +493 -0
  43. siliconcompiler/schema/cmdlineschema.py +250 -0
  44. siliconcompiler/{sphinx_ext → schema/docs}/__init__.py +3 -1
  45. siliconcompiler/{sphinx_ext → schema/docs}/dynamicgen.py +63 -81
  46. siliconcompiler/{sphinx_ext → schema/docs}/schemagen.py +73 -85
  47. siliconcompiler/{sphinx_ext → schema/docs}/utils.py +12 -13
  48. siliconcompiler/schema/editableschema.py +136 -0
  49. siliconcompiler/schema/journalingschema.py +238 -0
  50. siliconcompiler/schema/namedschema.py +41 -0
  51. siliconcompiler/schema/packageschema.py +101 -0
  52. siliconcompiler/schema/parameter.py +791 -0
  53. siliconcompiler/schema/parametertype.py +323 -0
  54. siliconcompiler/schema/parametervalue.py +736 -0
  55. siliconcompiler/schema/safeschema.py +37 -0
  56. siliconcompiler/schema/schema_cfg.py +109 -1789
  57. siliconcompiler/schema/utils.py +5 -68
  58. siliconcompiler/schema_obj.py +119 -0
  59. siliconcompiler/tool.py +1416 -0
  60. siliconcompiler/tools/_common/__init__.py +6 -10
  61. siliconcompiler/tools/_common/asic.py +5 -5
  62. siliconcompiler/tools/_common/sdc/sc_constraints.sdc +1 -1
  63. siliconcompiler/tools/bluespec/convert.py +9 -8
  64. siliconcompiler/tools/builtin/_common.py +9 -2
  65. siliconcompiler/tools/builtin/concatenate.py +7 -3
  66. siliconcompiler/tools/builtin/minimum.py +7 -2
  67. siliconcompiler/tools/builtin/mux.py +8 -2
  68. siliconcompiler/tools/builtin/nop.py +7 -2
  69. siliconcompiler/tools/builtin/verify.py +11 -5
  70. siliconcompiler/tools/chisel/convert.py +10 -10
  71. siliconcompiler/tools/genfasm/bitstream.py +3 -3
  72. siliconcompiler/tools/ghdl/convert.py +1 -1
  73. siliconcompiler/tools/icarus/compile.py +4 -4
  74. siliconcompiler/tools/icepack/bitstream.py +6 -1
  75. siliconcompiler/tools/klayout/convert_drc_db.py +5 -0
  76. siliconcompiler/tools/klayout/drc.py +2 -2
  77. siliconcompiler/tools/klayout/klayout_export.py +0 -1
  78. siliconcompiler/tools/klayout/klayout_show.py +6 -6
  79. siliconcompiler/tools/klayout/klayout_utils.py +15 -22
  80. siliconcompiler/tools/netgen/count_lvs.py +2 -2
  81. siliconcompiler/tools/netgen/lvs.py +1 -1
  82. siliconcompiler/tools/nextpnr/apr.py +6 -1
  83. siliconcompiler/tools/nextpnr/nextpnr.py +4 -4
  84. siliconcompiler/tools/openroad/_apr.py +15 -2
  85. siliconcompiler/tools/openroad/rdlroute.py +3 -3
  86. siliconcompiler/tools/openroad/scripts/apr/postamble.tcl +1 -1
  87. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +5 -5
  88. siliconcompiler/tools/openroad/scripts/apr/sc_antenna_repair.tcl +2 -2
  89. siliconcompiler/tools/openroad/scripts/apr/sc_clock_tree_synthesis.tcl +2 -2
  90. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_placement.tcl +2 -2
  91. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +2 -2
  92. siliconcompiler/tools/openroad/scripts/apr/sc_endcap_tapcell_insertion.tcl +2 -2
  93. siliconcompiler/tools/openroad/scripts/apr/sc_fillercell_insertion.tcl +2 -2
  94. siliconcompiler/tools/openroad/scripts/apr/sc_fillmetal_insertion.tcl +2 -2
  95. siliconcompiler/tools/openroad/scripts/apr/sc_global_placement.tcl +2 -2
  96. siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +2 -2
  97. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +3 -9
  98. siliconcompiler/tools/openroad/scripts/apr/sc_macro_placement.tcl +3 -3
  99. siliconcompiler/tools/openroad/scripts/apr/sc_metrics.tcl +2 -2
  100. siliconcompiler/tools/openroad/scripts/apr/sc_pin_placement.tcl +2 -2
  101. siliconcompiler/tools/openroad/scripts/apr/sc_power_grid.tcl +2 -2
  102. siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +2 -2
  103. siliconcompiler/tools/openroad/scripts/apr/sc_repair_timing.tcl +2 -2
  104. siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +2 -2
  105. siliconcompiler/tools/openroad/scripts/common/procs.tcl +75 -1
  106. siliconcompiler/tools/openroad/scripts/common/read_input_files.tcl +1 -7
  107. siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +2 -2
  108. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +28 -3
  109. siliconcompiler/tools/openroad/scripts/sc_rcx.tcl +1 -1
  110. siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -3
  111. siliconcompiler/tools/openroad/scripts/sc_show.tcl +6 -6
  112. siliconcompiler/tools/opensta/scripts/sc_timing.tcl +10 -0
  113. siliconcompiler/tools/opensta/timing.py +11 -0
  114. siliconcompiler/tools/slang/__init__.py +13 -13
  115. siliconcompiler/tools/slang/elaborate.py +6 -6
  116. siliconcompiler/tools/slang/lint.py +1 -3
  117. siliconcompiler/tools/surelog/parse.py +4 -4
  118. siliconcompiler/tools/sv2v/convert.py +20 -3
  119. siliconcompiler/tools/verilator/compile.py +2 -2
  120. siliconcompiler/tools/verilator/verilator.py +3 -3
  121. siliconcompiler/tools/vpr/_xml_constraint.py +8 -8
  122. siliconcompiler/tools/vpr/place.py +1 -1
  123. siliconcompiler/tools/vpr/route.py +4 -4
  124. siliconcompiler/tools/vpr/screenshot.py +1 -1
  125. siliconcompiler/tools/vpr/show.py +5 -5
  126. siliconcompiler/tools/vpr/vpr.py +24 -24
  127. siliconcompiler/tools/xdm/convert.py +2 -2
  128. siliconcompiler/tools/xyce/simulate.py +1 -1
  129. siliconcompiler/tools/yosys/prepareLib.py +2 -2
  130. siliconcompiler/tools/yosys/sc_synth_asic.tcl +111 -63
  131. siliconcompiler/tools/yosys/screenshot.py +1 -1
  132. siliconcompiler/tools/yosys/syn_asic.py +7 -7
  133. siliconcompiler/toolscripts/_tools.json +12 -10
  134. siliconcompiler/toolscripts/rhel8/install-chisel.sh +9 -2
  135. siliconcompiler/toolscripts/rhel8/install-icarus.sh +10 -3
  136. siliconcompiler/toolscripts/rhel8/install-klayout.sh +8 -1
  137. siliconcompiler/toolscripts/rhel8/install-magic.sh +9 -2
  138. siliconcompiler/toolscripts/rhel8/install-montage.sh +1 -1
  139. siliconcompiler/toolscripts/rhel8/install-netgen.sh +9 -2
  140. siliconcompiler/toolscripts/rhel8/install-slang.sh +11 -4
  141. siliconcompiler/toolscripts/rhel8/install-surelog.sh +9 -2
  142. siliconcompiler/toolscripts/rhel8/install-sv2v.sh +11 -4
  143. siliconcompiler/toolscripts/rhel8/install-verible.sh +11 -3
  144. siliconcompiler/toolscripts/rhel8/install-verilator.sh +10 -3
  145. siliconcompiler/toolscripts/rhel8/install-xyce.sh +15 -10
  146. siliconcompiler/toolscripts/rhel9/install-chisel.sh +9 -2
  147. siliconcompiler/toolscripts/rhel9/install-ghdl.sh +9 -2
  148. siliconcompiler/toolscripts/rhel9/install-gtkwave.sh +10 -3
  149. siliconcompiler/toolscripts/rhel9/install-icarus.sh +10 -3
  150. siliconcompiler/toolscripts/rhel9/install-klayout.sh +8 -1
  151. siliconcompiler/toolscripts/rhel9/install-magic.sh +9 -2
  152. siliconcompiler/toolscripts/rhel9/install-montage.sh +1 -1
  153. siliconcompiler/toolscripts/rhel9/install-netgen.sh +9 -2
  154. siliconcompiler/toolscripts/rhel9/install-openroad.sh +16 -3
  155. siliconcompiler/toolscripts/rhel9/install-opensta.sh +17 -5
  156. siliconcompiler/toolscripts/rhel9/install-slang.sh +11 -4
  157. siliconcompiler/toolscripts/rhel9/install-surelog.sh +9 -2
  158. siliconcompiler/toolscripts/rhel9/install-sv2v.sh +11 -4
  159. siliconcompiler/toolscripts/rhel9/install-verible.sh +11 -3
  160. siliconcompiler/toolscripts/rhel9/install-verilator.sh +10 -3
  161. siliconcompiler/toolscripts/rhel9/install-vpr.sh +9 -2
  162. siliconcompiler/toolscripts/rhel9/install-xdm.sh +10 -2
  163. siliconcompiler/toolscripts/rhel9/install-xyce.sh +15 -10
  164. siliconcompiler/toolscripts/rhel9/install-yosys-moosic.sh +9 -2
  165. siliconcompiler/toolscripts/rhel9/install-yosys-parmys.sh +10 -3
  166. siliconcompiler/toolscripts/rhel9/install-yosys-slang.sh +10 -2
  167. siliconcompiler/toolscripts/rhel9/install-yosys.sh +9 -2
  168. siliconcompiler/toolscripts/ubuntu20/install-bambu.sh +10 -2
  169. siliconcompiler/toolscripts/ubuntu20/install-bluespec.sh +10 -3
  170. siliconcompiler/toolscripts/ubuntu20/install-chisel.sh +9 -2
  171. siliconcompiler/toolscripts/ubuntu20/install-ghdl.sh +9 -2
  172. siliconcompiler/toolscripts/ubuntu20/install-gtkwave.sh +9 -2
  173. siliconcompiler/toolscripts/ubuntu20/install-icarus.sh +9 -2
  174. siliconcompiler/toolscripts/ubuntu20/install-icepack.sh +9 -2
  175. siliconcompiler/toolscripts/ubuntu20/install-klayout.sh +8 -1
  176. siliconcompiler/toolscripts/ubuntu20/install-magic.sh +9 -2
  177. siliconcompiler/toolscripts/ubuntu20/install-montage.sh +1 -1
  178. siliconcompiler/toolscripts/ubuntu20/install-netgen.sh +9 -2
  179. siliconcompiler/toolscripts/ubuntu20/install-nextpnr.sh +9 -2
  180. siliconcompiler/toolscripts/ubuntu20/install-openroad.sh +16 -3
  181. siliconcompiler/toolscripts/ubuntu20/install-opensta.sh +16 -5
  182. siliconcompiler/toolscripts/ubuntu20/install-slang.sh +11 -4
  183. siliconcompiler/toolscripts/ubuntu20/install-slurm.sh +9 -2
  184. siliconcompiler/toolscripts/ubuntu20/install-surelog.sh +10 -2
  185. siliconcompiler/toolscripts/ubuntu20/install-sv2v.sh +11 -4
  186. siliconcompiler/toolscripts/ubuntu20/install-verible.sh +11 -3
  187. siliconcompiler/toolscripts/ubuntu20/install-verilator.sh +9 -2
  188. siliconcompiler/toolscripts/ubuntu20/install-xdm.sh +10 -2
  189. siliconcompiler/toolscripts/ubuntu20/install-xyce.sh +13 -8
  190. siliconcompiler/toolscripts/ubuntu20/install-yosys-moosic.sh +9 -2
  191. siliconcompiler/toolscripts/ubuntu20/install-yosys.sh +9 -2
  192. siliconcompiler/toolscripts/ubuntu22/install-bambu.sh +10 -2
  193. siliconcompiler/toolscripts/ubuntu22/install-bluespec.sh +10 -3
  194. siliconcompiler/toolscripts/ubuntu22/install-chisel.sh +9 -2
  195. siliconcompiler/toolscripts/ubuntu22/install-ghdl.sh +9 -2
  196. siliconcompiler/toolscripts/ubuntu22/install-gtkwave.sh +9 -2
  197. siliconcompiler/toolscripts/ubuntu22/install-icarus.sh +9 -2
  198. siliconcompiler/toolscripts/ubuntu22/install-icepack.sh +9 -2
  199. siliconcompiler/toolscripts/ubuntu22/install-klayout.sh +8 -1
  200. siliconcompiler/toolscripts/ubuntu22/install-magic.sh +9 -2
  201. siliconcompiler/toolscripts/ubuntu22/install-montage.sh +1 -1
  202. siliconcompiler/toolscripts/ubuntu22/install-netgen.sh +9 -2
  203. siliconcompiler/toolscripts/ubuntu22/install-nextpnr.sh +9 -2
  204. siliconcompiler/toolscripts/ubuntu22/install-openroad.sh +16 -3
  205. siliconcompiler/toolscripts/ubuntu22/install-opensta.sh +17 -5
  206. siliconcompiler/toolscripts/ubuntu22/install-slang.sh +11 -4
  207. siliconcompiler/toolscripts/ubuntu22/install-slurm.sh +9 -2
  208. siliconcompiler/toolscripts/ubuntu22/install-surelog.sh +10 -2
  209. siliconcompiler/toolscripts/ubuntu22/install-sv2v.sh +11 -4
  210. siliconcompiler/toolscripts/ubuntu22/install-verible.sh +11 -3
  211. siliconcompiler/toolscripts/ubuntu22/install-verilator.sh +9 -2
  212. siliconcompiler/toolscripts/ubuntu22/install-vpr.sh +9 -4
  213. siliconcompiler/toolscripts/ubuntu22/install-xdm.sh +10 -2
  214. siliconcompiler/toolscripts/ubuntu22/install-xyce.sh +13 -8
  215. siliconcompiler/toolscripts/ubuntu22/install-yosys-moosic.sh +9 -2
  216. siliconcompiler/toolscripts/ubuntu22/install-yosys-parmys.sh +10 -3
  217. siliconcompiler/toolscripts/ubuntu22/install-yosys-slang.sh +10 -2
  218. siliconcompiler/toolscripts/ubuntu22/install-yosys.sh +9 -2
  219. siliconcompiler/toolscripts/ubuntu24/install-bambu.sh +12 -4
  220. siliconcompiler/toolscripts/ubuntu24/install-bluespec.sh +10 -3
  221. siliconcompiler/toolscripts/ubuntu24/install-chisel.sh +9 -2
  222. siliconcompiler/toolscripts/ubuntu24/install-ghdl.sh +9 -2
  223. siliconcompiler/toolscripts/ubuntu24/install-gtkwave.sh +9 -2
  224. siliconcompiler/toolscripts/ubuntu24/install-icarus.sh +9 -2
  225. siliconcompiler/toolscripts/ubuntu24/install-icepack.sh +9 -2
  226. siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +8 -1
  227. siliconcompiler/toolscripts/ubuntu24/install-magic.sh +9 -2
  228. siliconcompiler/toolscripts/ubuntu24/install-montage.sh +1 -1
  229. siliconcompiler/toolscripts/ubuntu24/install-netgen.sh +9 -2
  230. siliconcompiler/toolscripts/ubuntu24/install-nextpnr.sh +9 -2
  231. siliconcompiler/toolscripts/ubuntu24/install-openroad.sh +16 -3
  232. siliconcompiler/toolscripts/ubuntu24/install-opensta.sh +17 -5
  233. siliconcompiler/toolscripts/ubuntu24/install-slang.sh +11 -4
  234. siliconcompiler/toolscripts/ubuntu24/install-slurm.sh +9 -2
  235. siliconcompiler/toolscripts/ubuntu24/install-surelog.sh +10 -2
  236. siliconcompiler/toolscripts/ubuntu24/install-sv2v.sh +11 -4
  237. siliconcompiler/toolscripts/ubuntu24/install-verible.sh +11 -3
  238. siliconcompiler/toolscripts/ubuntu24/install-verilator.sh +9 -2
  239. siliconcompiler/toolscripts/ubuntu24/install-vpr.sh +9 -4
  240. siliconcompiler/toolscripts/ubuntu24/install-xdm.sh +10 -2
  241. siliconcompiler/toolscripts/ubuntu24/install-xyce.sh +13 -8
  242. siliconcompiler/toolscripts/ubuntu24/install-yosys-moosic.sh +9 -2
  243. siliconcompiler/toolscripts/ubuntu24/install-yosys-parmys.sh +10 -3
  244. siliconcompiler/toolscripts/ubuntu24/install-yosys-slang.sh +10 -2
  245. siliconcompiler/toolscripts/ubuntu24/install-yosys.sh +9 -2
  246. siliconcompiler/utils/__init__.py +19 -112
  247. siliconcompiler/utils/flowgraph.py +244 -0
  248. siliconcompiler/{issue.py → utils/issue.py} +18 -25
  249. siliconcompiler/utils/logging.py +3 -4
  250. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/METADATA +9 -8
  251. siliconcompiler-0.33.1.dist-info/RECORD +488 -0
  252. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/WHEEL +1 -1
  253. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/entry_points.txt +8 -8
  254. siliconcompiler/schema/schema_obj.py +0 -1936
  255. siliconcompiler/toolscripts/ubuntu20/install-vpr.sh +0 -29
  256. siliconcompiler/toolscripts/ubuntu20/install-yosys-parmys.sh +0 -61
  257. siliconcompiler-0.32.3.dist-info/RECORD +0 -470
  258. /siliconcompiler/{templates → data/templates}/__init__.py +0 -0
  259. /siliconcompiler/{templates → data/templates}/email/__init__.py +0 -0
  260. /siliconcompiler/{templates → data/templates}/email/general.j2 +0 -0
  261. /siliconcompiler/{templates → data/templates}/email/summary.j2 +0 -0
  262. /siliconcompiler/{templates → data/templates}/issue/README.txt +0 -0
  263. /siliconcompiler/{templates → data/templates}/issue/__init__.py +0 -0
  264. /siliconcompiler/{templates → data/templates}/issue/run.sh +0 -0
  265. /siliconcompiler/{templates → data/templates}/replay/replay.py.j2 +0 -0
  266. /siliconcompiler/{templates → data/templates}/replay/replay.sh.j2 +0 -0
  267. /siliconcompiler/{templates → data/templates}/replay/requirements.txt +0 -0
  268. /siliconcompiler/{templates → data/templates}/replay/setup.sh +0 -0
  269. /siliconcompiler/{templates → data/templates}/report/__init__.py +0 -0
  270. /siliconcompiler/{templates → data/templates}/report/bootstrap.min.css +0 -0
  271. /siliconcompiler/{templates → data/templates}/report/bootstrap.min.js +0 -0
  272. /siliconcompiler/{templates → data/templates}/report/bootstrap_LICENSE.md +0 -0
  273. /siliconcompiler/{templates → data/templates}/report/sc_report.j2 +0 -0
  274. /siliconcompiler/{templates → data/templates}/slurm/__init__.py +0 -0
  275. /siliconcompiler/{templates → data/templates}/slurm/run.sh +0 -0
  276. /siliconcompiler/{templates → data/templates}/tcl/__init__.py +0 -0
  277. /siliconcompiler/{templates → data/templates}/tcl/manifest.tcl.j2 +0 -0
  278. /siliconcompiler/{units.py → utils/units.py} +0 -0
  279. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/licenses/LICENSE +0 -0
  280. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.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.flowgraph import nodes_to_execute, _get_flowgraph_execution_order, \
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._render_event = threading.Event()
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.__log_handler
251
- self._logger.addHandler(self.__log_handler)
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
- if not self.is_running():
258
- self.set_logger(self._logger)
259
- self._update_render_data()
35
+ self.set_logger(self._logger)
260
36
 
261
- self._render_thread = threading.Thread(target=self._render, daemon=True)
262
- self._render_event.clear()
263
- self._render_stop_event.clear()
264
-
265
- self._render_thread.start()
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._update_render_data(starttimes=starttimes)
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,25 @@ class CliDashboard(AbstractDashboard):
281
52
 
282
53
  def is_running(self):
283
54
  """Returns True to indicate that the dashboard is running."""
284
- if not self._render_thread:
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.stop()
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
- if not self.is_running():
300
- return
67
+ self._dashboard.end_of_run(self._chip)
301
68
 
302
- self._render_stop_event.set()
303
- self._render_event.set()
304
- # Wait for rendering to finish
305
- self.wait()
69
+ self._dashboard.stop()
306
70
 
307
71
  # Restore logger
308
72
  if self.__logger_console:
309
- self._logger.removeHandler(self.__log_handler)
73
+ self._logger.removeHandler(self._dashboard._log_handler)
310
74
  self._chip.logger._console = self.__logger_console
311
75
  self._logger.addHandler(self.__logger_console)
312
76
  self._chip._init_logger_formats()
@@ -314,475 +78,4 @@ class CliDashboard(AbstractDashboard):
314
78
 
315
79
  def wait(self):
316
80
  """Waits for the dashboard rendering thread to finish."""
317
- if not self.is_running():
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
81
+ self._dashboard.wait()