portacode 1.4.13.dev8__py3-none-any.whl → 1.4.13.dev10__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 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.dev8'
32
- __version_tuple__ = version_tuple = (1, 4, 13, 'dev8')
31
+ __version__ = version = '1.4.13.dev10'
32
+ __version_tuple__ = version_tuple = (1, 4, 13, 'dev10')
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
- _cpu_percent = psutil.cpu_percent(interval=5.0)
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,81 @@ 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
+ now = time.monotonic()
202
+ global _last_cgroup_usage, _last_cgroup_time
203
+ prev_usage = _last_cgroup_usage
204
+ prev_time = _last_cgroup_time
205
+ _last_cgroup_usage = usage
206
+ _last_cgroup_time = now
207
+ if prev_usage is None or prev_time is None:
208
+ return None
209
+ delta_usage = usage - prev_usage
210
+ delta_time = now - prev_time
211
+ if delta_time <= 0 or delta_usage < 0:
212
+ return None
213
+ cpu_ratio = (delta_usage / 1_000_000) / delta_time
214
+ limit_cpus = _read_cgroup_cpu_limit()
215
+ if limit_cpus and limit_cpus > 0:
216
+ percent = (cpu_ratio / limit_cpus) * 100.0
217
+ else:
218
+ cpu_count = psutil.cpu_count(logical=True) or 1
219
+ percent = (cpu_ratio / cpu_count) * 100.0
220
+ return max(0.0, min(percent, 100.0))
221
+
222
+
133
223
  def _run_probe_command(cmd: list[str]) -> str | None:
134
224
  try:
135
225
  result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=3)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.13.dev8
3
+ Version: 1.4.13.dev10
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -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=YBDBPaROqFisxZ_FDGVz2X8528vah3gRidVgpzkvUbc,719
4
+ portacode/_version.py,sha256=U2EKSWYRJkwX2iDCL-xl_a57oBp4wmZQrD95MJrxg_s,721
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=AKh7IbwptlLYrbSw5f-DHigvlaKHsg9lDP-lkAUm8cE,10755
28
+ portacode/connection/handlers/system_handlers.py,sha256=mH3kxmOBInnnHtwwNHYjvU80U3Ok5TzpsGyIbSRA4QE,13579
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.dev8.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.13.dev10.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.dev8.dist-info/METADATA,sha256=7RHT2uUSDkC1UZZH_jl86SqIgQejC5TSkm3MsNneI_M,13051
95
- portacode-1.4.13.dev8.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
96
- portacode-1.4.13.dev8.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.13.dev8.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.13.dev8.dist-info/RECORD,,
94
+ portacode-1.4.13.dev10.dist-info/METADATA,sha256=Wyj8cxL1Yfo287OncA94KT2rzqpDN4-QE67BBuB9agM,13052
95
+ portacode-1.4.13.dev10.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
96
+ portacode-1.4.13.dev10.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.13.dev10.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.13.dev10.dist-info/RECORD,,