portacode 1.4.13.dev7__py3-none-any.whl → 1.4.13.dev9__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.
- portacode/_version.py +2 -2
- portacode/connection/handlers/proxmox_infra.py +13 -2
- portacode/connection/handlers/system_handlers.py +90 -2
- {portacode-1.4.13.dev7.dist-info → portacode-1.4.13.dev9.dist-info}/METADATA +1 -1
- {portacode-1.4.13.dev7.dist-info → portacode-1.4.13.dev9.dist-info}/RECORD +9 -9
- {portacode-1.4.13.dev7.dist-info → portacode-1.4.13.dev9.dist-info}/WHEEL +0 -0
- {portacode-1.4.13.dev7.dist-info → portacode-1.4.13.dev9.dist-info}/entry_points.txt +0 -0
- {portacode-1.4.13.dev7.dist-info → portacode-1.4.13.dev9.dist-info}/licenses/LICENSE +0 -0
- {portacode-1.4.13.dev7.dist-info → portacode-1.4.13.dev9.dist-info}/top_level.txt +0 -0
portacode/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.4.13.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 4, 13, '
|
|
31
|
+
__version__ = version = '1.4.13.dev9'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 4, 13, 'dev9')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -979,13 +979,24 @@ def _bootstrap_portacode(
|
|
|
979
979
|
history_snippet = ""
|
|
980
980
|
if isinstance(history, list) and history:
|
|
981
981
|
history_snippet = f" history={history[-3:]}"
|
|
982
|
+
command = details.get("cmd")
|
|
983
|
+
command_text = ""
|
|
984
|
+
if command:
|
|
985
|
+
if isinstance(command, (list, tuple)):
|
|
986
|
+
command_text = shlex.join(str(entry) for entry in command)
|
|
987
|
+
else:
|
|
988
|
+
command_text = str(command)
|
|
989
|
+
command_suffix = f" command={command_text}" if command_text else ""
|
|
982
990
|
if summary:
|
|
983
991
|
logger.warning(
|
|
984
|
-
"Portacode bootstrap failure summary=%s%s",
|
|
992
|
+
"Portacode bootstrap failure summary=%s%s%s",
|
|
985
993
|
summary,
|
|
986
994
|
f" history_len={len(history)}" if history else "",
|
|
995
|
+
f" command={command_text}" if command_text else "",
|
|
996
|
+
)
|
|
997
|
+
raise RuntimeError(
|
|
998
|
+
f"Portacode bootstrap steps failed: {summary}{history_snippet}{command_suffix}"
|
|
987
999
|
)
|
|
988
|
-
raise RuntimeError(f"Portacode bootstrap steps failed: {summary}{history_snippet}")
|
|
989
1000
|
raise RuntimeError("Portacode bootstrap steps failed.")
|
|
990
1001
|
key_step = next((entry for entry in results if entry.get("name") == "portacode_connect"), None)
|
|
991
1002
|
public_key = key_step.get("public_key") if key_step else default_public_key
|
|
@@ -10,8 +10,9 @@ import platform
|
|
|
10
10
|
import shutil
|
|
11
11
|
import subprocess
|
|
12
12
|
import threading
|
|
13
|
+
import time
|
|
13
14
|
from pathlib import Path
|
|
14
|
-
from typing import Any, Dict
|
|
15
|
+
from typing import Any, Dict, Optional
|
|
15
16
|
|
|
16
17
|
from portacode import __version__
|
|
17
18
|
import psutil
|
|
@@ -31,11 +32,25 @@ _cpu_percent = 0.0
|
|
|
31
32
|
_cpu_thread = None
|
|
32
33
|
_cpu_lock = threading.Lock()
|
|
33
34
|
|
|
35
|
+
# Cgroup v2 tracking
|
|
36
|
+
_CGROUP_CONTROLLERS = Path("/sys/fs/cgroup/cgroup.controllers")
|
|
37
|
+
_CGROUP_CPU_STAT = Path("/sys/fs/cgroup/cpu.stat")
|
|
38
|
+
_CGROUP_CPU_MAX = Path("/sys/fs/cgroup/cpu.max")
|
|
39
|
+
_cgroup_v2_supported: Optional[bool] = None
|
|
40
|
+
_last_cgroup_usage: Optional[int] = None
|
|
41
|
+
_last_cgroup_time: Optional[float] = None
|
|
42
|
+
|
|
34
43
|
def _cpu_monitor():
|
|
35
44
|
"""Background thread to update CPU usage every 5 seconds."""
|
|
36
45
|
global _cpu_percent
|
|
37
46
|
while True:
|
|
38
|
-
|
|
47
|
+
percent = _get_cgroup_cpu_percent()
|
|
48
|
+
if percent is None:
|
|
49
|
+
# Fall back to psutil when cgroup stats are not available yet.
|
|
50
|
+
percent = psutil.cpu_percent(interval=5.0)
|
|
51
|
+
else:
|
|
52
|
+
time.sleep(5.0)
|
|
53
|
+
_cpu_percent = percent
|
|
39
54
|
|
|
40
55
|
def _ensure_cpu_thread():
|
|
41
56
|
"""Ensure CPU monitoring thread is running (singleton)."""
|
|
@@ -130,6 +145,79 @@ def _get_playwright_info() -> Dict[str, Any]:
|
|
|
130
145
|
return result
|
|
131
146
|
|
|
132
147
|
|
|
148
|
+
def _detect_cgroup_v2() -> bool:
|
|
149
|
+
global _cgroup_v2_supported
|
|
150
|
+
if _cgroup_v2_supported is not None:
|
|
151
|
+
return _cgroup_v2_supported
|
|
152
|
+
_cgroup_v2_supported = _CGROUP_CONTROLLERS.exists() and _CGROUP_CPU_STAT.exists()
|
|
153
|
+
return _cgroup_v2_supported
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _read_cgroup_cpu_usage() -> Optional[int]:
|
|
157
|
+
try:
|
|
158
|
+
data = _CGROUP_CPU_STAT.read_text()
|
|
159
|
+
except (OSError, UnicodeDecodeError):
|
|
160
|
+
return None
|
|
161
|
+
for line in data.splitlines():
|
|
162
|
+
parts = line.strip().split()
|
|
163
|
+
if len(parts) >= 2 and parts[0] == "usage_usec":
|
|
164
|
+
try:
|
|
165
|
+
return int(parts[1])
|
|
166
|
+
except ValueError:
|
|
167
|
+
return None
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _read_cgroup_cpu_limit() -> Optional[float]:
|
|
172
|
+
"""Return the allowed CPU (cores) for this cgroup, if limited."""
|
|
173
|
+
if not _CGROUP_CPU_MAX.exists():
|
|
174
|
+
return None
|
|
175
|
+
try:
|
|
176
|
+
data = _CGROUP_CPU_MAX.read_text().strip()
|
|
177
|
+
except (OSError, UnicodeDecodeError):
|
|
178
|
+
return None
|
|
179
|
+
parts = data.split()
|
|
180
|
+
if len(parts) < 2:
|
|
181
|
+
return None
|
|
182
|
+
quota, period = parts[0], parts[1]
|
|
183
|
+
if quota == "max":
|
|
184
|
+
return None
|
|
185
|
+
try:
|
|
186
|
+
quota_value = int(quota)
|
|
187
|
+
period_value = int(period)
|
|
188
|
+
except ValueError:
|
|
189
|
+
return None
|
|
190
|
+
if period_value <= 0:
|
|
191
|
+
return None
|
|
192
|
+
return quota_value / period_value
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _get_cgroup_cpu_percent() -> Optional[float]:
|
|
196
|
+
if not _detect_cgroup_v2():
|
|
197
|
+
return None
|
|
198
|
+
usage = _read_cgroup_cpu_usage()
|
|
199
|
+
if usage is None:
|
|
200
|
+
return None
|
|
201
|
+
limit_cpus = _read_cgroup_cpu_limit()
|
|
202
|
+
if limit_cpus is None or limit_cpus <= 0:
|
|
203
|
+
return None
|
|
204
|
+
now = time.monotonic()
|
|
205
|
+
global _last_cgroup_usage, _last_cgroup_time
|
|
206
|
+
prev_usage = _last_cgroup_usage
|
|
207
|
+
prev_time = _last_cgroup_time
|
|
208
|
+
_last_cgroup_usage = usage
|
|
209
|
+
_last_cgroup_time = now
|
|
210
|
+
if prev_usage is None or prev_time is None:
|
|
211
|
+
return None
|
|
212
|
+
delta_usage = usage - prev_usage
|
|
213
|
+
delta_time = now - prev_time
|
|
214
|
+
if delta_time <= 0 or delta_usage < 0:
|
|
215
|
+
return None
|
|
216
|
+
cpu_ratio = (delta_usage / 1_000_000) / delta_time
|
|
217
|
+
percent = (cpu_ratio / limit_cpus) * 100.0
|
|
218
|
+
return min(percent, 100.0)
|
|
219
|
+
|
|
220
|
+
|
|
133
221
|
def _run_probe_command(cmd: list[str]) -> str | None:
|
|
134
222
|
try:
|
|
135
223
|
result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=3)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
portacode/README.md,sha256=4dKtpvR8LNgZPVz37GmkQCMWIr_u25Ao63iW56s7Ke4,775
|
|
2
2
|
portacode/__init__.py,sha256=oB3sV1wXr-um-RXio73UG8E5Xx6cF2ZVJveqjNmC-vQ,1086
|
|
3
3
|
portacode/__main__.py,sha256=jmHTGC1hzmo9iKJLv-SSYe9BSIbPPZ2IOpecI03PlTs,296
|
|
4
|
-
portacode/_version.py,sha256=
|
|
4
|
+
portacode/_version.py,sha256=ZSjwVSR47mg9NxQs9KfKq7HWjbxgX3-fWsuoIrDnPQc,719
|
|
5
5
|
portacode/cli.py,sha256=mGLKoZ-T2FBF7IA9wUq0zyG0X9__-A1ao7gajjcVRH8,21828
|
|
6
6
|
portacode/data.py,sha256=5-s291bv8J354myaHm1Y7CQZTZyRzMU3TGe5U4hb-FA,1591
|
|
7
7
|
portacode/keypair.py,sha256=0OO4vHDcF1XMxCDqce61xFTlFwlTcmqe5HyGsXFEt7s,5838
|
|
@@ -22,10 +22,10 @@ portacode/connection/handlers/diff_handlers.py,sha256=iYTIRCcpEQ03vIPKZCsMTE5aZb
|
|
|
22
22
|
portacode/connection/handlers/file_handlers.py,sha256=nAJH8nXnX07xxD28ngLpgIUzcTuRwZBNpEGEKdRqohw,39507
|
|
23
23
|
portacode/connection/handlers/project_aware_file_handlers.py,sha256=AqgMnDqX2893T2NsrvUSCwjN5VKj4Pb2TN0S_SuboOE,9803
|
|
24
24
|
portacode/connection/handlers/project_state_handlers.py,sha256=v6ZefGW9i7n1aZLq2jOGumJIjYb6aHlPI4m1jkYewm8,1686
|
|
25
|
-
portacode/connection/handlers/proxmox_infra.py,sha256=
|
|
25
|
+
portacode/connection/handlers/proxmox_infra.py,sha256=v2IftCXnNO8ta8dWfdXf_eRBAeFO6YxkFTCSuTrp5ss,61134
|
|
26
26
|
portacode/connection/handlers/registry.py,sha256=qXGE60sYEWg6ZtVQzFcZ5YI2XWR6lMgw4hAL9x5qR1I,6181
|
|
27
27
|
portacode/connection/handlers/session.py,sha256=uNGfiO_1B9-_yjJKkpvmbiJhIl6b-UXlT86UTfd6WYE,42219
|
|
28
|
-
portacode/connection/handlers/system_handlers.py,sha256=
|
|
28
|
+
portacode/connection/handlers/system_handlers.py,sha256=N3sxNqoWS7KKhAk1nwhPpkCfQv1GbVKenv2NwAYeK34,13477
|
|
29
29
|
portacode/connection/handlers/tab_factory.py,sha256=yn93h6GASjD1VpvW1oqpax3EpoT0r7r97zFXxML1wdA,16173
|
|
30
30
|
portacode/connection/handlers/terminal_handlers.py,sha256=HRwHW1GiqG1NtHVEqXHKaYkFfQEzCDDH6YIlHcb4XD8,11866
|
|
31
31
|
portacode/connection/handlers/test_proxmox_infra.py,sha256=d6iBB4pwAqWWdEGRayLxDEexqCElbGZDJlCB4bXba24,682
|
|
@@ -65,7 +65,7 @@ portacode/utils/__init__.py,sha256=NgBlWTuNJESfIYJzP_3adI1yJQJR0XJLRpSdVNaBAN0,3
|
|
|
65
65
|
portacode/utils/diff_apply.py,sha256=4Oi7ft3VUCKmiUE4VM-OeqO7Gk6H7PF3WnN4WHXtjxI,15157
|
|
66
66
|
portacode/utils/diff_renderer.py,sha256=S76StnQ2DLfsz4Gg0m07UwPfRp8270PuzbNaQq-rmYk,13850
|
|
67
67
|
portacode/utils/ntp_clock.py,sha256=VqCnWCTehCufE43W23oB-WUdAZGeCcLxkmIOPwInYHc,2499
|
|
68
|
-
portacode-1.4.13.
|
|
68
|
+
portacode-1.4.13.dev9.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
|
|
69
69
|
test_modules/README.md,sha256=Do_agkm9WhSzueXjRAkV_xEj6Emy5zB3N3VKY5Roce8,9274
|
|
70
70
|
test_modules/__init__.py,sha256=1LcbHodIHsB0g-g4NGjSn6AMuCoGbymvXPYLOb6Z7F0,53
|
|
71
71
|
test_modules/test_device_online.py,sha256=QtYq0Dq9vME8Gp2O4fGSheqVf8LUtpsSKosXXk56gGM,1654
|
|
@@ -91,8 +91,8 @@ testing_framework/core/playwright_manager.py,sha256=Tw46qwxIhOFkS48C2IWIQHHNpEe-
|
|
|
91
91
|
testing_framework/core/runner.py,sha256=j2QwNJmAxVBmJvcbVS7DgPJUKPNzqfLmt_4NNdaKmZU,19297
|
|
92
92
|
testing_framework/core/shared_cli_manager.py,sha256=BESSNtyQb7BOlaOvZmm04T8Uezjms4KCBs2MzTxvzYQ,8790
|
|
93
93
|
testing_framework/core/test_discovery.py,sha256=2FZ9fJ8Dp5dloA-fkgXoJ_gCMC_nYPBnA3Hs2xlagzM,4928
|
|
94
|
-
portacode-1.4.13.
|
|
95
|
-
portacode-1.4.13.
|
|
96
|
-
portacode-1.4.13.
|
|
97
|
-
portacode-1.4.13.
|
|
98
|
-
portacode-1.4.13.
|
|
94
|
+
portacode-1.4.13.dev9.dist-info/METADATA,sha256=XrvTrxRde9mNvinPiBvLoJX-dzUZKdqJmY1QY4gbxCc,13051
|
|
95
|
+
portacode-1.4.13.dev9.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
96
|
+
portacode-1.4.13.dev9.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
|
|
97
|
+
portacode-1.4.13.dev9.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
|
|
98
|
+
portacode-1.4.13.dev9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|