siliconcompiler 0.32.2__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 +3 -2
- 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 +319 -345
- siliconcompiler/{templates → data/templates}/replay/replay.sh.j2 +2 -2
- 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 +61 -19
- siliconcompiler/remote/schema.py +116 -112
- siliconcompiler/remote/server.py +3 -5
- siliconcompiler/report/__init__.py +3 -2
- siliconcompiler/report/dashboard/__init__.py +61 -170
- siliconcompiler/report/dashboard/cli/__init__.py +79 -0
- siliconcompiler/report/dashboard/cli/board.py +895 -0
- siliconcompiler/report/dashboard/web/__init__.py +196 -0
- siliconcompiler/report/dashboard/{components → web/components}/__init__.py +9 -8
- siliconcompiler/report/dashboard/{components → web/components}/flowgraph.py +3 -3
- siliconcompiler/report/dashboard/{components → web/components}/graph.py +7 -4
- siliconcompiler/report/dashboard/{layouts → web/layouts}/__init__.py +3 -3
- siliconcompiler/report/dashboard/{layouts → web/layouts}/_common.py +1 -1
- siliconcompiler/report/dashboard/{layouts → web/layouts}/vertical_flowgraph.py +5 -5
- siliconcompiler/report/dashboard/{layouts → web/layouts}/vertical_flowgraph_node_tab.py +6 -6
- siliconcompiler/report/dashboard/{layouts → web/layouts}/vertical_flowgraph_sac_tabs.py +6 -6
- siliconcompiler/report/dashboard/{state.py → web/state.py} +1 -1
- siliconcompiler/report/dashboard/{utils → web/utils}/__init__.py +4 -3
- siliconcompiler/report/dashboard/{viewer.py → web/viewer.py} +4 -4
- 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 +153 -286
- 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 +8 -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 +17 -0
- siliconcompiler/tools/openroad/fillmetal_insertion.py +14 -14
- 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 +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_clock_tree_synthesis.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_detailed_placement.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_endcap_tapcell_insertion.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_fillercell_insertion.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_fillmetal_insertion.tcl +4 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_global_placement.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_macro_placement.tcl +4 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_metrics.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_pin_placement.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_power_grid.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/apr/sc_repair_timing.tcl +4 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +3 -3
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +58 -2
- siliconcompiler/tools/openroad/scripts/common/reports.tcl +2 -2
- 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 +2 -2
- siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +4 -4
- siliconcompiler/tools/openroad/scripts/sc_show.tcl +7 -7
- siliconcompiler/tools/opensta/__init__.py +1 -1
- siliconcompiler/tools/opensta/scripts/sc_check_library.tcl +1 -1
- siliconcompiler/tools/opensta/scripts/sc_procs.tcl +16 -0
- siliconcompiler/tools/opensta/scripts/sc_report_libraries.tcl +1 -1
- siliconcompiler/tools/opensta/scripts/sc_timing.tcl +35 -7
- siliconcompiler/tools/opensta/timing.py +6 -2
- 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 +104 -90
- siliconcompiler/tools/yosys/syn_asic.py +13 -4
- siliconcompiler/toolscripts/_tools.json +12 -7
- siliconcompiler/toolscripts/rhel8/install-chisel.sh +2 -0
- siliconcompiler/toolscripts/rhel8/install-icarus.sh +1 -0
- siliconcompiler/toolscripts/rhel8/install-klayout.sh +2 -0
- siliconcompiler/toolscripts/rhel8/install-magic.sh +1 -2
- siliconcompiler/toolscripts/rhel8/install-netgen.sh +1 -1
- siliconcompiler/toolscripts/rhel8/install-slang.sh +2 -0
- siliconcompiler/toolscripts/rhel8/install-surelog.sh +3 -1
- siliconcompiler/toolscripts/rhel8/install-sv2v.sh +1 -0
- siliconcompiler/toolscripts/rhel8/install-verible.sh +2 -0
- siliconcompiler/toolscripts/rhel8/install-verilator.sh +1 -0
- siliconcompiler/toolscripts/rhel8/install-xyce.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-chisel.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-ghdl.sh +1 -0
- siliconcompiler/toolscripts/rhel9/install-gtkwave.sh +1 -0
- siliconcompiler/toolscripts/rhel9/install-icarus.sh +1 -0
- siliconcompiler/toolscripts/rhel9/install-klayout.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-magic.sh +1 -2
- siliconcompiler/toolscripts/rhel9/install-netgen.sh +1 -1
- siliconcompiler/toolscripts/rhel9/install-openroad.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-opensta.sh +76 -0
- siliconcompiler/toolscripts/rhel9/install-slang.sh +3 -1
- siliconcompiler/toolscripts/rhel9/install-surelog.sh +2 -1
- siliconcompiler/toolscripts/rhel9/install-sv2v.sh +1 -0
- siliconcompiler/toolscripts/rhel9/install-verible.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-verilator.sh +1 -0
- siliconcompiler/toolscripts/rhel9/install-vpr.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-xdm.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-xyce.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-yosys-moosic.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-yosys-parmys.sh +2 -0
- siliconcompiler/toolscripts/rhel9/install-yosys-slang.sh +3 -1
- siliconcompiler/toolscripts/rhel9/install-yosys.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-bambu.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-bluespec.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-chisel.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-ghdl.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-gtkwave.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-icarus.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-icepack.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-klayout.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-magic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-netgen.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-nextpnr.sh +1 -3
- siliconcompiler/toolscripts/ubuntu20/install-openroad.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-opensta.sh +72 -0
- siliconcompiler/toolscripts/ubuntu20/install-slang.sh +3 -1
- siliconcompiler/toolscripts/ubuntu20/install-slurm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-surelog.sh +3 -1
- siliconcompiler/toolscripts/ubuntu20/install-sv2v.sh +1 -1
- siliconcompiler/toolscripts/ubuntu20/install-verible.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-verilator.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-xdm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-xyce.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-yosys-moosic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu20/install-yosys.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-bambu.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-bluespec.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-chisel.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-ghdl.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-gtkwave.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-icarus.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-icepack.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-klayout.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-magic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-netgen.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-nextpnr.sh +1 -2
- siliconcompiler/toolscripts/ubuntu22/install-openroad.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-opensta.sh +72 -0
- siliconcompiler/toolscripts/ubuntu22/install-slang.sh +3 -1
- siliconcompiler/toolscripts/ubuntu22/install-slurm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-surelog.sh +3 -1
- siliconcompiler/toolscripts/ubuntu22/install-sv2v.sh +1 -1
- siliconcompiler/toolscripts/ubuntu22/install-verible.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-verilator.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-vpr.sh +2 -2
- siliconcompiler/toolscripts/ubuntu22/install-xdm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-xyce.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-yosys-moosic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-yosys-parmys.sh +2 -0
- siliconcompiler/toolscripts/ubuntu22/install-yosys-slang.sh +3 -1
- siliconcompiler/toolscripts/ubuntu22/install-yosys.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-bambu.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-bluespec.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-chisel.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-ghdl.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-gtkwave.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-icarus.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-icepack.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-magic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-netgen.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-nextpnr.sh +1 -3
- siliconcompiler/toolscripts/ubuntu24/install-openroad.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-opensta.sh +72 -0
- siliconcompiler/toolscripts/ubuntu24/install-slang.sh +3 -1
- siliconcompiler/toolscripts/ubuntu24/install-slurm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-surelog.sh +3 -1
- siliconcompiler/toolscripts/ubuntu24/install-sv2v.sh +1 -1
- siliconcompiler/toolscripts/ubuntu24/install-verible.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-verilator.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-vpr.sh +2 -2
- siliconcompiler/toolscripts/ubuntu24/install-xdm.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-xyce.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-yosys-moosic.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-yosys-parmys.sh +2 -0
- siliconcompiler/toolscripts/ubuntu24/install-yosys-slang.sh +3 -1
- siliconcompiler/toolscripts/ubuntu24/install-yosys.sh +2 -0
- siliconcompiler/utils/__init__.py +8 -112
- siliconcompiler/utils/flowgraph.py +339 -0
- siliconcompiler/{issue.py → utils/issue.py} +7 -4
- siliconcompiler/utils/logging.py +86 -33
- {siliconcompiler-0.32.2.dist-info → siliconcompiler-0.33.0.dist-info}/METADATA +10 -8
- siliconcompiler-0.33.0.dist-info/RECORD +487 -0
- {siliconcompiler-0.32.2.dist-info → siliconcompiler-0.33.0.dist-info}/WHEEL +1 -1
- {siliconcompiler-0.32.2.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 -27
- siliconcompiler/toolscripts/ubuntu20/install-yosys-parmys.sh +0 -59
- siliconcompiler-0.32.2.dist-info/RECORD +0 -464
- /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/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/report/dashboard/{utils → web/utils}/file_utils.py +0 -0
- /siliconcompiler/{units.py → utils/units.py} +0 -0
- {siliconcompiler-0.32.2.dist-info → siliconcompiler-0.33.0.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.32.2.dist-info → siliconcompiler-0.33.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,895 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import threading
|
|
3
|
+
import logging
|
|
4
|
+
import queue
|
|
5
|
+
import time
|
|
6
|
+
import multiprocessing
|
|
7
|
+
|
|
8
|
+
from collections import deque
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import List, Dict
|
|
11
|
+
|
|
12
|
+
from rich import box
|
|
13
|
+
from rich.theme import Theme
|
|
14
|
+
from rich.live import Live
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
from rich.progress import Progress, BarColumn, TextColumn, MofNCompleteColumn
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
from rich.console import Group
|
|
19
|
+
from rich.padding import Padding
|
|
20
|
+
|
|
21
|
+
from siliconcompiler import SiliconCompilerError, NodeStatus
|
|
22
|
+
from siliconcompiler.utils.logging import SCColorLoggerFormatter
|
|
23
|
+
from siliconcompiler.flowgraph import RuntimeFlowgraph
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LogBufferHandler(logging.Handler):
|
|
27
|
+
def __init__(self, sync_queue, n=50, event=None):
|
|
28
|
+
"""
|
|
29
|
+
Initializes the handler.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
n (int): Maximum number of lines to keep.
|
|
33
|
+
event (threading.Event): Optional event to trigger on every log line.
|
|
34
|
+
"""
|
|
35
|
+
super().__init__()
|
|
36
|
+
self.queue = sync_queue
|
|
37
|
+
self.buffer = deque(maxlen=n)
|
|
38
|
+
self.event = event
|
|
39
|
+
self._lock = threading.Lock()
|
|
40
|
+
|
|
41
|
+
def emit(self, record):
|
|
42
|
+
"""
|
|
43
|
+
Processes a log record.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
record (logging.LogRecord): The log record to process.
|
|
47
|
+
"""
|
|
48
|
+
log_entry = self.format(record)
|
|
49
|
+
log_entry = log_entry.replace("[", "\\[")
|
|
50
|
+
|
|
51
|
+
# Replace console coloring
|
|
52
|
+
for color, replacement in (
|
|
53
|
+
(SCColorLoggerFormatter.reset.replace("[", "\\["), "[/]"),
|
|
54
|
+
(SCColorLoggerFormatter.blue.replace("[", "\\["), "[blue]"),
|
|
55
|
+
(SCColorLoggerFormatter.yellow.replace("[", "\\["), "[yellow]"),
|
|
56
|
+
(SCColorLoggerFormatter.red.replace("[", "\\["), "[red]"),
|
|
57
|
+
(SCColorLoggerFormatter.bold_red.replace("[", "\\["), "[bold red]")):
|
|
58
|
+
log_entry = log_entry.replace(color, replacement)
|
|
59
|
+
self.queue.put(log_entry)
|
|
60
|
+
if self.event:
|
|
61
|
+
self.event.set()
|
|
62
|
+
|
|
63
|
+
def get_lines(self, lines=None):
|
|
64
|
+
"""
|
|
65
|
+
Retrieves the last logged lines.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
list: A list of the last logged lines.
|
|
69
|
+
"""
|
|
70
|
+
with self._lock:
|
|
71
|
+
while not self.queue.empty():
|
|
72
|
+
try:
|
|
73
|
+
self.buffer.append(self.queue.get_nowait())
|
|
74
|
+
except queue.Empty:
|
|
75
|
+
break
|
|
76
|
+
buffer_list = list(self.buffer)
|
|
77
|
+
if lines is None or lines > len(buffer_list):
|
|
78
|
+
return buffer_list
|
|
79
|
+
return buffer_list[-lines:]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class JobData:
|
|
84
|
+
total: int = 0
|
|
85
|
+
success: int = 0
|
|
86
|
+
error: int = 0
|
|
87
|
+
skipped: int = 0
|
|
88
|
+
finished: int = 0
|
|
89
|
+
jobname: str = ""
|
|
90
|
+
design: str = ""
|
|
91
|
+
runtime: float = 0.0
|
|
92
|
+
complete: bool = False
|
|
93
|
+
nodes: List[dict] = field(default_factory=list)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class SessionData:
|
|
98
|
+
total: int = 0
|
|
99
|
+
success: int = 0
|
|
100
|
+
error: int = 0
|
|
101
|
+
skipped: int = 0
|
|
102
|
+
finished: int = 0
|
|
103
|
+
runtime: float = 0.0
|
|
104
|
+
jobs: Dict[str, JobData] = field(default_factory=dict)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class Layout:
|
|
109
|
+
"""
|
|
110
|
+
Layout class represents the configuration for a dashboard layout.
|
|
111
|
+
|
|
112
|
+
Attributes:
|
|
113
|
+
height (int): The total height of the layout.
|
|
114
|
+
width (int): The total width of the layout.
|
|
115
|
+
job_board_min (int): The minimum height allocated for the job board.
|
|
116
|
+
job_board_max (int): The maximum height allocated for the job board.
|
|
117
|
+
log_max (int): The maximum height allocated for the log section.
|
|
118
|
+
log_min (int): The minimum height allocated for the log section.
|
|
119
|
+
progress_bar_min (int): The minimum height allocated for the progress bar.
|
|
120
|
+
progress_bar_max (int): The maximum height allocated for the progress bar.
|
|
121
|
+
job_board_show_log (bool): A flag indicating whether to show the log in the job board.
|
|
122
|
+
|
|
123
|
+
__reserved (int): Reserved space for table headings and extra padding.
|
|
124
|
+
|
|
125
|
+
Methods:
|
|
126
|
+
available_height():
|
|
127
|
+
Calculates and returns the available height for other components in the layout
|
|
128
|
+
after accounting for reserved space, job board, and log sections.
|
|
129
|
+
Returns 0 if the total height is not set.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
height: int = 0
|
|
133
|
+
width: int = 0
|
|
134
|
+
|
|
135
|
+
log_height = 0
|
|
136
|
+
job_board_height = 0
|
|
137
|
+
progress_bar_height = 0
|
|
138
|
+
|
|
139
|
+
job_board_show_log: bool = True
|
|
140
|
+
job_board_v_limit: int = 120
|
|
141
|
+
|
|
142
|
+
__progress_bar_height_default = 1
|
|
143
|
+
padding_log = 2
|
|
144
|
+
padding_progress_bar = 1
|
|
145
|
+
padding_job_board = 1
|
|
146
|
+
padding_job_board_header = 1
|
|
147
|
+
|
|
148
|
+
def update(self, height, width, visible_jobs, visible_bars):
|
|
149
|
+
self.height = height
|
|
150
|
+
self.width = width
|
|
151
|
+
|
|
152
|
+
min_required = (
|
|
153
|
+
max(visible_bars, self.__progress_bar_height_default)
|
|
154
|
+
+ self.padding_progress_bar
|
|
155
|
+
)
|
|
156
|
+
if self.height < min_required:
|
|
157
|
+
self.progress_bar_height = 0
|
|
158
|
+
self.job_board_height = 0
|
|
159
|
+
self.log_height = 0
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
remaining_height = self.height
|
|
163
|
+
|
|
164
|
+
# Allocate progress bar space (highest priority)
|
|
165
|
+
self.progress_bar_height = max(visible_bars, self.__progress_bar_height_default)
|
|
166
|
+
if self.progress_bar_height > 0:
|
|
167
|
+
remaining_height -= self.progress_bar_height + self.padding_progress_bar
|
|
168
|
+
|
|
169
|
+
# Calculate job board requirements
|
|
170
|
+
job_board_min_space = self.padding_job_board_header + self.padding_job_board
|
|
171
|
+
job_board_max_nodes = remaining_height // 2
|
|
172
|
+
visible_jobs = min(visible_jobs, job_board_max_nodes)
|
|
173
|
+
if visible_jobs > 0:
|
|
174
|
+
job_board_full_space = visible_jobs + job_board_min_space
|
|
175
|
+
else:
|
|
176
|
+
job_board_full_space = 0
|
|
177
|
+
|
|
178
|
+
# Allocate job board space (second priority)
|
|
179
|
+
if remaining_height <= job_board_min_space:
|
|
180
|
+
self.job_board_height = 0
|
|
181
|
+
self.log_height = 0
|
|
182
|
+
elif remaining_height <= job_board_full_space:
|
|
183
|
+
self.job_board_height = remaining_height - job_board_min_space
|
|
184
|
+
self.log_height = 0
|
|
185
|
+
elif visible_jobs == 0:
|
|
186
|
+
self.job_board_height = 0
|
|
187
|
+
self.log_height = remaining_height
|
|
188
|
+
else:
|
|
189
|
+
self.job_board_height = visible_jobs
|
|
190
|
+
self.log_height = remaining_height - job_board_full_space - self.padding_log
|
|
191
|
+
|
|
192
|
+
if self.width < self.job_board_v_limit:
|
|
193
|
+
self.job_board_show_log = False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class BoardSingleton(type):
|
|
197
|
+
_instances = {}
|
|
198
|
+
_lock = multiprocessing.Lock()
|
|
199
|
+
|
|
200
|
+
def __call__(cls, *args, **kwargs):
|
|
201
|
+
with cls._lock:
|
|
202
|
+
if cls not in cls._instances:
|
|
203
|
+
cls._instances[cls] = super(BoardSingleton, cls).__call__(*args, **kwargs)
|
|
204
|
+
cls._instances[cls]._init_singleton()
|
|
205
|
+
return cls._instances[cls]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class Board(metaclass=BoardSingleton):
|
|
209
|
+
__status_color_map = {
|
|
210
|
+
NodeStatus.PENDING: "blue",
|
|
211
|
+
NodeStatus.QUEUED: "blue",
|
|
212
|
+
NodeStatus.RUNNING: "orange4",
|
|
213
|
+
NodeStatus.SUCCESS: "green",
|
|
214
|
+
NodeStatus.ERROR: "red",
|
|
215
|
+
NodeStatus.SKIPPED: "bright_black",
|
|
216
|
+
NodeStatus.TIMEOUT: "red",
|
|
217
|
+
}
|
|
218
|
+
__theme = Theme(
|
|
219
|
+
{
|
|
220
|
+
# Text colors
|
|
221
|
+
"text.primary": "white",
|
|
222
|
+
"text.secondary": "cyan",
|
|
223
|
+
# Background colors
|
|
224
|
+
"background.primary": "grey15",
|
|
225
|
+
"background.secondary": "dark_blue",
|
|
226
|
+
# Highlight and accent colors
|
|
227
|
+
"highlight": "green",
|
|
228
|
+
"accent": " cyan",
|
|
229
|
+
# Status colors
|
|
230
|
+
"error": "red",
|
|
231
|
+
"warning": "yellow",
|
|
232
|
+
"success": "green",
|
|
233
|
+
# Node status colors
|
|
234
|
+
**{f"node.{status}": color for status, color in __status_color_map.items()},
|
|
235
|
+
# Custom style for headers
|
|
236
|
+
"header": "bold underline cyan",
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
__JOB_BOARD_HEADER = True
|
|
241
|
+
|
|
242
|
+
__JOB_BOARD_BOX = box.SIMPLE_HEAD
|
|
243
|
+
|
|
244
|
+
def __init__(self):
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
def _init_singleton(self):
|
|
248
|
+
self._console = Console(theme=Board.__theme)
|
|
249
|
+
self._active = self._console.is_terminal
|
|
250
|
+
if not self._active:
|
|
251
|
+
self._console = None
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
self._layout = Layout()
|
|
255
|
+
|
|
256
|
+
# Manager to thread data
|
|
257
|
+
self._manager = multiprocessing.Manager()
|
|
258
|
+
|
|
259
|
+
self._render_event = self._manager.Event()
|
|
260
|
+
self._render_stop_event = self._manager.Event()
|
|
261
|
+
self._render_thread = None
|
|
262
|
+
|
|
263
|
+
# Holds thread job data
|
|
264
|
+
self._board_info = self._manager.Namespace()
|
|
265
|
+
self._board_info.data_modified = False
|
|
266
|
+
self._job_data = self._manager.dict()
|
|
267
|
+
self._job_data_lock = self._manager.Lock()
|
|
268
|
+
|
|
269
|
+
self._render_data = SessionData()
|
|
270
|
+
self._render_data_lock = threading.Lock()
|
|
271
|
+
|
|
272
|
+
self._log_handler_queue = self._manager.Queue()
|
|
273
|
+
|
|
274
|
+
self._log_handler = LogBufferHandler(
|
|
275
|
+
self._log_handler_queue, n=120, event=self._render_event)
|
|
276
|
+
|
|
277
|
+
if not self.__JOB_BOARD_HEADER:
|
|
278
|
+
self._layout.padding_job_board_header = 0
|
|
279
|
+
|
|
280
|
+
self._metrics = ("warnings", "errors")
|
|
281
|
+
|
|
282
|
+
def open_dashboard(self):
|
|
283
|
+
"""Starts the dashboard rendering thread if it is not already running."""
|
|
284
|
+
|
|
285
|
+
if not self._active:
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
if not self.is_running():
|
|
289
|
+
self._update_render_data(None)
|
|
290
|
+
|
|
291
|
+
with self._job_data_lock:
|
|
292
|
+
if not self._render_thread:
|
|
293
|
+
self._render_thread = threading.Thread(target=self._render, daemon=True)
|
|
294
|
+
self._render_event.clear()
|
|
295
|
+
self._render_stop_event.clear()
|
|
296
|
+
|
|
297
|
+
self._render_thread.start()
|
|
298
|
+
|
|
299
|
+
def update_manifest(self, chip, starttimes=None):
|
|
300
|
+
"""
|
|
301
|
+
Updates the manifest file with the latest data from the chip object.
|
|
302
|
+
This ensures that the dashboard reflects the current state of the chip.
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
if not self._active:
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
self._update_render_data(chip, starttimes=starttimes)
|
|
309
|
+
|
|
310
|
+
def is_running(self):
|
|
311
|
+
"""Returns True to indicate that the dashboard is running."""
|
|
312
|
+
|
|
313
|
+
if not self._active:
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
with self._job_data_lock:
|
|
317
|
+
if not self._render_thread:
|
|
318
|
+
return False
|
|
319
|
+
|
|
320
|
+
return self._render_thread.is_alive()
|
|
321
|
+
|
|
322
|
+
def end_of_run(self, chip):
|
|
323
|
+
"""
|
|
324
|
+
Stops the dashboard rendering thread and ensures all rendering operations are completed.
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
if not self._active:
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
self._update_render_data(chip, complete=True)
|
|
331
|
+
self.stop()
|
|
332
|
+
|
|
333
|
+
def stop(self):
|
|
334
|
+
"""
|
|
335
|
+
Stops the dashboard rendering thread and ensures all rendering operations are completed.
|
|
336
|
+
"""
|
|
337
|
+
if not self.is_running():
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
# check for running jobs
|
|
341
|
+
with self._job_data_lock:
|
|
342
|
+
if self._job_data:
|
|
343
|
+
if any([not job.complete for job in self._job_data.values()]):
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
self._render_stop_event.set()
|
|
347
|
+
self._render_event.set()
|
|
348
|
+
|
|
349
|
+
# Wait for rendering to finish
|
|
350
|
+
self.wait()
|
|
351
|
+
|
|
352
|
+
def wait(self):
|
|
353
|
+
"""Waits for the dashboard rendering thread to finish."""
|
|
354
|
+
if not self.is_running():
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
self._render_thread.join()
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
def format_status(status: str):
|
|
361
|
+
"""
|
|
362
|
+
Formats the status of a node for display in the dashboard.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
status (str): The status of the node (e.g., 'running', 'success', 'error').
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
str: A formatted string with the status styled for display.
|
|
369
|
+
"""
|
|
370
|
+
return f"[node.{status.lower()}]{status.upper()}[/]"
|
|
371
|
+
|
|
372
|
+
@staticmethod
|
|
373
|
+
def format_node(design, jobname, step, index, multi_job) -> str:
|
|
374
|
+
"""
|
|
375
|
+
Formats a node's information for display in the dashboard.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
design (str): The design name.
|
|
379
|
+
jobname (str): The job name.
|
|
380
|
+
step (str): The step name.
|
|
381
|
+
index (int): The step index.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
str: A formatted string with the node's information styled for display.
|
|
385
|
+
"""
|
|
386
|
+
if multi_job:
|
|
387
|
+
return f"{design}/{jobname}/{step}/{index}"
|
|
388
|
+
else:
|
|
389
|
+
return f"{step}/{index}"
|
|
390
|
+
|
|
391
|
+
def _render_log(self, layout):
|
|
392
|
+
if layout.log_height == 0:
|
|
393
|
+
return None
|
|
394
|
+
|
|
395
|
+
table = Table(box=None)
|
|
396
|
+
table.add_column(overflow="ellipsis", no_wrap=True, vertical="bottom")
|
|
397
|
+
table.show_edge = False
|
|
398
|
+
table.show_lines = False
|
|
399
|
+
table.show_footer = False
|
|
400
|
+
table.show_header = False
|
|
401
|
+
for line in self._log_handler.get_lines(layout.log_height):
|
|
402
|
+
table.add_row(f"[bright_black]{line}[/]")
|
|
403
|
+
while table.row_count < layout.log_height:
|
|
404
|
+
table.add_row("")
|
|
405
|
+
|
|
406
|
+
return Group(table, Padding("", (0, 0)))
|
|
407
|
+
|
|
408
|
+
def _render_job_dashboard(self, layout):
|
|
409
|
+
"""
|
|
410
|
+
Creates a table of jobs and their statuses for display in the dashboard.
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Group: A Rich Group object containing tables for each job.
|
|
414
|
+
"""
|
|
415
|
+
# Don't render anything if there is not enough space
|
|
416
|
+
if layout.job_board_height == 0:
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
with self._render_data_lock:
|
|
420
|
+
job_data = self._render_data.jobs.copy() # Access jobs from SessionData
|
|
421
|
+
|
|
422
|
+
if self.__JOB_BOARD_HEADER:
|
|
423
|
+
table_box = self.__JOB_BOARD_BOX
|
|
424
|
+
else:
|
|
425
|
+
table_box = None
|
|
426
|
+
|
|
427
|
+
table = Table(box=table_box, pad_edge=False)
|
|
428
|
+
table.show_edge = False
|
|
429
|
+
table.show_lines = False
|
|
430
|
+
table.show_footer = False
|
|
431
|
+
table.show_header = self.__JOB_BOARD_HEADER
|
|
432
|
+
table.add_column("Status")
|
|
433
|
+
table.add_column("Node")
|
|
434
|
+
table.add_column("Time", justify="right")
|
|
435
|
+
for metric in self._metrics:
|
|
436
|
+
table.add_column(metric.capitalize(), justify="right")
|
|
437
|
+
if layout.job_board_show_log:
|
|
438
|
+
table.add_column("Log")
|
|
439
|
+
|
|
440
|
+
multi_jobs = len(job_data) > 1 or True
|
|
441
|
+
|
|
442
|
+
# jobname, node index, priority, node order
|
|
443
|
+
table_data_select = []
|
|
444
|
+
for chipid, job in job_data.items():
|
|
445
|
+
for n, node in enumerate(job.nodes):
|
|
446
|
+
table_data_select.append(
|
|
447
|
+
(chipid, n, node["print"]["priority"], node["print"]["order"])
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# sort for priority
|
|
451
|
+
table_data_select = sorted(table_data_select, key=lambda d: (d[2], *d[3], d[0]))
|
|
452
|
+
|
|
453
|
+
# trim to size
|
|
454
|
+
table_data_select = table_data_select[0:layout.job_board_height]
|
|
455
|
+
|
|
456
|
+
# sort for printing order
|
|
457
|
+
table_data_select = sorted(table_data_select, key=lambda d: (d[0], *d[3], d[2]))
|
|
458
|
+
|
|
459
|
+
table_data = []
|
|
460
|
+
|
|
461
|
+
for chipid, node_idx, _, _ in table_data_select:
|
|
462
|
+
job = job_data[chipid]
|
|
463
|
+
node = job.nodes[node_idx]
|
|
464
|
+
|
|
465
|
+
if (
|
|
466
|
+
layout.job_board_show_log
|
|
467
|
+
and os.path.exists(node["log"])
|
|
468
|
+
):
|
|
469
|
+
log_file = "[bright_black]{}[/]".format(node['log'])
|
|
470
|
+
else:
|
|
471
|
+
log_file = None
|
|
472
|
+
|
|
473
|
+
if node["time"]["duration"] is not None:
|
|
474
|
+
duration = f'{node["time"]["duration"]:.1f}s'
|
|
475
|
+
elif node["time"]["start"] is not None:
|
|
476
|
+
duration = f'{time.time() - node["time"]["start"]:.1f}s'
|
|
477
|
+
else:
|
|
478
|
+
duration = ""
|
|
479
|
+
|
|
480
|
+
table_data.append((
|
|
481
|
+
Board.format_status(node["status"]),
|
|
482
|
+
Board.format_node(
|
|
483
|
+
job.design, job.jobname, node["step"], node["index"],
|
|
484
|
+
multi_jobs
|
|
485
|
+
),
|
|
486
|
+
duration,
|
|
487
|
+
*node["metrics"],
|
|
488
|
+
log_file
|
|
489
|
+
))
|
|
490
|
+
|
|
491
|
+
for row_data in table_data:
|
|
492
|
+
table.add_row(*row_data)
|
|
493
|
+
|
|
494
|
+
if table.row_count == 0:
|
|
495
|
+
return None
|
|
496
|
+
|
|
497
|
+
if self.__JOB_BOARD_HEADER:
|
|
498
|
+
return Group(table, Padding("", (0, 0)))
|
|
499
|
+
return Group(Padding("", (0, 0)), table, Padding("", (0, 0)))
|
|
500
|
+
|
|
501
|
+
def _render_progress_bar(self, layout):
|
|
502
|
+
"""
|
|
503
|
+
Creates progress bars showing job completion for display in the dashboard.
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
Group: A Rich Group object containing progress bars for each job.
|
|
507
|
+
"""
|
|
508
|
+
with self._render_data_lock:
|
|
509
|
+
job_data = self._render_data.jobs.copy()
|
|
510
|
+
done = self._render_data.finished > 0 \
|
|
511
|
+
and self._render_data.total == self._render_data.finished \
|
|
512
|
+
and self._render_data.success == self._render_data.total
|
|
513
|
+
|
|
514
|
+
if done:
|
|
515
|
+
return None
|
|
516
|
+
|
|
517
|
+
ref_time = time.time()
|
|
518
|
+
runtimes = {}
|
|
519
|
+
for name, job in job_data.items():
|
|
520
|
+
if job.complete:
|
|
521
|
+
runtimes[name] = job.runtime
|
|
522
|
+
else:
|
|
523
|
+
runtime = 0.0
|
|
524
|
+
for node in job.nodes:
|
|
525
|
+
if node["time"]["duration"] is not None:
|
|
526
|
+
runtime += node["time"]["duration"]
|
|
527
|
+
elif node["time"]["start"] is not None:
|
|
528
|
+
runtime += ref_time - node["time"]["start"]
|
|
529
|
+
runtimes[name] = runtime
|
|
530
|
+
|
|
531
|
+
runtime_width = len(f"{max([0, *runtimes.values()]):.1f}")
|
|
532
|
+
|
|
533
|
+
progress = Progress(
|
|
534
|
+
TextColumn("[progress.description]{task.description}"),
|
|
535
|
+
MofNCompleteColumn(),
|
|
536
|
+
BarColumn(bar_width=60),
|
|
537
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
538
|
+
TextColumn(f" {{task.fields[runtime]:>{runtime_width}.1f}}s")
|
|
539
|
+
)
|
|
540
|
+
nodes = 0
|
|
541
|
+
for name, job in job_data.items():
|
|
542
|
+
nodes += len(job.nodes)
|
|
543
|
+
progress.add_task(
|
|
544
|
+
f"[text.primary]Progress ({job.design}/{job.jobname}):",
|
|
545
|
+
total=job.total,
|
|
546
|
+
completed=job.success,
|
|
547
|
+
runtime=runtimes[name]
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
if nodes == 0:
|
|
551
|
+
return Padding("", (0, 0))
|
|
552
|
+
|
|
553
|
+
return Group(progress, Padding("", (0, 0)))
|
|
554
|
+
|
|
555
|
+
def _render_final(self, layout):
|
|
556
|
+
"""
|
|
557
|
+
Creates a summary of the final results, including runtime, passed, and failed jobs.
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
Padding: A Rich Padding object containing the summary text.
|
|
561
|
+
"""
|
|
562
|
+
with self._render_data_lock:
|
|
563
|
+
success = self._render_data.success
|
|
564
|
+
error = self._render_data.error
|
|
565
|
+
total = self._render_data.total
|
|
566
|
+
finished = self._render_data.finished
|
|
567
|
+
runtime = self._render_data.runtime
|
|
568
|
+
|
|
569
|
+
if finished != 0 and finished == total:
|
|
570
|
+
return Padding(
|
|
571
|
+
f"[text.primary]Results {runtime:.2f}s\n"
|
|
572
|
+
f" [success]{success} passed[/]\n"
|
|
573
|
+
f" [error]{error} failed[/]\n"
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
return self._render_log(layout)
|
|
577
|
+
|
|
578
|
+
def _render(self):
|
|
579
|
+
"""
|
|
580
|
+
Main rendering method for the TUI. Continuously updates the dashboard
|
|
581
|
+
with the latest data until the stop event is set.
|
|
582
|
+
"""
|
|
583
|
+
|
|
584
|
+
def update_data():
|
|
585
|
+
try:
|
|
586
|
+
self._update_rendable_data()
|
|
587
|
+
except: # noqa E722
|
|
588
|
+
# Catch any multiprocessing errors
|
|
589
|
+
pass
|
|
590
|
+
|
|
591
|
+
def check_stop_event():
|
|
592
|
+
try:
|
|
593
|
+
return self._render_stop_event.is_set()
|
|
594
|
+
except: # noqa E722
|
|
595
|
+
# Catch any multiprocessing errors
|
|
596
|
+
return True
|
|
597
|
+
|
|
598
|
+
live = None
|
|
599
|
+
try:
|
|
600
|
+
update_data()
|
|
601
|
+
live = Live(
|
|
602
|
+
self._get_rendable(),
|
|
603
|
+
console=self._console,
|
|
604
|
+
screen=False,
|
|
605
|
+
# transient=True,
|
|
606
|
+
auto_refresh=True,
|
|
607
|
+
# refresh_per_second=60,
|
|
608
|
+
)
|
|
609
|
+
live.start()
|
|
610
|
+
|
|
611
|
+
while not check_stop_event():
|
|
612
|
+
try:
|
|
613
|
+
if self._render_event.wait(timeout=0.2):
|
|
614
|
+
self._render_event.clear()
|
|
615
|
+
except: # noqa E722
|
|
616
|
+
# Catch any multiprocessing errors
|
|
617
|
+
break
|
|
618
|
+
|
|
619
|
+
if check_stop_event():
|
|
620
|
+
break
|
|
621
|
+
|
|
622
|
+
update_data()
|
|
623
|
+
live.update(self._get_rendable(), refresh=True)
|
|
624
|
+
finally:
|
|
625
|
+
try:
|
|
626
|
+
update_data()
|
|
627
|
+
if live:
|
|
628
|
+
live.update(self._get_rendable(), refresh=True)
|
|
629
|
+
live.stop()
|
|
630
|
+
else:
|
|
631
|
+
self._console.print(self._get_rendable())
|
|
632
|
+
finally:
|
|
633
|
+
# Restore the prompt
|
|
634
|
+
print("\033[?25h", end="")
|
|
635
|
+
|
|
636
|
+
def _update_layout(self):
|
|
637
|
+
with self._render_data_lock:
|
|
638
|
+
visible_progress_bars = len(self._render_data.jobs)
|
|
639
|
+
visible_jobs_count = self._render_data.total - self._render_data.skipped
|
|
640
|
+
|
|
641
|
+
self._layout.update(
|
|
642
|
+
self._console.height,
|
|
643
|
+
self._console.width,
|
|
644
|
+
visible_jobs_count,
|
|
645
|
+
visible_progress_bars,
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
return self._layout
|
|
649
|
+
|
|
650
|
+
def _update_rendable_data(self):
|
|
651
|
+
jobs = {}
|
|
652
|
+
with self._job_data_lock:
|
|
653
|
+
if self._board_info.data_modified:
|
|
654
|
+
self._board_info.data_modified = False
|
|
655
|
+
for job, data in self._job_data.items():
|
|
656
|
+
jobs[job] = data
|
|
657
|
+
|
|
658
|
+
if not jobs:
|
|
659
|
+
return
|
|
660
|
+
|
|
661
|
+
with self._render_data_lock:
|
|
662
|
+
self._render_data.jobs.clear()
|
|
663
|
+
for job, data in jobs.items():
|
|
664
|
+
self._render_data.jobs[job] = data
|
|
665
|
+
|
|
666
|
+
self._render_data.total = sum(
|
|
667
|
+
[0, *[job.total for job in self._render_data.jobs.values()]]
|
|
668
|
+
)
|
|
669
|
+
self._render_data.success = sum(
|
|
670
|
+
[0, *[job.success for job in self._render_data.jobs.values()]]
|
|
671
|
+
)
|
|
672
|
+
self._render_data.error = sum(
|
|
673
|
+
[0, *[job.error for job in self._render_data.jobs.values()]]
|
|
674
|
+
)
|
|
675
|
+
self._render_data.skipped = sum(
|
|
676
|
+
[0, *[job.skipped for job in self._render_data.jobs.values()]]
|
|
677
|
+
)
|
|
678
|
+
self._render_data.finished = sum(
|
|
679
|
+
[0, *[job.finished for job in self._render_data.jobs.values()]]
|
|
680
|
+
)
|
|
681
|
+
self._render_data.runtime = max(
|
|
682
|
+
[0, *[job.runtime for job in self._render_data.jobs.values()]]
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
def _get_rendable(self):
|
|
686
|
+
"""
|
|
687
|
+
Combines all dashboard components (job table, progress bars, final summary)
|
|
688
|
+
into a single renderable group.
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
Group: A Rich Group object containing all dashboard components.
|
|
692
|
+
"""
|
|
693
|
+
|
|
694
|
+
layout = self._update_layout()
|
|
695
|
+
|
|
696
|
+
new_table = self._render_job_dashboard(layout)
|
|
697
|
+
new_bar = self._render_progress_bar(layout)
|
|
698
|
+
footer = self._render_final(layout)
|
|
699
|
+
|
|
700
|
+
items = []
|
|
701
|
+
if new_table:
|
|
702
|
+
items.extend([new_table])
|
|
703
|
+
|
|
704
|
+
if new_bar:
|
|
705
|
+
items.extend([new_bar])
|
|
706
|
+
|
|
707
|
+
if footer:
|
|
708
|
+
items.extend([footer])
|
|
709
|
+
|
|
710
|
+
return Group(*items)
|
|
711
|
+
|
|
712
|
+
def _update_render_data(self, chip, starttimes=None, complete=False):
|
|
713
|
+
"""
|
|
714
|
+
Updates the render data with the latest job and node information from the chip object.
|
|
715
|
+
This data is used to populate the dashboard.
|
|
716
|
+
"""
|
|
717
|
+
|
|
718
|
+
if not chip:
|
|
719
|
+
return
|
|
720
|
+
|
|
721
|
+
job_data = self._get_job(chip, starttimes=starttimes)
|
|
722
|
+
job_data.complete = complete
|
|
723
|
+
|
|
724
|
+
if not job_data.nodes:
|
|
725
|
+
return
|
|
726
|
+
|
|
727
|
+
chip_id = f"{job_data.design}/{job_data.jobname}"
|
|
728
|
+
with self._job_data_lock:
|
|
729
|
+
self._job_data[chip_id] = job_data
|
|
730
|
+
self._board_info.data_modified = True
|
|
731
|
+
self._render_event.set()
|
|
732
|
+
|
|
733
|
+
def _get_job(self, chip, starttimes=None) -> JobData:
|
|
734
|
+
if not starttimes:
|
|
735
|
+
starttimes = {}
|
|
736
|
+
|
|
737
|
+
nodes = []
|
|
738
|
+
nodestatus = {}
|
|
739
|
+
nodeorder = {}
|
|
740
|
+
node_priority = {}
|
|
741
|
+
try:
|
|
742
|
+
node_inputs = {}
|
|
743
|
+
node_outputs = {}
|
|
744
|
+
flow = chip.get("option", "flow")
|
|
745
|
+
if not flow:
|
|
746
|
+
raise SiliconCompilerError("dummy error")
|
|
747
|
+
|
|
748
|
+
runtime_flow = RuntimeFlowgraph(
|
|
749
|
+
chip.schema.get("flowgraph", flow, field='schema'),
|
|
750
|
+
args=(chip.get('arg', 'step'), chip.get('arg', 'index')),
|
|
751
|
+
to_steps=chip.get('option', 'to'),
|
|
752
|
+
prune_nodes=chip.get('option', 'prune'))
|
|
753
|
+
record = chip.schema.get("record", field='schema')
|
|
754
|
+
|
|
755
|
+
execnodes = runtime_flow.get_nodes()
|
|
756
|
+
lowest_priority = 3 * len(execnodes) # 2x + 1 is lowest computed, so 3x will be lower
|
|
757
|
+
for n, nodeset in enumerate(runtime_flow.get_execution_order()):
|
|
758
|
+
for m, node in enumerate(nodeset):
|
|
759
|
+
if node not in execnodes:
|
|
760
|
+
continue
|
|
761
|
+
nodes.append(node)
|
|
762
|
+
|
|
763
|
+
node_priority[node] = lowest_priority
|
|
764
|
+
|
|
765
|
+
status = chip.get("record", "status", step=node[0], index=node[1])
|
|
766
|
+
if status is None:
|
|
767
|
+
status = NodeStatus.PENDING
|
|
768
|
+
nodestatus[node] = status
|
|
769
|
+
nodeorder[node] = (n, m)
|
|
770
|
+
|
|
771
|
+
node_inputs[node] = runtime_flow.get_node_inputs(*node, record=record)
|
|
772
|
+
for in_node in chip.get('flowgraph', flow, node[0], node[1], 'input'):
|
|
773
|
+
node_outputs.setdefault(in_node, set()).add(node)
|
|
774
|
+
|
|
775
|
+
flow_entry_nodes = set(
|
|
776
|
+
chip.schema.get("flowgraph", flow, field="schema").get_entry_nodes())
|
|
777
|
+
|
|
778
|
+
running_nodes = set([node for node in nodes if NodeStatus.is_running(nodestatus[node])])
|
|
779
|
+
done_nodes = set([node for node in nodes if NodeStatus.is_done(nodestatus[node])])
|
|
780
|
+
error_nodes = set([node for node in nodes if NodeStatus.is_error(nodestatus[node])])
|
|
781
|
+
|
|
782
|
+
def get_node_distance(node, search, level=1):
|
|
783
|
+
dists = {}
|
|
784
|
+
|
|
785
|
+
if node not in search:
|
|
786
|
+
return dists
|
|
787
|
+
|
|
788
|
+
for snode in search[node]:
|
|
789
|
+
dists[snode] = level
|
|
790
|
+
dists.update(get_node_distance(snode, search, level=level+1))
|
|
791
|
+
|
|
792
|
+
return dists
|
|
793
|
+
|
|
794
|
+
# Compute relative node distances
|
|
795
|
+
node_dists = {}
|
|
796
|
+
for cnode in nodes:
|
|
797
|
+
# use 2x + 1 to give completed nodes sorting priority
|
|
798
|
+
node_dists[cnode] = {
|
|
799
|
+
node: 2*level+1 for node, level in get_node_distance(cnode, node_inputs).items()
|
|
800
|
+
}
|
|
801
|
+
node_dists[cnode].update({
|
|
802
|
+
node: 2*level for node, level in get_node_distance(cnode, node_outputs).items()
|
|
803
|
+
})
|
|
804
|
+
|
|
805
|
+
# Compute printing priority of nodes
|
|
806
|
+
remaining_entry_nodes = flow_entry_nodes - done_nodes
|
|
807
|
+
startnodes = running_nodes.union(remaining_entry_nodes)
|
|
808
|
+
priority_node = {0: startnodes.union(error_nodes)}
|
|
809
|
+
for node in nodes:
|
|
810
|
+
dists = node_dists[node]
|
|
811
|
+
levels = []
|
|
812
|
+
for snode in startnodes:
|
|
813
|
+
if snode not in dists:
|
|
814
|
+
continue
|
|
815
|
+
levels.append(dists[snode])
|
|
816
|
+
if not levels:
|
|
817
|
+
continue
|
|
818
|
+
priority_node.setdefault(min(levels), set()).add(node)
|
|
819
|
+
for level, level_nodes in priority_node.items():
|
|
820
|
+
for node in level_nodes:
|
|
821
|
+
if node not in node_priority:
|
|
822
|
+
continue
|
|
823
|
+
node_priority[node] = min(node_priority[node], level)
|
|
824
|
+
except SiliconCompilerError:
|
|
825
|
+
pass
|
|
826
|
+
|
|
827
|
+
design = chip.get("design")
|
|
828
|
+
jobname = chip.get("option", "jobname")
|
|
829
|
+
|
|
830
|
+
job_data = JobData()
|
|
831
|
+
job_data.jobname = jobname
|
|
832
|
+
job_data.design = design
|
|
833
|
+
totaltimes = [
|
|
834
|
+
chip.get("metric", "totaltime", step=step, index=index) or 0
|
|
835
|
+
for step, index in nodes
|
|
836
|
+
]
|
|
837
|
+
if not totaltimes:
|
|
838
|
+
totaltimes = [0]
|
|
839
|
+
job_data.runtime = max(totaltimes)
|
|
840
|
+
|
|
841
|
+
for step, index in nodes:
|
|
842
|
+
status = nodestatus[(step, index)]
|
|
843
|
+
|
|
844
|
+
job_data.total += 1
|
|
845
|
+
if NodeStatus.is_error(status):
|
|
846
|
+
job_data.error += 1
|
|
847
|
+
if NodeStatus.is_success(status):
|
|
848
|
+
job_data.success += 1
|
|
849
|
+
if NodeStatus.is_done(status):
|
|
850
|
+
job_data.finished += 1
|
|
851
|
+
|
|
852
|
+
if status == NodeStatus.SKIPPED:
|
|
853
|
+
job_data.skipped += 1
|
|
854
|
+
continue
|
|
855
|
+
|
|
856
|
+
starttime = None
|
|
857
|
+
duration = None
|
|
858
|
+
if NodeStatus.is_done(status):
|
|
859
|
+
duration = chip.get("metric", "tasktime", step=step, index=index)
|
|
860
|
+
if (step, index) in starttimes:
|
|
861
|
+
starttime = starttimes[(step, index)]
|
|
862
|
+
|
|
863
|
+
node_metrics = []
|
|
864
|
+
for metric in self._metrics:
|
|
865
|
+
value = chip.get('metric', metric, step=step, index=index)
|
|
866
|
+
if value is None:
|
|
867
|
+
node_metrics.append("")
|
|
868
|
+
else:
|
|
869
|
+
node_metrics.append(str(value))
|
|
870
|
+
|
|
871
|
+
job_data.nodes.append(
|
|
872
|
+
{
|
|
873
|
+
"step": step,
|
|
874
|
+
"index": index,
|
|
875
|
+
"status": status,
|
|
876
|
+
"time": {
|
|
877
|
+
"start": starttime,
|
|
878
|
+
"duration": duration
|
|
879
|
+
},
|
|
880
|
+
"metrics": node_metrics,
|
|
881
|
+
"log": os.path.join(
|
|
882
|
+
os.path.relpath(
|
|
883
|
+
chip.getworkdir(step=step, index=index),
|
|
884
|
+
chip.cwd,
|
|
885
|
+
),
|
|
886
|
+
f"{step}.log",
|
|
887
|
+
),
|
|
888
|
+
"print": {
|
|
889
|
+
"order": nodeorder[(step, index)],
|
|
890
|
+
"priority": node_priority[(step, index)]
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
return job_data
|