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 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.dev7'
32
- __version_tuple__ = version_tuple = (1, 4, 13, 'dev7')
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
- _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,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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.13.dev7
3
+ Version: 1.4.13.dev9
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=iXNm9XA0Hf5UtyMc434neHqt0hbFCvLPPvzu1ohmHu4,719
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=vvu1ZGVNxGJ0qoG-cobgVtqHH58Lr_5WO11Un4txFOs,60671
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=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.dev7.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
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.dev7.dist-info/METADATA,sha256=wsfXxckiSZD5SHEvrxPJGx6EGfCxscZBAsGjC5Ivur4,13051
95
- portacode-1.4.13.dev7.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
96
- portacode-1.4.13.dev7.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.13.dev7.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.13.dev7.dist-info/RECORD,,
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,,