gini-toolkit 6.0.1.dev0__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.
- gini/__init__.py +12 -0
- gini/__main__.py +107 -0
- gini/_version.py +24 -0
- gini/agent/__init__.py +17 -0
- gini/agent/agent_gamemaster.py +140 -0
- gini/agent/api.py +291 -0
- gini/agent/ask.py +123 -0
- gini/agent/authoring.py +72 -0
- gini/agent/blackboard.py +114 -0
- gini/agent/contracts.py +142 -0
- gini/agent/domains.py +91 -0
- gini/agent/embed.py +123 -0
- gini/agent/gamemaster.py +256 -0
- gini/agent/kb.py +148 -0
- gini/agent/lesson_resolver.py +261 -0
- gini/agent/llm/__init__.py +5 -0
- gini/agent/llm/backend.py +43 -0
- gini/agent/llm/fake.py +25 -0
- gini/agent/llm/ollama.py +206 -0
- gini/agent/loop.py +258 -0
- gini/agent/mcp_server.py +86 -0
- gini/agent/meaning.py +225 -0
- gini/agent/mission.py +210 -0
- gini/agent/mission_controller.py +208 -0
- gini/agent/narration.py +116 -0
- gini/agent/notifier.py +86 -0
- gini/agent/personas.py +79 -0
- gini/agent/reasoning.py +172 -0
- gini/agent/recall.py +248 -0
- gini/agent/session.py +79 -0
- gini/agent/teaching_center.py +482 -0
- gini/agent/tools/__init__.py +3 -0
- gini/agent/tools/registry.py +193 -0
- gini/agent/twin/__init__.py +28 -0
- gini/agent/twin/authoring.py +71 -0
- gini/agent/twin/contracts.py +54 -0
- gini/agent/twin/dialectic.py +189 -0
- gini/agent/twin/harness.py +93 -0
- gini/agent/twin/justify.py +156 -0
- gini/agent/twin/learner.py +64 -0
- gini/agent/twin/mission.py +60 -0
- gini/agent/twin/os_coach.py +79 -0
- gini/agent/twin/salience.py +30 -0
- gini/agent/understand.py +250 -0
- gini/agent/verifiers.py +106 -0
- gini/agent/wizard.py +178 -0
- gini/agent/xv6_pack.py +74 -0
- gini/app/__init__.py +3 -0
- gini/app/context.py +368 -0
- gini/app/paths.py +121 -0
- gini/data/README.md +21 -0
- gini/domain/__init__.py +9 -0
- gini/domain/assembly.py +209 -0
- gini/domain/authoring.py +353 -0
- gini/domain/blueprints.py +5 -0
- gini/domain/capabilities.py +177 -0
- gini/domain/catalog.py +85 -0
- gini/domain/certify.py +201 -0
- gini/domain/compose.py +413 -0
- gini/domain/composition.py +88 -0
- gini/domain/concepts.py +383 -0
- gini/domain/connection_rules.py +269 -0
- gini/domain/constraints.py +153 -0
- gini/domain/content.py +59 -0
- gini/domain/cpu_journey.py +89 -0
- gini/domain/devices.py +747 -0
- gini/domain/diagnose.py +201 -0
- gini/domain/element_guide.py +327 -0
- gini/domain/explain.py +90 -0
- gini/domain/fingerprint.py +201 -0
- gini/domain/firewall.py +34 -0
- gini/domain/flowlog.py +61 -0
- gini/domain/flowtable.py +179 -0
- gini/domain/fragment_yaml.py +230 -0
- gini/domain/fragments.py +169 -0
- gini/domain/games/__init__.py +2 -0
- gini/domain/games/paging_games.py +119 -0
- gini/domain/games/policy_game.py +86 -0
- gini/domain/games/process_game.py +48 -0
- gini/domain/games/thrash_game.py +75 -0
- gini/domain/games/translate_game.py +60 -0
- gini/domain/games/trap_game.py +86 -0
- gini/domain/grader.py +155 -0
- gini/domain/grouping.py +67 -0
- gini/domain/legality.py +103 -0
- gini/domain/lesson.py +241 -0
- gini/domain/lexicon.py +150 -0
- gini/domain/machine_state.py +410 -0
- gini/domain/missions/networking/basic-lan.yaml +32 -0
- gini/domain/missions/networking/cache-in-front.yaml +23 -0
- gini/domain/missions/networking/decouple-with-queue.yaml +31 -0
- gini/domain/missions/networking/drive-load.yaml +20 -0
- gini/domain/missions/networking/fix-the-address.yaml +75 -0
- gini/domain/missions/networking/fix-the-lan.yaml +43 -0
- gini/domain/missions/networking/inspect-flows.yaml +16 -0
- gini/domain/missions/networking/k8s-autoscale.yaml +27 -0
- gini/domain/missions/networking/least-privilege.yaml +21 -0
- gini/domain/missions/networking/load-balanced-web.yaml +29 -0
- gini/domain/missions/networking/observe-it.yaml +24 -0
- gini/domain/missions/networking/put-in-vpc.yaml +30 -0
- gini/domain/missions/networking/reachability-boundary.yaml +56 -0
- gini/domain/missions/networking/sdn-reactive.yaml +35 -0
- gini/domain/missions/networking/send-request.yaml +19 -0
- gini/domain/missions/networking/serverless-api.yaml +25 -0
- gini/domain/missions/networking/service-chain.yaml +33 -0
- gini/domain/missions/os/lottery-fix.yaml +19 -0
- gini/domain/missions/os/priority-fix.yaml +24 -0
- gini/domain/missions.py +111 -0
- gini/domain/modulechain.py +36 -0
- gini/domain/objectives.py +488 -0
- gini/domain/os_zoo.py +79 -0
- gini/domain/paging_sim.py +141 -0
- gini/domain/pricing.py +199 -0
- gini/domain/probes.py +226 -0
- gini/domain/profile.py +142 -0
- gini/domain/recipes.py +738 -0
- gini/domain/riders.py +309 -0
- gini/domain/router_modules.py +224 -0
- gini/domain/routetable.py +67 -0
- gini/domain/scoring.py +76 -0
- gini/domain/staging.py +122 -0
- gini/domain/syscall_builder.py +144 -0
- gini/domain/topic_cloud.py +62 -0
- gini/domain/topology.py +213 -0
- gini/domain/vocabulary.py +51 -0
- gini/domain/xv6.py +808 -0
- gini/domain/xv6_fs.py +250 -0
- gini/domain/xv6_runner.py +113 -0
- gini/domain/xv6_vm.py +385 -0
- gini/gloader.py +17 -0
- gini/runtime/__init__.py +18 -0
- gini/runtime/cloudfabric_agent.py +370 -0
- gini/runtime/console.py +68 -0
- gini/runtime/control.py +70 -0
- gini/runtime/frame.py +138 -0
- gini/runtime/gbridge.py +638 -0
- gini/runtime/grouter.py +223 -0
- gini/runtime/hostsim.py +90 -0
- gini/runtime/shuttle.py +348 -0
- gini/runtime/switch.py +109 -0
- gini/runtime/transport.py +77 -0
- gini/runtime/xv6_bridge.py +312 -0
- gini/server/__init__.py +22 -0
- gini/server/__main__.py +74 -0
- gini/server/app.py +140 -0
- gini/server/auth.py +82 -0
- gini/server/policy.py +57 -0
- gini/server/session.py +23 -0
- gini/services/__init__.py +15 -0
- gini/services/boardflash.py +248 -0
- gini/services/boardsetup.py +374 -0
- gini/services/cloud_catalog.py +143 -0
- gini/services/compiler.py +1858 -0
- gini/services/discovery.py +324 -0
- gini/services/gloader.py +183 -0
- gini/services/orchestrator.py +1460 -0
- gini/services/persistence.py +28 -0
- gini/services/probe_runner.py +149 -0
- gini/services/project.py +217 -0
- gini/services/remote.py +93 -0
- gini/services/rider_runner.py +96 -0
- gini/services/rider_session.py +171 -0
- gini/services/shadow_store.py +52 -0
- gini/services/terminal.py +45 -0
- gini/setup/__init__.py +17 -0
- gini/setup/cli.py +109 -0
- gini/setup/images.py +33 -0
- gini/setup/marker.py +43 -0
- gini/setup/runtime.py +69 -0
- gini/ui/__init__.py +3 -0
- gini/ui/assets/app_icon.icns +0 -0
- gini/ui/assets/app_icon.ico +0 -0
- gini/ui/assets/app_icon.png +0 -0
- gini/ui/assets/app_icon_1024.png +0 -0
- gini/ui/assets/cue/_w.txt +1 -0
- gini/ui/assets/cue/ai.png +0 -0
- gini/ui/assets/cue/canvas.png +0 -0
- gini/ui/assets/cue/cloud.png +0 -0
- gini/ui/assets/cue/cost.png +0 -0
- gini/ui/assets/cue/dark/ai.png +0 -0
- gini/ui/assets/cue/dark/canvas.png +0 -0
- gini/ui/assets/cue/dark/cloud.png +0 -0
- gini/ui/assets/cue/dark/cost.png +0 -0
- gini/ui/assets/cue/dark/metrics.png +0 -0
- gini/ui/assets/cue/dark/router.png +0 -0
- gini/ui/assets/cue/dark/run.png +0 -0
- gini/ui/assets/cue/dark/serverless.png +0 -0
- gini/ui/assets/cue/dark/settings.png +0 -0
- gini/ui/assets/cue/dark/welcome.png +0 -0
- gini/ui/assets/cue/dark/wizard.png +0 -0
- gini/ui/assets/cue/ginibrand/ai.png +0 -0
- gini/ui/assets/cue/ginibrand/canvas.png +0 -0
- gini/ui/assets/cue/ginibrand/cloud.png +0 -0
- gini/ui/assets/cue/ginibrand/cost.png +0 -0
- gini/ui/assets/cue/ginibrand/metrics.png +0 -0
- gini/ui/assets/cue/ginibrand/router.png +0 -0
- gini/ui/assets/cue/ginibrand/run.png +0 -0
- gini/ui/assets/cue/ginibrand/serverless.png +0 -0
- gini/ui/assets/cue/ginibrand/settings.png +0 -0
- gini/ui/assets/cue/ginibrand/welcome.png +0 -0
- gini/ui/assets/cue/ginibrand/wizard.png +0 -0
- gini/ui/assets/cue/highcontrast/ai.png +0 -0
- gini/ui/assets/cue/highcontrast/canvas.png +0 -0
- gini/ui/assets/cue/highcontrast/cloud.png +0 -0
- gini/ui/assets/cue/highcontrast/cost.png +0 -0
- gini/ui/assets/cue/highcontrast/metrics.png +0 -0
- gini/ui/assets/cue/highcontrast/router.png +0 -0
- gini/ui/assets/cue/highcontrast/run.png +0 -0
- gini/ui/assets/cue/highcontrast/serverless.png +0 -0
- gini/ui/assets/cue/highcontrast/settings.png +0 -0
- gini/ui/assets/cue/highcontrast/welcome.png +0 -0
- gini/ui/assets/cue/highcontrast/wizard.png +0 -0
- gini/ui/assets/cue/light/ai.png +0 -0
- gini/ui/assets/cue/light/canvas.png +0 -0
- gini/ui/assets/cue/light/cloud.png +0 -0
- gini/ui/assets/cue/light/cost.png +0 -0
- gini/ui/assets/cue/light/metrics.png +0 -0
- gini/ui/assets/cue/light/router.png +0 -0
- gini/ui/assets/cue/light/run.png +0 -0
- gini/ui/assets/cue/light/serverless.png +0 -0
- gini/ui/assets/cue/light/settings.png +0 -0
- gini/ui/assets/cue/light/welcome.png +0 -0
- gini/ui/assets/cue/light/wizard.png +0 -0
- gini/ui/assets/cue/metrics.png +0 -0
- gini/ui/assets/cue/router.png +0 -0
- gini/ui/assets/cue/run.png +0 -0
- gini/ui/assets/cue/serverless.png +0 -0
- gini/ui/assets/cue/settings.png +0 -0
- gini/ui/assets/cue/welcome.png +0 -0
- gini/ui/assets/cue/wizard.png +0 -0
- gini/ui/assistant.py +2111 -0
- gini/ui/author_dialog.py +184 -0
- gini/ui/board_dialog.py +247 -0
- gini/ui/branding.py +21 -0
- gini/ui/canvas.py +2007 -0
- gini/ui/chat_panel.py +7 -0
- gini/ui/cpu_journey.py +212 -0
- gini/ui/cpu_lab.py +306 -0
- gini/ui/cue_cards.py +214 -0
- gini/ui/dashboard.py +222 -0
- gini/ui/diagnose_game.py +336 -0
- gini/ui/fingerprint_lab.py +219 -0
- gini/ui/flash_dialog.py +244 -0
- gini/ui/flow_layout.py +63 -0
- gini/ui/fragment_manager.py +1415 -0
- gini/ui/game_catalog.py +184 -0
- gini/ui/game_renderers.py +340 -0
- gini/ui/games_lab.py +90 -0
- gini/ui/inspector.py +1055 -0
- gini/ui/live_metrics.py +130 -0
- gini/ui/machine_lab.py +1412 -0
- gini/ui/main_window.py +3153 -0
- gini/ui/memory_lab.py +371 -0
- gini/ui/mission_panel.py +302 -0
- gini/ui/mode_indicator.py +227 -0
- gini/ui/palette.py +112 -0
- gini/ui/peripherals.py +218 -0
- gini/ui/process_tree.py +130 -0
- gini/ui/reset_dialog.py +179 -0
- gini/ui/router_lab.py +776 -0
- gini/ui/run_button.py +183 -0
- gini/ui/settings_dialog.py +234 -0
- gini/ui/signin_dialog.py +111 -0
- gini/ui/storage_lab.py +219 -0
- gini/ui/syscall_builder.py +235 -0
- gini/ui/syscall_lab.py +152 -0
- gini/ui/theme/__init__.py +5 -0
- gini/ui/theme/icons.py +145 -0
- gini/ui/theme/manager.py +291 -0
- gini/ui/theme/tokens.py +194 -0
- gini/ui/trap_lab.py +270 -0
- gini/ui/worker_host.py +102 -0
- gini/ui/zoo_lab.py +112 -0
- gini_toolkit-6.0.1.dev0.dist-info/METADATA +77 -0
- gini_toolkit-6.0.1.dev0.dist-info/RECORD +278 -0
- gini_toolkit-6.0.1.dev0.dist-info/WHEEL +5 -0
- gini_toolkit-6.0.1.dev0.dist-info/entry_points.txt +3 -0
- gini_toolkit-6.0.1.dev0.dist-info/top_level.txt +1 -0
gini/ui/chat_panel.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""RETIRED. Messaging is no longer a separate dock — it folded into the Ask GINI panel as one
|
|
2
|
+
unified conversation surface (GINI + people, one transcript, a conversation switcher). See
|
|
3
|
+
`assistant.py` (`_select_convo`, `_render_convo`, `_send_human`).
|
|
4
|
+
|
|
5
|
+
This module is intentionally empty; it remains only so any stale import fails loudly rather than
|
|
6
|
+
silently resurrecting a second chat panel.
|
|
7
|
+
"""
|
gini/ui/cpu_journey.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""CPU journey — a step-driven walkthrough of a system call (trap) vs a context switch (swtch).
|
|
2
|
+
|
|
3
|
+
Pick a mode, then Step through the stages. At each stage the view shows the privilege band
|
|
4
|
+
(user/kernel) and process lane the CPU is in, and highlights which save-area is touched — the
|
|
5
|
+
TRAPFRAME (all user registers, on a trap) or the CONTEXT (14 callee-saved, on a swtch). Real
|
|
6
|
+
register values from the running process seed the syscall path.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from PySide6.QtCore import Qt
|
|
11
|
+
from PySide6.QtWidgets import (
|
|
12
|
+
QButtonGroup, QDialog, QFrame, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from ..domain.cpu_journey import JOURNEY_TITLES, JOURNEYS
|
|
16
|
+
from .theme import ThemeManager, icons
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CpuJourney(QDialog):
|
|
20
|
+
def __init__(self, parent, theme: ThemeManager, device=None, cpu=None, frame=None) -> None:
|
|
21
|
+
super().__init__(parent)
|
|
22
|
+
self.theme = theme
|
|
23
|
+
self.device = device
|
|
24
|
+
self.cpu = cpu # a CpuState (real regs) to seed the syscall path
|
|
25
|
+
self.frame = frame # a TrapFrame from /trapcatch (a real frozen trap)
|
|
26
|
+
self._mode = "syscall"
|
|
27
|
+
self._i = 0
|
|
28
|
+
|
|
29
|
+
t = theme.theme
|
|
30
|
+
self.setWindowTitle(f"CPU journey — {getattr(device, 'name', 'xv6')}")
|
|
31
|
+
self.resize(820, 500)
|
|
32
|
+
self.setStyleSheet(f"QDialog{{background:{t.bg};}}")
|
|
33
|
+
root = QVBoxLayout(self)
|
|
34
|
+
|
|
35
|
+
head = QLabel("A system call is a TRAP (same process, user↔kernel, saves the trapframe). "
|
|
36
|
+
"A context switch is swtch (a different process, kernel↔kernel, saves the "
|
|
37
|
+
"context). Preemption is both. Step through and watch which save-area moves.")
|
|
38
|
+
head.setWordWrap(True); head.setStyleSheet(f"color:{t.muted};font-size:12px;")
|
|
39
|
+
root.addWidget(head)
|
|
40
|
+
|
|
41
|
+
# a live banner when we froze a real trap (Phase 2): its actual scause/sepc/stval
|
|
42
|
+
self._live = QLabel(); self._live.setWordWrap(True)
|
|
43
|
+
self._live.setVisible(bool(frame))
|
|
44
|
+
if frame is not None and getattr(frame, "ok", False):
|
|
45
|
+
acc = t.accent_for("amber")
|
|
46
|
+
self._live.setStyleSheet(
|
|
47
|
+
f"color:{acc};background:{t.panel2};border:1px solid {acc};border-radius:8px;"
|
|
48
|
+
"padding:6px 10px;font-family:monospace;font-size:12px;")
|
|
49
|
+
msg = f"● froze a live trap: {frame.kind_name} · scause {frame.scause} · sepc {frame.sepc}"
|
|
50
|
+
if frame.stval and frame.stval not in ("0x0", "0x0000000000000000"):
|
|
51
|
+
msg += f" · stval {frame.stval}"
|
|
52
|
+
if frame.pid is not None and frame.pid >= 0:
|
|
53
|
+
msg += f" · pid {frame.pid}"
|
|
54
|
+
self._live.setText(msg)
|
|
55
|
+
elif frame is not None:
|
|
56
|
+
self._live.setStyleSheet(f"color:{t.faint};font-size:12px;")
|
|
57
|
+
self._live.setText("● couldn't freeze a trap (kernel idle) — showing the reference "
|
|
58
|
+
"walkthrough. Launch a program and try again.")
|
|
59
|
+
root.addWidget(self._live)
|
|
60
|
+
|
|
61
|
+
modes = QHBoxLayout()
|
|
62
|
+
self._mode_group = QButtonGroup(self); self._mode_group.setExclusive(True)
|
|
63
|
+
self._mode_btns = {}
|
|
64
|
+
for key in ("syscall", "context", "preempt"):
|
|
65
|
+
b = QPushButton(JOURNEY_TITLES[key])
|
|
66
|
+
b.setCheckable(True); b.setChecked(key == "syscall")
|
|
67
|
+
b.setStyleSheet(self._btn_css())
|
|
68
|
+
b.clicked.connect(lambda _c=False, k=key: self._set_mode(k))
|
|
69
|
+
self._mode_group.addButton(b); self._mode_btns[key] = b
|
|
70
|
+
modes.addWidget(b)
|
|
71
|
+
modes.addStretch(1)
|
|
72
|
+
root.addLayout(modes)
|
|
73
|
+
|
|
74
|
+
self._stage_row = QHBoxLayout(); self._stage_row.setSpacing(4)
|
|
75
|
+
sw = QWidget(); sw.setLayout(self._stage_row)
|
|
76
|
+
root.addWidget(sw)
|
|
77
|
+
|
|
78
|
+
self._band = QLabel(); self._band.setStyleSheet(f"color:{t.muted};font-size:12px;")
|
|
79
|
+
root.addWidget(self._band)
|
|
80
|
+
|
|
81
|
+
self._caption = QLabel(); self._caption.setWordWrap(True)
|
|
82
|
+
self._caption.setStyleSheet(
|
|
83
|
+
f"color:{t.text};font-size:13px;background:{t.panel2};border:1px solid {t.line};"
|
|
84
|
+
"border-radius:10px;padding:12px;")
|
|
85
|
+
self._caption.setMinimumHeight(90)
|
|
86
|
+
root.addWidget(self._caption, 1)
|
|
87
|
+
|
|
88
|
+
save = QHBoxLayout()
|
|
89
|
+
self._tf = self._save_card("trapframe", "34 user registers · saved on every TRAP")
|
|
90
|
+
self._ctx = self._save_card("context", "14 callee-saved · saved on every swtch")
|
|
91
|
+
save.addWidget(self._tf); save.addWidget(self._ctx)
|
|
92
|
+
root.addLayout(save)
|
|
93
|
+
|
|
94
|
+
nav = QHBoxLayout()
|
|
95
|
+
self._prev = QPushButton(" Prev"); self._prev.setStyleSheet(self._btn_css())
|
|
96
|
+
self._prev.clicked.connect(lambda: self._step(-1))
|
|
97
|
+
self._next = QPushButton(" Step ▸"); self._next.setStyleSheet(self._btn_css())
|
|
98
|
+
self._next.clicked.connect(lambda: self._step(1))
|
|
99
|
+
self._pos = QLabel(); self._pos.setStyleSheet(f"color:{t.muted};font-size:12px;")
|
|
100
|
+
nav.addWidget(self._pos); nav.addStretch(1)
|
|
101
|
+
nav.addWidget(self._prev); nav.addWidget(self._next)
|
|
102
|
+
root.addLayout(nav)
|
|
103
|
+
|
|
104
|
+
self._set_mode("syscall")
|
|
105
|
+
|
|
106
|
+
def _btn_css(self) -> str:
|
|
107
|
+
t = self.theme.theme
|
|
108
|
+
return (f"QPushButton{{color:{t.text};background:{t.panel2};border:1px solid {t.line};"
|
|
109
|
+
f"border-radius:8px;padding:6px 12px;}}"
|
|
110
|
+
f"QPushButton:checked{{border-color:{t.accent};color:{t.accent_for('blue')};}}"
|
|
111
|
+
f"QPushButton:hover{{border-color:{t.accent};}}"
|
|
112
|
+
f"QPushButton:disabled{{color:{t.faint};}}")
|
|
113
|
+
|
|
114
|
+
def _save_card(self, name, sub) -> QFrame:
|
|
115
|
+
t = self.theme.theme
|
|
116
|
+
f = QFrame(); f.setObjectName(name)
|
|
117
|
+
v = QVBoxLayout(f); v.setContentsMargins(10, 8, 10, 8)
|
|
118
|
+
title = QLabel(name); title.setObjectName("t")
|
|
119
|
+
sublbl = QLabel(sub); sublbl.setObjectName("s")
|
|
120
|
+
v.addWidget(title); v.addWidget(sublbl)
|
|
121
|
+
f._title, f._sub = title, sublbl
|
|
122
|
+
return f
|
|
123
|
+
|
|
124
|
+
def _set_mode(self, key) -> None:
|
|
125
|
+
self._mode = key
|
|
126
|
+
self._i = 0
|
|
127
|
+
for k, b in self._mode_btns.items():
|
|
128
|
+
b.setChecked(k == key)
|
|
129
|
+
# rebuild the stage chips
|
|
130
|
+
while self._stage_row.count():
|
|
131
|
+
w = self._stage_row.takeAt(0).widget()
|
|
132
|
+
if w:
|
|
133
|
+
w.deleteLater()
|
|
134
|
+
self._chips = []
|
|
135
|
+
t = self.theme.theme
|
|
136
|
+
for s in JOURNEYS[key]:
|
|
137
|
+
c = QLabel(s.title)
|
|
138
|
+
c.setAlignment(Qt.AlignCenter)
|
|
139
|
+
c.setStyleSheet(
|
|
140
|
+
f"color:{t.muted};background:{t.panel2};border:1px solid {t.line};"
|
|
141
|
+
"border-radius:6px;padding:4px 6px;font-size:11px;")
|
|
142
|
+
self._chips.append(c)
|
|
143
|
+
self._stage_row.addWidget(c)
|
|
144
|
+
self._render()
|
|
145
|
+
|
|
146
|
+
def _step(self, d) -> None:
|
|
147
|
+
stages = JOURNEYS[self._mode]
|
|
148
|
+
self._i = max(0, min(len(stages) - 1, self._i + d))
|
|
149
|
+
self._render()
|
|
150
|
+
|
|
151
|
+
def _live_note(self, title) -> str:
|
|
152
|
+
"""The real-values line appended to a trap-entry stage caption. Prefers a frozen trap
|
|
153
|
+
(TrapFrame) — its actual saved registers and scause — else falls back to the running
|
|
154
|
+
proc's live registers at the dispatch stage."""
|
|
155
|
+
fr = self.frame
|
|
156
|
+
if fr is not None and getattr(fr, "ok", False):
|
|
157
|
+
r = fr.regs
|
|
158
|
+
if title == "uservec":
|
|
159
|
+
parts = [f"{k}={r[k]}" for k in ("ra", "sp", "a0", "a7") if k in r]
|
|
160
|
+
return ("this trap's user registers, saved into the trapframe: " + " ".join(parts)
|
|
161
|
+
if parts else "this trap's user registers are saved into the trapframe")
|
|
162
|
+
if title == "usertrap":
|
|
163
|
+
s = f"scause={fr.scause} → {fr.kind_name} · sepc={fr.sepc}"
|
|
164
|
+
if fr.stval and fr.stval not in ("0x0", "0x0000000000000000"):
|
|
165
|
+
s += f" · stval (faulting address) = {fr.stval}"
|
|
166
|
+
return s
|
|
167
|
+
if title == "syscall()":
|
|
168
|
+
a7, a0 = r.get("a7", ""), r.get("a0", "")
|
|
169
|
+
return f"live: a7={a7} a0={a0}" if (a7 or a0) else ""
|
|
170
|
+
return ""
|
|
171
|
+
# no frozen trap — the original behaviour: seed the dispatch stage from the running proc
|
|
172
|
+
if title == "syscall()" and self.cpu is not None:
|
|
173
|
+
return (f"live: a7={self.cpu.key('a7')} a0={self.cpu.key('a0')} "
|
|
174
|
+
f"pc={self.cpu.key('pc')}")
|
|
175
|
+
return ""
|
|
176
|
+
|
|
177
|
+
def _render(self) -> None:
|
|
178
|
+
t = self.theme.theme
|
|
179
|
+
stages = JOURNEYS[self._mode]
|
|
180
|
+
s = stages[self._i]
|
|
181
|
+
for idx, c in enumerate(self._chips):
|
|
182
|
+
active = idx == self._i
|
|
183
|
+
done = idx < self._i
|
|
184
|
+
col = t.accent_for("blue") if active else (t.text if done else t.muted)
|
|
185
|
+
border = t.accent if active else t.line
|
|
186
|
+
c.setStyleSheet(
|
|
187
|
+
f"color:{col};background:{t.panel2};border:1px solid {border};"
|
|
188
|
+
f"border-radius:6px;padding:4px 6px;font-size:11px;"
|
|
189
|
+
f"{'font-weight:600;' if active else ''}")
|
|
190
|
+
lane = {"A": "process A", "sched": "scheduler", "B": "process B"}.get(s.lane, s.lane)
|
|
191
|
+
self._band.setText(f"CPU is in: {s.band.upper()} mode · {lane}"
|
|
192
|
+
+ (f" · privilege {'change' if s.band == 'user' else 'stays S'}"
|
|
193
|
+
if self._mode != 'context' else " · never leaves S"))
|
|
194
|
+
# seed the trap-entry captions with real values — from a frozen trap if we have one,
|
|
195
|
+
# else the running proc's registers (the old behaviour), else nothing.
|
|
196
|
+
note = self._live_note(s.title) if self._mode == "syscall" else ""
|
|
197
|
+
self._caption.setText(s.caption + (f"\n\n{note}" if note else ""))
|
|
198
|
+
# highlight the active save-area
|
|
199
|
+
for card, kind in ((self._tf, "trapframe"), (self._ctx, "context")):
|
|
200
|
+
on = s.save == kind
|
|
201
|
+
accent = t.accent_for("green" if kind == "trapframe" else "amber")
|
|
202
|
+
card.setStyleSheet(
|
|
203
|
+
f"QFrame#{kind}{{background:{t.panel2};border:1px solid "
|
|
204
|
+
f"{accent if on else t.line};border-radius:10px;}}")
|
|
205
|
+
card._title.setStyleSheet(
|
|
206
|
+
f"color:{accent if on else t.text};font-size:13px;font-weight:600;border:none;"
|
|
207
|
+
+ (f"" if on else ""))
|
|
208
|
+
card._sub.setStyleSheet(f"color:{t.muted};font-size:11px;border:none;")
|
|
209
|
+
card._title.setText(("▸ " if on else "") + kind + (" (writing)" if on else ""))
|
|
210
|
+
self._pos.setText(f"stage {self._i + 1} / {len(stages)}")
|
|
211
|
+
self._prev.setEnabled(self._i > 0)
|
|
212
|
+
self._next.setEnabled(self._i < len(stages) - 1)
|
gini/ui/cpu_lab.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""CPU & Registers face of the xv6 Machine — the HARDWARE / privilege view.
|
|
2
|
+
|
|
3
|
+
Deliberately NOT a subset of the Process Scheduler (which is about *which* process runs). This is the
|
|
4
|
+
CPU's privilege-and-trap machinery, all read from the live kernel with no halt:
|
|
5
|
+
|
|
6
|
+
• Mode-time bar — user / kernel / idle split of the last second, sampled at each timer tick by the
|
|
7
|
+
trap entry path (usertrap=user, kerneltrap=kernel|idle). The honest 'how busy, and where'.
|
|
8
|
+
• CSR / interrupt strip — the three S-mode interrupt sources (enabled + pending) from sie/sip, the
|
|
9
|
+
came-from privilege (sstatus.SPP), the trap vector (stvec), and the last trap cause (scause).
|
|
10
|
+
• Register tiles — the saved user register file per hart, as colored tiles grouped by role.
|
|
11
|
+
|
|
12
|
+
The momentary global interrupt bit (sstatus.SIE) reads 0 during a dump (we're inside a handler), so
|
|
13
|
+
the strip leans on the enable CONFIG (sie) for the honest interrupt state — see interrupt_sources().
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from PySide6.QtCore import Qt, QTimer, Signal
|
|
18
|
+
from PySide6.QtGui import QColor, QPainter
|
|
19
|
+
from PySide6.QtWidgets import (
|
|
20
|
+
QFrame, QGridLayout, QHBoxLayout, QLabel, QVBoxLayout, QWidget,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from ..domain.xv6 import (
|
|
24
|
+
interrupt_sources, mode_split, scause_str, sstatus_flags,
|
|
25
|
+
)
|
|
26
|
+
from .theme import ThemeManager, icons
|
|
27
|
+
from .theme.manager import scale_css as _scss
|
|
28
|
+
|
|
29
|
+
# register tiles grouped by role -> (row of reg names, accent key)
|
|
30
|
+
_REG_GROUPS = [
|
|
31
|
+
("instruction", ["pc"], "blue"),
|
|
32
|
+
("stack", ["sp", "s0"], "amber"),
|
|
33
|
+
("return", ["ra"], "purple"),
|
|
34
|
+
("arguments", ["a0", "a7"], "green"),
|
|
35
|
+
("paging", ["satp"], "red"),
|
|
36
|
+
("mem size", ["sz"], "slate"),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _decode_satp(satp: str) -> str:
|
|
41
|
+
"""Sv39 satp -> mode + physical page-table root (PPN << 12)."""
|
|
42
|
+
try:
|
|
43
|
+
v = int(satp, 16)
|
|
44
|
+
except (TypeError, ValueError):
|
|
45
|
+
return ""
|
|
46
|
+
mode = (v >> 60) & 0xF
|
|
47
|
+
ppn = v & ((1 << 44) - 1)
|
|
48
|
+
return {0: "Bare", 8: "Sv39", 9: "Sv48"}.get(mode, f"mode {mode}") + f" · root {hex(ppn << 12)}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SegmentBar(QWidget):
|
|
52
|
+
"""A horizontal proportional bar: [(label, fraction, color)] painted end to end."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, parent=None) -> None:
|
|
55
|
+
super().__init__(parent)
|
|
56
|
+
self._segs: list = []
|
|
57
|
+
self.setMinimumHeight(30)
|
|
58
|
+
|
|
59
|
+
def set_segments(self, segs) -> None:
|
|
60
|
+
self._segs = [s for s in segs if s[1] > 0]
|
|
61
|
+
self.update()
|
|
62
|
+
|
|
63
|
+
def paintEvent(self, _e) -> None: # noqa: N802
|
|
64
|
+
p = QPainter(self)
|
|
65
|
+
p.setRenderHint(QPainter.Antialiasing)
|
|
66
|
+
w, h = self.width(), self.height()
|
|
67
|
+
x = 0.0
|
|
68
|
+
for label, frac, color in self._segs:
|
|
69
|
+
seg_w = w * frac
|
|
70
|
+
p.setBrush(QColor(color))
|
|
71
|
+
p.setPen(Qt.NoPen)
|
|
72
|
+
p.drawRoundedRect(int(x) + 1, 0, int(seg_w) - 2, h, 5, 5)
|
|
73
|
+
if seg_w > 46: # only label a wide-enough segment
|
|
74
|
+
p.setPen(QColor("#ffffff"))
|
|
75
|
+
p.drawText(int(x), 0, int(seg_w), h, Qt.AlignCenter,
|
|
76
|
+
f"{label} {round(frac * 100)}%")
|
|
77
|
+
x += seg_w
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CpuLab(QWidget):
|
|
81
|
+
snap_ready = Signal(object)
|
|
82
|
+
|
|
83
|
+
def __init__(self, parent, theme: ThemeManager, device, state, live=False) -> None:
|
|
84
|
+
super().__init__(parent)
|
|
85
|
+
self.setWindowFlag(Qt.Window, True)
|
|
86
|
+
self.theme = theme
|
|
87
|
+
self.device = device
|
|
88
|
+
self.state = state
|
|
89
|
+
self.live = live
|
|
90
|
+
self._closed = False
|
|
91
|
+
self._busy = False
|
|
92
|
+
self._prev_modetime: dict | None = None
|
|
93
|
+
|
|
94
|
+
t = theme.theme
|
|
95
|
+
self.setWindowTitle(f"CPU & Registers Lab — {device.name}")
|
|
96
|
+
self.resize(860, 620)
|
|
97
|
+
self.setStyleSheet(f"QWidget{{background:{t.bg};}}")
|
|
98
|
+
root = QVBoxLayout(self)
|
|
99
|
+
root.setSpacing(12)
|
|
100
|
+
|
|
101
|
+
head = QHBoxLayout()
|
|
102
|
+
ic = QLabel(); ic.setPixmap(icons.render_pixmap("host", t.accent_for("red"), 22))
|
|
103
|
+
title = QLabel(f" CPU & Registers Lab — {device.name}")
|
|
104
|
+
title.setStyleSheet(_scss(f"color:{t.text};font-size:16px;font-weight:600;"))
|
|
105
|
+
head.addWidget(ic); head.addWidget(title); head.addStretch(1)
|
|
106
|
+
mode = QLabel("live" if live else "offline demo")
|
|
107
|
+
mode.setStyleSheet(
|
|
108
|
+
f"color:{t.success if live else t.muted};background:{t.panel2};"
|
|
109
|
+
f"border:1px solid {t.line};border-radius:9px;padding:2px 10px;font-size:11px;")
|
|
110
|
+
head.addWidget(mode)
|
|
111
|
+
root.addLayout(head)
|
|
112
|
+
|
|
113
|
+
# 1) mode-time bar
|
|
114
|
+
self._bar = SegmentBar()
|
|
115
|
+
self._bar_note = QLabel()
|
|
116
|
+
self._bar_note.setStyleSheet(_scss(f"color:{t.muted};font-size:11px;"))
|
|
117
|
+
root.addWidget(self._panel("Where the CPU spends time · user / kernel / idle (last ~1s)",
|
|
118
|
+
self._bar, self._bar_note))
|
|
119
|
+
|
|
120
|
+
# 2) CSR / interrupt strip
|
|
121
|
+
self._csr_box = QWidget()
|
|
122
|
+
self._csr_lay = QHBoxLayout(self._csr_box)
|
|
123
|
+
self._csr_lay.setContentsMargins(0, 0, 0, 0)
|
|
124
|
+
self._csr_lay.setSpacing(6)
|
|
125
|
+
self._csr_note = QLabel()
|
|
126
|
+
self._csr_note.setStyleSheet(_scss(f"color:{t.muted};font-size:11px;"))
|
|
127
|
+
root.addWidget(self._panel("Traps & interrupts · control CSRs (this hart)",
|
|
128
|
+
self._csr_box, self._csr_note))
|
|
129
|
+
|
|
130
|
+
# 3) register tiles (per core)
|
|
131
|
+
self._tiles_box = QWidget()
|
|
132
|
+
self._tiles_lay = QVBoxLayout(self._tiles_box)
|
|
133
|
+
self._tiles_lay.setContentsMargins(0, 0, 0, 0)
|
|
134
|
+
self._tiles_lay.setSpacing(10)
|
|
135
|
+
root.addWidget(self._panel("Register file · saved user context, per core",
|
|
136
|
+
self._tiles_box), 1)
|
|
137
|
+
root.addStretch(0)
|
|
138
|
+
|
|
139
|
+
self.snap_ready.connect(self._on_snap)
|
|
140
|
+
self._poll = QTimer(self)
|
|
141
|
+
self._poll.timeout.connect(lambda: self._fetch())
|
|
142
|
+
self._render()
|
|
143
|
+
if self.live:
|
|
144
|
+
self._poll.start(1000)
|
|
145
|
+
self._fetch()
|
|
146
|
+
|
|
147
|
+
# -- panels / chips / tiles -------------------------------------------- #
|
|
148
|
+
def _panel(self, title, *inners) -> QFrame:
|
|
149
|
+
t = self.theme.theme
|
|
150
|
+
f = QFrame(); f.setStyleSheet(
|
|
151
|
+
_scss(f"QFrame{{background:{t.panel2};border:1px solid {t.line};border-radius:10px;}}"))
|
|
152
|
+
v = QVBoxLayout(f); v.setContentsMargins(12, 9, 12, 11); v.setSpacing(7)
|
|
153
|
+
h = QLabel(title)
|
|
154
|
+
h.setStyleSheet(_scss(f"color:{t.muted};font-size:11px;font-weight:600;border:none;"))
|
|
155
|
+
v.addWidget(h)
|
|
156
|
+
for inner in inners:
|
|
157
|
+
inner.setStyleSheet((inner.styleSheet() or "") + "border:none;")
|
|
158
|
+
v.addWidget(inner)
|
|
159
|
+
return f
|
|
160
|
+
|
|
161
|
+
def _chip(self, text, color, filled=False, dot=False) -> QLabel:
|
|
162
|
+
t = self.theme.theme
|
|
163
|
+
lbl = QLabel(("● " if dot else "") + text)
|
|
164
|
+
if filled:
|
|
165
|
+
lbl.setStyleSheet(_scss(
|
|
166
|
+
f"color:#ffffff;background:{color};border-radius:8px;padding:3px 9px;"
|
|
167
|
+
"font-size:11px;font-weight:600;"))
|
|
168
|
+
else:
|
|
169
|
+
lbl.setStyleSheet(_scss(
|
|
170
|
+
f"color:{t.muted};background:{t.panel};border:1px solid {t.line};"
|
|
171
|
+
"border-radius:8px;padding:3px 9px;font-size:11px;"))
|
|
172
|
+
return lbl
|
|
173
|
+
|
|
174
|
+
def _reg_tile(self, name, value, color, subtitle="") -> QFrame:
|
|
175
|
+
t = self.theme.theme
|
|
176
|
+
f = QFrame()
|
|
177
|
+
f.setStyleSheet(_scss(
|
|
178
|
+
f"QFrame{{background:{t.panel};border:1px solid {t.line};"
|
|
179
|
+
f"border-left:3px solid {color};border-radius:7px;}}"))
|
|
180
|
+
v = QVBoxLayout(f); v.setContentsMargins(9, 5, 9, 6); v.setSpacing(1)
|
|
181
|
+
n = QLabel(name.upper())
|
|
182
|
+
n.setStyleSheet(_scss(f"color:{color};font-size:10px;font-weight:700;border:none;"))
|
|
183
|
+
val = QLabel(value)
|
|
184
|
+
val.setStyleSheet(_scss(f"color:{t.text};font-family:monospace;font-size:13px;border:none;"))
|
|
185
|
+
v.addWidget(n); v.addWidget(val)
|
|
186
|
+
if subtitle:
|
|
187
|
+
s = QLabel(subtitle)
|
|
188
|
+
s.setStyleSheet(_scss(f"color:{t.faint};font-size:10px;border:none;"))
|
|
189
|
+
v.addWidget(s)
|
|
190
|
+
return f
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def _clear(layout) -> None:
|
|
194
|
+
while layout.count():
|
|
195
|
+
it = layout.takeAt(0)
|
|
196
|
+
w = it.widget()
|
|
197
|
+
if w is not None:
|
|
198
|
+
w.setParent(None)
|
|
199
|
+
|
|
200
|
+
# -- data -------------------------------------------------------------- #
|
|
201
|
+
def _fetch(self) -> None:
|
|
202
|
+
if self._busy or self._closed:
|
|
203
|
+
return
|
|
204
|
+
self._busy = True
|
|
205
|
+
import threading
|
|
206
|
+
|
|
207
|
+
def work():
|
|
208
|
+
try:
|
|
209
|
+
self.state.refresh()
|
|
210
|
+
if not self._closed:
|
|
211
|
+
self.snap_ready.emit(None)
|
|
212
|
+
except (Exception, RuntimeError):
|
|
213
|
+
pass
|
|
214
|
+
threading.Thread(target=work, daemon=True).start()
|
|
215
|
+
|
|
216
|
+
def _on_snap(self, _obj) -> None:
|
|
217
|
+
self._busy = False
|
|
218
|
+
if not self._closed:
|
|
219
|
+
self._render()
|
|
220
|
+
|
|
221
|
+
def _render(self) -> None:
|
|
222
|
+
snap = self.state.latest
|
|
223
|
+
if snap is None:
|
|
224
|
+
return
|
|
225
|
+
t = self.theme.theme
|
|
226
|
+
self._render_modebar(snap, t)
|
|
227
|
+
self._render_csr(snap, t)
|
|
228
|
+
self._render_tiles(snap, t)
|
|
229
|
+
|
|
230
|
+
def _render_modebar(self, snap, t) -> None:
|
|
231
|
+
if not snap.modetime:
|
|
232
|
+
self._bar.set_segments([])
|
|
233
|
+
self._bar_note.setText("mode-time needs the kernel rebuild (MODETIME line).")
|
|
234
|
+
return
|
|
235
|
+
split = mode_split(self._prev_modetime, snap.modetime)
|
|
236
|
+
self._prev_modetime = dict(snap.modetime)
|
|
237
|
+
self._bar.set_segments([
|
|
238
|
+
("user", split["user"], t.accent_for("green")),
|
|
239
|
+
("kernel", split["kernel"], t.accent_for("amber")),
|
|
240
|
+
("idle", split["idle"], t.accent_for("slate")),
|
|
241
|
+
])
|
|
242
|
+
if sum(split.values()) == 0:
|
|
243
|
+
self._bar_note.setText("sampling… (needs a second timer tick to show a delta)")
|
|
244
|
+
else:
|
|
245
|
+
self._bar_note.setText(
|
|
246
|
+
f"user {round(split['user']*100)}% · kernel {round(split['kernel']*100)}% "
|
|
247
|
+
f"· idle {round(split['idle']*100)}% — sampled at each timer interrupt")
|
|
248
|
+
|
|
249
|
+
def _render_csr(self, snap, t) -> None:
|
|
250
|
+
self._clear(self._csr_lay)
|
|
251
|
+
csr = snap.csr
|
|
252
|
+
if not csr:
|
|
253
|
+
self._csr_note.setText("control CSRs need the kernel rebuild (CSR line).")
|
|
254
|
+
return
|
|
255
|
+
# interrupt sources: enabled -> filled+colored, pending -> a dot
|
|
256
|
+
for src in interrupt_sources(csr.get("sie", 0), csr.get("sip", 0)):
|
|
257
|
+
self._csr_lay.addWidget(self._chip(
|
|
258
|
+
src["name"], t.accent_for("purple"),
|
|
259
|
+
filled=src["enabled"], dot=src["pending"]))
|
|
260
|
+
self._csr_lay.addSpacing(8)
|
|
261
|
+
flags = sstatus_flags(csr.get("sstatus", 0))
|
|
262
|
+
came = "user" if flags["SPP"] == "U" else "kernel"
|
|
263
|
+
self._csr_lay.addWidget(self._chip(
|
|
264
|
+
f"came from {came}", t.accent_for("blue"), filled=True))
|
|
265
|
+
self._csr_lay.addStretch(1)
|
|
266
|
+
self._csr_note.setText(
|
|
267
|
+
f"trap vector (stvec) {hex(csr.get('stvec', 0))} · "
|
|
268
|
+
f"last trap (scause): {scause_str(csr.get('scause'))} · "
|
|
269
|
+
"interrupt state = enabled sources above (global SIE reads 0 mid-dump)")
|
|
270
|
+
|
|
271
|
+
def _render_tiles(self, snap, t) -> None:
|
|
272
|
+
self._clear(self._tiles_lay)
|
|
273
|
+
cpu_regs = snap.cpu_regs or ({0: snap.cpu} if snap.cpu else {})
|
|
274
|
+
if not cpu_regs:
|
|
275
|
+
self._tiles_lay.addWidget(QLabel("no running process on any core"))
|
|
276
|
+
return
|
|
277
|
+
for c in sorted(cpu_regs):
|
|
278
|
+
cs = cpu_regs[c]
|
|
279
|
+
pid = cs.key("pid")
|
|
280
|
+
sec = QFrame()
|
|
281
|
+
sv = QVBoxLayout(sec); sv.setContentsMargins(0, 0, 0, 0); sv.setSpacing(6)
|
|
282
|
+
hdr = QLabel(f"CPU {c}" + (f" · pid {pid}" if pid not in ("—",) else " · idle"))
|
|
283
|
+
hdr.setStyleSheet(_scss(f"color:{t.text};font-size:12px;font-weight:600;border:none;"))
|
|
284
|
+
sv.addWidget(hdr)
|
|
285
|
+
grid = QGridLayout(); grid.setSpacing(7)
|
|
286
|
+
col = 0
|
|
287
|
+
for _group, names, key in _REG_GROUPS:
|
|
288
|
+
color = t.accent_for(key)
|
|
289
|
+
for name in names:
|
|
290
|
+
val = cs.key(name)
|
|
291
|
+
sub = _decode_satp(val) if name == "satp" else ""
|
|
292
|
+
grid.addWidget(self._reg_tile(name, val, color, sub), 0, col)
|
|
293
|
+
col += 1
|
|
294
|
+
grid.setColumnStretch(col, 1)
|
|
295
|
+
gw = QWidget(); gw.setLayout(grid)
|
|
296
|
+
sv.addWidget(gw)
|
|
297
|
+
self._tiles_lay.addWidget(sec)
|
|
298
|
+
|
|
299
|
+
def closeEvent(self, e) -> None: # noqa: N802
|
|
300
|
+
self._closed = True
|
|
301
|
+
self._poll.stop()
|
|
302
|
+
try:
|
|
303
|
+
self.snap_ready.disconnect(self._on_snap)
|
|
304
|
+
except (RuntimeError, TypeError):
|
|
305
|
+
pass
|
|
306
|
+
super().closeEvent(e)
|