portacode 1.4.13.dev8__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/system_handlers.py +90 -2
- {portacode-1.4.13.dev8.dist-info → portacode-1.4.13.dev9.dist-info}/METADATA +1 -1
- {portacode-1.4.13.dev8.dist-info → portacode-1.4.13.dev9.dist-info}/RECORD +8 -8
- {portacode-1.4.13.dev8.dist-info → portacode-1.4.13.dev9.dist-info}/WHEEL +0 -0
- {portacode-1.4.13.dev8.dist-info → portacode-1.4.13.dev9.dist-info}/entry_points.txt +0 -0
- {portacode-1.4.13.dev8.dist-info → portacode-1.4.13.dev9.dist-info}/licenses/LICENSE +0 -0
- {portacode-1.4.13.dev8.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
|
|
@@ -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
|
|
@@ -25,7 +25,7 @@ portacode/connection/handlers/project_state_handlers.py,sha256=v6ZefGW9i7n1aZLq2
|
|
|
25
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
|