machineconfig 6.45__py3-none-any.whl → 6.48__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.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/scripts/python/cloud.py +3 -3
- machineconfig/scripts/python/croshell.py +8 -6
- machineconfig/scripts/python/devops.py +6 -6
- machineconfig/scripts/python/devops_navigator.py +1 -1
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/ftpx.py +1 -1
- machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_copy.py +2 -2
- machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_sync.py +3 -3
- machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/helpers2.py +1 -1
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_config.py +3 -3
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_data.py +2 -2
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_nw.py +2 -2
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_repos.py +10 -10
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_self.py +4 -4
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/devops_backup_retrieve.py +1 -1
- machineconfig/scripts/python/{devops_helpers → helpers_devops}/devops_update_repos.py +1 -1
- machineconfig/scripts/python/helpers_navigator/__init__.py +20 -0
- machineconfig/scripts/python/{helper_navigator → helpers_navigator}/command_builder.py +1 -1
- machineconfig/scripts/python/{helper_navigator → helpers_navigator}/command_detail.py +1 -1
- machineconfig/scripts/python/{helper_navigator → helpers_navigator}/command_tree.py +1 -1
- machineconfig/scripts/python/{helper_navigator → helpers_navigator}/main_app.py +5 -5
- machineconfig/scripts/python/{repos_helpers → helpers_repos}/action.py +1 -1
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +3 -3
- machineconfig/scripts/python/{repos_helpers → helpers_repos}/count_lines_frontend.py +2 -2
- machineconfig/scripts/python/{repos_helpers → helpers_repos}/entrypoint.py +2 -2
- machineconfig/scripts/python/interactive.py +3 -3
- machineconfig/scripts/python/nw/mount_nfs +1 -1
- machineconfig/scripts/python/sessions.py +1 -1
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +1 -1
- machineconfig/utils/code.py +28 -67
- machineconfig/utils/meta.py +67 -34
- machineconfig/utils/procs.py +10 -23
- machineconfig/utils/scheduler.py +30 -36
- machineconfig/utils/ssh.py +1 -1
- {machineconfig-6.45.dist-info → machineconfig-6.48.dist-info}/METADATA +1 -1
- {machineconfig-6.45.dist-info → machineconfig-6.48.dist-info}/RECORD +70 -71
- machineconfig/scripts/python/helper_navigator/__init__.py +0 -20
- machineconfig/scripts/python/sessions_helpers/__init__.py +0 -0
- /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/__init__.py +0 -0
- /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_helpers.py +0 -0
- /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/cloud_mount.py +0 -0
- /machineconfig/scripts/python/{cloud_helpers → helpers_cloud}/helpers5.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/__init__.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/crosh.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/pomodoro.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/scheduler.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/start_slidev.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/viewer.py +0 -0
- /machineconfig/scripts/python/{croshell_helpers → helpers_croshell}/viewer_template.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/__init__.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_config_dotfile.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_share_server.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_terminal.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/cli_utils.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/devops_status.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/themes/__init__.py +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/themes/choose_pwsh_theme.ps1 +0 -0
- /machineconfig/scripts/python/{devops_helpers → helpers_devops}/themes/choose_wezterm_theme.py +0 -0
- /machineconfig/scripts/python/{helper_navigator → helpers_navigator}/data_models.py +0 -0
- /machineconfig/scripts/python/{helper_navigator → helpers_navigator}/search_bar.py +0 -0
- /machineconfig/scripts/python/{repos_helpers → helpers_repos}/clone.py +0 -0
- /machineconfig/scripts/python/{repos_helpers → helpers_repos}/count_lines.py +0 -0
- /machineconfig/scripts/python/{repos_helpers → helpers_repos}/record.py +0 -0
- /machineconfig/scripts/python/{repos_helpers → helpers_repos}/sync.py +0 -0
- /machineconfig/scripts/python/{repos_helpers → helpers_repos}/update.py +0 -0
- /machineconfig/scripts/python/{helpers_repos → helpers_sessions}/__init__.py +0 -0
- /machineconfig/scripts/python/{sessions_helpers → helpers_sessions}/sessions_multiprocess.py +0 -0
- {machineconfig-6.45.dist-info → machineconfig-6.48.dist-info}/WHEEL +0 -0
- {machineconfig-6.45.dist-info → machineconfig-6.48.dist-info}/entry_points.txt +0 -0
- {machineconfig-6.45.dist-info → machineconfig-6.48.dist-info}/top_level.txt +0 -0
machineconfig/utils/code.py
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
+
import atexit
|
|
1
2
|
import platform
|
|
2
3
|
from typing import Optional
|
|
3
4
|
import subprocess
|
|
4
|
-
import os
|
|
5
|
-
# import time
|
|
6
5
|
from rich.console import Console
|
|
7
6
|
from rich.panel import Panel
|
|
8
7
|
from rich.syntax import Syntax
|
|
9
8
|
|
|
10
9
|
from machineconfig.utils.accessories import randstr
|
|
11
10
|
from machineconfig.utils.ve import get_ve_activate_line
|
|
12
|
-
from machineconfig.utils.path_extended import PathExtended
|
|
11
|
+
from machineconfig.utils.path_extended import PathExtended # type: ignore[import-not-found]
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
def get_shell_script_executing_python_file(python_file: str, func: Optional[str], ve_path: Optional[str], executable: Optional[str], strict_execution: bool = True):
|
|
@@ -138,71 +137,33 @@ def run_shell_script(script: str, display_script: bool = True, clean_env: bool =
|
|
|
138
137
|
return proc
|
|
139
138
|
|
|
140
139
|
|
|
141
|
-
def run_shell_script_after_exit(script: str,
|
|
142
|
-
current_pid = os.getpid()
|
|
140
|
+
def run_shell_script_after_exit(script: str, display_script: bool = True) -> None:
|
|
143
141
|
console = Console()
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
""
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
check_interval={check_interval}
|
|
170
|
-
|
|
171
|
-
echo "🔍 Monitoring process PID: $target_pid"
|
|
172
|
-
|
|
173
|
-
while kill -0 $target_pid 2>/dev/null; do
|
|
174
|
-
sleep $check_interval
|
|
175
|
-
done
|
|
176
|
-
|
|
177
|
-
echo "✅ Process $target_pid has exited. Running script..."
|
|
178
|
-
|
|
179
|
-
# Execute the provided script
|
|
180
|
-
{script}
|
|
181
|
-
"""
|
|
182
|
-
suffix = ".sh"
|
|
183
|
-
cmd = ["bash"]
|
|
184
|
-
|
|
185
|
-
monitor_script_path = PathExtended.tmp().joinpath("tmp_scripts", "monitor", randstr() + suffix)
|
|
186
|
-
monitor_script_path.parent.mkdir(parents=True, exist_ok=True)
|
|
187
|
-
monitor_script_path.write_text(monitor_script, encoding="utf-8")
|
|
188
|
-
|
|
189
|
-
if display_script:
|
|
190
|
-
lexer = "powershell" if platform.system() == "Windows" else "bash"
|
|
191
|
-
console.print(Panel(Syntax(code=monitor_script, lexer=lexer), title=f"📄 Monitor script @ {monitor_script_path}", subtitle="Will run after current process exits"), style="bold yellow")
|
|
192
|
-
|
|
193
|
-
if platform.system() != "Windows":
|
|
194
|
-
monitor_script_path.chmod(0o755)
|
|
195
|
-
|
|
196
|
-
cmd.append(str(monitor_script_path))
|
|
197
|
-
|
|
198
|
-
if platform.system() == "Windows":
|
|
199
|
-
creation_flags = subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS # type: ignore
|
|
200
|
-
process = subprocess.Popen(cmd, creationflags=creation_flags, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
|
|
201
|
-
else:
|
|
202
|
-
process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, start_new_session=True)
|
|
203
|
-
if display_script:
|
|
204
|
-
console.print(f"🚀 [green]Monitor process started with PID:[/green] [blue]{process.pid}[/blue]")
|
|
205
|
-
console.print(f"📍 [yellow]Watching PID:[/yellow] [blue]{current_pid}[/blue]")
|
|
143
|
+
def execute_script_at_exit() -> None:
|
|
144
|
+
if platform.system() == "Windows":
|
|
145
|
+
suffix = ".ps1"
|
|
146
|
+
lexer = "powershell"
|
|
147
|
+
else:
|
|
148
|
+
suffix = ".sh"
|
|
149
|
+
lexer = "bash"
|
|
150
|
+
|
|
151
|
+
script_path = PathExtended.tmp().joinpath("tmp_scripts", "exit", randstr() + suffix)
|
|
152
|
+
script_path.parent.mkdir(parents=True, exist_ok=True)
|
|
153
|
+
script_path.write_text(script, encoding="utf-8")
|
|
154
|
+
|
|
155
|
+
if display_script:
|
|
156
|
+
console.print(Panel(Syntax(code=script, lexer=lexer), title=f"📄 Exit script @ {script_path}", subtitle="Running at exit"), style="bold yellow")
|
|
157
|
+
|
|
158
|
+
if platform.system() != "Windows":
|
|
159
|
+
script_path.chmod(0o755)
|
|
160
|
+
|
|
161
|
+
if platform.system() == "Windows":
|
|
162
|
+
subprocess.run(["powershell", "-ExecutionPolicy", "Bypass", "-NoProfile", "-File", str(script_path)], check=False)
|
|
163
|
+
else:
|
|
164
|
+
subprocess.run(["bash", str(script_path)], check=False)
|
|
165
|
+
|
|
166
|
+
script_path.unlink(missing_ok=True)
|
|
206
167
|
|
|
207
|
-
|
|
168
|
+
atexit.register(execute_script_at_exit)
|
|
208
169
|
|
machineconfig/utils/meta.py
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
import ast
|
|
4
4
|
import inspect
|
|
5
5
|
import textwrap
|
|
6
|
+
from collections.abc import Callable, Mapping
|
|
6
7
|
from types import FunctionType, ModuleType
|
|
7
|
-
from typing import
|
|
8
|
+
from typing import ParamSpec
|
|
8
9
|
|
|
10
|
+
P = ParamSpec("P")
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
|
|
13
|
+
def function_to_script(func: Callable[P, object], call_with_kwargs: Mapping[str, object] | None) -> str:
|
|
11
14
|
"""Convert a function to a standalone executable Python script.
|
|
12
15
|
|
|
13
16
|
This function analyzes a given function and generates a complete Python script
|
|
@@ -16,7 +19,6 @@ def function_to_script(func: FunctionType, call_with_args: tuple[Any, ...] | Non
|
|
|
16
19
|
|
|
17
20
|
Args:
|
|
18
21
|
func: The function to convert to a script
|
|
19
|
-
call_with_args: Optional tuple of positional arguments to call the function with
|
|
20
22
|
call_with_kwargs: Optional dict of keyword arguments to call the function with
|
|
21
23
|
|
|
22
24
|
Returns:
|
|
@@ -25,35 +27,37 @@ def function_to_script(func: FunctionType, call_with_args: tuple[Any, ...] | Non
|
|
|
25
27
|
Raises:
|
|
26
28
|
ValueError: If the function cannot be inspected or analyzed
|
|
27
29
|
"""
|
|
28
|
-
if not
|
|
29
|
-
raise ValueError(f"Expected a function, got {type(func)}")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
call_statement = _generate_call_statement(
|
|
38
|
-
|
|
30
|
+
if not isinstance(func, FunctionType):
|
|
31
|
+
raise ValueError(f"""Expected a Python function, got {type(func)}""")
|
|
32
|
+
|
|
33
|
+
python_func = func
|
|
34
|
+
|
|
35
|
+
imports = _extract_imports(python_func)
|
|
36
|
+
globals_needed = _extract_globals(python_func)
|
|
37
|
+
source_code = _get_function_source(python_func).rstrip()
|
|
38
|
+
validated_kwargs = _prepare_call_kwargs(python_func, call_with_kwargs)
|
|
39
|
+
call_statement = _generate_call_statement(python_func, validated_kwargs) if validated_kwargs is not None else None
|
|
40
|
+
|
|
39
41
|
script_parts: list[str] = []
|
|
40
|
-
|
|
42
|
+
|
|
41
43
|
if imports:
|
|
42
44
|
script_parts.append(imports)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
if globals_needed:
|
|
47
|
+
if script_parts:
|
|
48
|
+
script_parts.append("")
|
|
46
49
|
script_parts.append(globals_needed)
|
|
50
|
+
|
|
51
|
+
if script_parts:
|
|
47
52
|
script_parts.append("")
|
|
48
|
-
|
|
53
|
+
|
|
49
54
|
script_parts.append(source_code)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if call_statement:
|
|
55
|
+
|
|
56
|
+
if call_statement is not None:
|
|
53
57
|
script_parts.append("")
|
|
54
58
|
script_parts.append("if __name__ == '__main__':")
|
|
55
59
|
script_parts.append(f" {call_statement}")
|
|
56
|
-
|
|
60
|
+
|
|
57
61
|
return "\n".join(script_parts)
|
|
58
62
|
|
|
59
63
|
|
|
@@ -149,18 +153,47 @@ def _extract_globals(func: FunctionType) -> str:
|
|
|
149
153
|
return "\n".join(global_assignments)
|
|
150
154
|
|
|
151
155
|
|
|
152
|
-
def
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
def _prepare_call_kwargs(func: FunctionType, call_with_kwargs: Mapping[str, object] | None) -> dict[str, object] | None:
|
|
157
|
+
if call_with_kwargs is None:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
normalized_kwargs = dict(call_with_kwargs)
|
|
161
|
+
|
|
162
|
+
if not normalized_kwargs:
|
|
163
|
+
return {}
|
|
164
|
+
|
|
165
|
+
signature = inspect.signature(func)
|
|
166
|
+
positional_only = [parameter.name for parameter in signature.parameters.values() if parameter.kind is inspect.Parameter.POSITIONAL_ONLY]
|
|
167
|
+
|
|
168
|
+
if positional_only:
|
|
169
|
+
joined = ", ".join(positional_only)
|
|
170
|
+
raise ValueError(f"""Cannot call {func.__name__} with positional-only parameters: {joined}""")
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
signature.bind(**normalized_kwargs)
|
|
174
|
+
except TypeError as error:
|
|
175
|
+
raise ValueError(f"""Invalid call_with_kwargs for {func.__name__}: {error}""") from error
|
|
176
|
+
|
|
177
|
+
return normalized_kwargs
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _generate_call_statement(func: FunctionType, kwargs: dict[str, object]) -> str:
|
|
181
|
+
"""Generate a function call statement with the given keyword arguments."""
|
|
182
|
+
if not kwargs:
|
|
155
183
|
return f"{func.__name__}()"
|
|
156
|
-
|
|
157
|
-
arg_parts: list[str] = []
|
|
158
|
-
|
|
159
|
-
for arg in args:
|
|
160
|
-
arg_parts.append(repr(arg))
|
|
161
|
-
|
|
162
|
-
for key, value in kwargs.items():
|
|
163
|
-
arg_parts.append(f"{key}={repr(value)}")
|
|
164
|
-
|
|
184
|
+
|
|
185
|
+
arg_parts: list[str] = [f"{key}={repr(value)}" for key, value in kwargs.items()]
|
|
165
186
|
args_str = ", ".join(arg_parts)
|
|
166
187
|
return f"{func.__name__}({args_str})"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
# Example usage
|
|
192
|
+
a = True
|
|
193
|
+
b = 3
|
|
194
|
+
def func(no_copy_assets: bool = a):
|
|
195
|
+
from machineconfig.scripts.python.helpers_devops.cli_self import update
|
|
196
|
+
update(no_copy_assets=no_copy_assets)
|
|
197
|
+
script = function_to_script(func, call_with_kwargs=None)
|
|
198
|
+
print(script)
|
|
199
|
+
pass
|
machineconfig/utils/procs.py
CHANGED
|
@@ -106,51 +106,45 @@ class ProcessManager:
|
|
|
106
106
|
"""Format process data as table string for display."""
|
|
107
107
|
if not self.data:
|
|
108
108
|
return ""
|
|
109
|
-
|
|
110
109
|
# Create header
|
|
111
110
|
_headers = ["Command", "PID", "Name", "Username", "CPU%", "Memory(MB)", "Status", "Create Time"]
|
|
112
111
|
header_line = f"{'Command':<50} {'PID':<8} {'Name':<20} {'Username':<12} {'CPU%':<8} {'Memory(MB)':<12} {'Status':<12} {'Create Time':<20}"
|
|
113
112
|
separator = "-" * len(header_line)
|
|
114
|
-
|
|
115
113
|
lines = [header_line, separator]
|
|
116
|
-
|
|
117
114
|
for process in self.data:
|
|
118
115
|
# Format create_time as string
|
|
119
116
|
create_time_str = process["create_time"].strftime("%Y-%m-%d %H:%M:%S")
|
|
120
117
|
# Truncate command if too long
|
|
121
118
|
command = process["command"][:47] + "..." if len(process["command"]) > 50 else process["command"]
|
|
122
|
-
|
|
123
119
|
line = f"{command:<50} {process['pid']:<8} {process['name'][:19]:<20} {process['username'][:11]:<12} {process['cpu_percent']:<8.1f} {process['memory_usage_mb']:<12.2f} {process['status'][:11]:<12} {create_time_str:<20}"
|
|
124
120
|
lines.append(line)
|
|
125
|
-
|
|
126
121
|
return "\n".join(lines)
|
|
127
122
|
|
|
128
123
|
def choose_and_kill(self):
|
|
129
124
|
# header for interactive process selection
|
|
130
125
|
title = "🎯 INTERACTIVE PROCESS SELECTION AND TERMINATION"
|
|
131
126
|
console.print(Panel(title, title="[bold blue]Process Info[/bold blue]", border_style="blue"))
|
|
132
|
-
|
|
133
127
|
# Format data as table for display
|
|
134
128
|
formatted_data = self._format_process_table()
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
129
|
+
all_lines = formatted_data.split("\n")
|
|
130
|
+
header_and_separator = all_lines[:2] # First two lines: header and separator
|
|
131
|
+
options = all_lines[2:] # Skip header and separator, only process lines
|
|
132
|
+
res = choose_from_options(options=all_lines, msg="📋 Select processes to manage:", fzf=True, multi=True)
|
|
133
|
+
# Filter out header and separator if they were selected
|
|
134
|
+
selected_lines = [line for line in res if line not in header_and_separator]
|
|
135
|
+
indices = [options.index(val) for val in selected_lines]
|
|
138
136
|
selected_processes = [self.data[i] for i in indices]
|
|
139
|
-
|
|
140
137
|
print("\n📊 All Processes:")
|
|
141
138
|
print(formatted_data)
|
|
142
139
|
print("\n🎯 Selected Processes:")
|
|
143
140
|
for process in selected_processes:
|
|
144
141
|
print(f"PID: {process['pid']}, Name: {process['name']}, Memory: {process['memory_usage_mb']:.2f}MB")
|
|
145
|
-
|
|
146
142
|
for idx, process in enumerate(selected_processes):
|
|
147
143
|
pprint(dict(process), f"📌 Process {idx}")
|
|
148
|
-
|
|
149
144
|
kill_all = input("\n⚠️ Confirm killing ALL selected processes? y/[n] ").lower() == "y"
|
|
150
145
|
if kill_all:
|
|
151
146
|
self.kill(pids=[p["pid"] for p in selected_processes])
|
|
152
147
|
return
|
|
153
|
-
|
|
154
148
|
kill_by_index = input("\n🔫 Kill by index? (enter numbers separated by spaces, e.g. '1 4') or [n] to cancel: ")
|
|
155
149
|
if kill_by_index != "" and kill_by_index != "n":
|
|
156
150
|
indices = [int(val) for val in kill_by_index.split(" ")]
|
|
@@ -164,12 +158,10 @@ class ProcessManager:
|
|
|
164
158
|
# header for filtering processes by name
|
|
165
159
|
title = "🔍 FILTERING AND TERMINATING PROCESSES BY NAME"
|
|
166
160
|
console.print(Panel(title, title="[bold blue]Process Info[/bold blue]", border_style="blue"))
|
|
167
|
-
|
|
168
161
|
# Filter processes by name
|
|
169
162
|
filtered_processes = [p for p in self.data if p["name"] == name]
|
|
170
163
|
# Sort by create_time (ascending)
|
|
171
164
|
filtered_processes.sort(key=lambda x: x["create_time"])
|
|
172
|
-
|
|
173
165
|
print(f"🎯 Found {len(filtered_processes)} processes matching name: '{name}'")
|
|
174
166
|
self.kill(pids=[p["pid"] for p in filtered_processes])
|
|
175
167
|
console.print(Panel("", title="[bold blue]Process Info[/bold blue]", border_style="blue"))
|
|
@@ -186,40 +178,35 @@ class ProcessManager:
|
|
|
186
178
|
pids = []
|
|
187
179
|
if commands is None:
|
|
188
180
|
commands = []
|
|
189
|
-
|
|
190
181
|
killed_count = 0
|
|
191
|
-
|
|
192
182
|
for name in names:
|
|
193
183
|
matching_processes = [p for p in self.data if p["name"] == name]
|
|
194
184
|
if len(matching_processes) > 0:
|
|
195
185
|
for process in matching_processes:
|
|
196
186
|
psutil.Process(process["pid"]).kill()
|
|
197
|
-
print(f"💀 Killed process {name} with PID {process['pid']}. It lived {get_age(process['create_time'])}. RIP
|
|
187
|
+
print(f"💀 Killed process {name} with PID {process['pid']}. It lived {get_age(process['create_time'])}. RIP 💐")
|
|
198
188
|
killed_count += 1
|
|
199
189
|
else:
|
|
200
190
|
print(f'❓ No process named "{name}" found')
|
|
201
|
-
|
|
202
191
|
for pid in pids:
|
|
203
192
|
try:
|
|
204
193
|
proc = psutil.Process(pid)
|
|
205
194
|
proc_name = proc.name()
|
|
206
195
|
proc_lifetime = get_age(datetime.fromtimestamp(proc.create_time(), tz=None))
|
|
207
196
|
proc.kill()
|
|
208
|
-
print(f'💀 Killed process with PID {pid} and name "{proc_name}". It lived {proc_lifetime}. RIP
|
|
197
|
+
print(f'💀 Killed process with PID {pid} and name "{proc_name}". It lived {proc_lifetime}. RIP 💐')
|
|
209
198
|
killed_count += 1
|
|
210
199
|
except psutil.NoSuchProcess:
|
|
211
200
|
print(f"❓ No process with PID {pid} found")
|
|
212
|
-
|
|
213
201
|
for command in commands:
|
|
214
202
|
matching_processes = [p for p in self.data if command in p["command"]]
|
|
215
203
|
if len(matching_processes) > 0:
|
|
216
204
|
for process in matching_processes:
|
|
217
205
|
psutil.Process(process["pid"]).kill()
|
|
218
|
-
print(f'💀 Killed process with "{command}" in its command & PID {process["pid"]}. It lived {get_age(process["create_time"])}. RIP
|
|
206
|
+
print(f'💀 Killed process with "{command}" in its command & PID {process["pid"]}. It lived {get_age(process["create_time"])}. RIP 💐')
|
|
219
207
|
killed_count += 1
|
|
220
208
|
else:
|
|
221
209
|
print(f'❓ No process has "{command}" in its command.')
|
|
222
|
-
|
|
223
210
|
console.print(Panel(f"✅ Termination complete: {killed_count} processes terminated", title="[bold blue]Process Info[/bold blue]", border_style="blue"))
|
|
224
211
|
|
|
225
212
|
|
machineconfig/utils/scheduler.py
CHANGED
|
@@ -170,35 +170,32 @@ class CacheMemory[T]():
|
|
|
170
170
|
def __call__(self, fresh: bool = False) -> T:
|
|
171
171
|
self.last_call_is_fresh = False
|
|
172
172
|
if fresh or not hasattr(self, "cache"):
|
|
173
|
-
if
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
🆕 ════════════════════ NEW CACHE ════════════════════
|
|
177
|
-
|
|
178
|
-
ℹ️ Reason: {why}
|
|
179
|
-
════════════════════════════════════════════════════════""")
|
|
173
|
+
why = "There was an explicit fresh order." if fresh else "Previous cache never existed."
|
|
174
|
+
t0 = time.time()
|
|
175
|
+
self.logger.warning(f"""
|
|
176
|
+
🆕 ════════════════════ NEW {self.name} CACHE ════════════════════
|
|
177
|
+
ℹ️ Reason: {why}""")
|
|
180
178
|
self.cache = self.source_func()
|
|
179
|
+
self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
|
|
181
180
|
self.last_call_is_fresh = True
|
|
182
181
|
self.time_produced = datetime.now()
|
|
183
182
|
else:
|
|
184
183
|
age = self.age
|
|
185
184
|
if age > self.expire:
|
|
186
|
-
|
|
187
|
-
self.logger.warning(f"""
|
|
185
|
+
self.logger.warning(f"""
|
|
188
186
|
🔄 ════════════════════ CACHE UPDATE ════════════════════
|
|
189
187
|
⚠️ {self.name} cache: Updating cache from source func
|
|
190
|
-
⏱️ Age = {age} > {self.expire}
|
|
191
|
-
|
|
188
|
+
⏱️ Age = {age} > {self.expire}""")
|
|
189
|
+
t0 = time.time()
|
|
192
190
|
self.cache = self.source_func()
|
|
191
|
+
self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
|
|
193
192
|
self.last_call_is_fresh = True
|
|
194
193
|
self.time_produced = datetime.now()
|
|
195
194
|
else:
|
|
196
|
-
|
|
197
|
-
self.logger.warning(f"""
|
|
195
|
+
self.logger.warning(f"""
|
|
198
196
|
✅ ════════════════════ USING CACHE ════════════════════
|
|
199
197
|
📦 {self.name} cache: Using cached values
|
|
200
|
-
⏱️ Lag = {age}
|
|
201
|
-
════════════════════════════════════════════════════════""")
|
|
198
|
+
⏱️ Lag = {age}""")
|
|
202
199
|
return self.cache
|
|
203
200
|
|
|
204
201
|
@staticmethod
|
|
@@ -236,19 +233,18 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
|
|
|
236
233
|
msg1 = f"""
|
|
237
234
|
📦 ════════════════════ CACHE OPERATION ════════════════════
|
|
238
235
|
🔄 {self.name} cache: Reading cached values from `{self.path}`
|
|
239
|
-
⏱️ Lag = {age}
|
|
240
|
-
════════════════════════════════════════════════════════════"""
|
|
236
|
+
⏱️ Lag = {age}"""
|
|
241
237
|
try:
|
|
242
238
|
self.cache = self.reader(self.path)
|
|
243
239
|
except Exception as ex:
|
|
244
|
-
|
|
245
|
-
msg2 = f"""
|
|
240
|
+
msg2 = f"""
|
|
246
241
|
❌ ════════════════════ CACHE ERROR ════════════════════
|
|
247
242
|
⚠️ {self.name} cache: Cache file is corrupted
|
|
248
|
-
🔍 Error: {ex}
|
|
249
|
-
|
|
250
|
-
|
|
243
|
+
🔍 Error: {ex}"""
|
|
244
|
+
self.logger.warning(msg1 + msg2)
|
|
245
|
+
t0 = time.time()
|
|
251
246
|
self.cache = self.source_func()
|
|
247
|
+
self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
|
|
252
248
|
self.last_call_is_fresh = True
|
|
253
249
|
self.time_produced = datetime.now()
|
|
254
250
|
# if self.path is not None:
|
|
@@ -256,15 +252,15 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
|
|
|
256
252
|
return self.cache
|
|
257
253
|
return self(fresh=False) # may be the cache is old ==> check that by passing it through the logic again.
|
|
258
254
|
else:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
self.logger.warning(f"""
|
|
255
|
+
# Previous cache never existed or there was an explicit fresh order.
|
|
256
|
+
why = "There was an explicit fresh order." if fresh else "Previous cache never existed or is corrupted."
|
|
257
|
+
self.logger.warning(f"""
|
|
263
258
|
🆕 ════════════════════ NEW CACHE ════════════════════
|
|
264
259
|
🔄 {self.name} cache: Populating fresh cache from source func
|
|
265
|
-
ℹ️ Reason: {why}
|
|
266
|
-
|
|
260
|
+
ℹ️ Reason: {why}""")
|
|
261
|
+
t0 = time.time()
|
|
267
262
|
self.cache = self.source_func() # fresh data.
|
|
263
|
+
self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
|
|
268
264
|
self.last_call_is_fresh = True
|
|
269
265
|
self.time_produced = datetime.now()
|
|
270
266
|
self.save(self.cache, self.path)
|
|
@@ -274,23 +270,21 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
|
|
|
274
270
|
except AttributeError: # path doesn't exist (may be deleted) ==> need to repopulate cache form source_func.
|
|
275
271
|
return self(fresh=True)
|
|
276
272
|
if age > self.expire:
|
|
277
|
-
|
|
278
|
-
self.logger.warning(f"""
|
|
273
|
+
self.logger.warning(f"""
|
|
279
274
|
🔄 ════════════════════ CACHE UPDATE ════════════════════
|
|
280
275
|
⚠️ {self.name} cache: Updating cache from source func
|
|
281
|
-
⏱️ Age = {age} > {self.expire}
|
|
282
|
-
|
|
276
|
+
⏱️ Age = {age} > {self.expire}""")
|
|
277
|
+
t0 = time.time()
|
|
283
278
|
self.cache = self.source_func()
|
|
279
|
+
self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
|
|
284
280
|
self.last_call_is_fresh = True
|
|
285
281
|
self.time_produced = datetime.now()
|
|
286
282
|
self.save(self.cache, self.path)
|
|
287
283
|
else:
|
|
288
|
-
|
|
289
|
-
self.logger.warning(f"""
|
|
284
|
+
self.logger.warning(f"""
|
|
290
285
|
✅ ════════════════════ USING CACHE ════════════════════
|
|
291
286
|
📦 {self.name} cache: Using cached values
|
|
292
|
-
⏱️ Lag = {age}
|
|
293
|
-
════════════════════════════════════════════════════════""")
|
|
287
|
+
⏱️ Lag = {age}""")
|
|
294
288
|
return self.cache
|
|
295
289
|
|
|
296
290
|
@staticmethod
|
machineconfig/utils/ssh.py
CHANGED
|
@@ -6,7 +6,7 @@ from machineconfig.utils.terminal import Response, MACHINE
|
|
|
6
6
|
from machineconfig.utils.accessories import pprint
|
|
7
7
|
|
|
8
8
|
UV_RUN_CMD = "$HOME/.local/bin/uv run"
|
|
9
|
-
MACHINECONFIG_VERSION = "machineconfig>=6.
|
|
9
|
+
MACHINECONFIG_VERSION = "machineconfig>=6.48"
|
|
10
10
|
DEFAULT_PICKLE_SUBDIR = "tmp_results/tmp_scripts/ssh"
|
|
11
11
|
|
|
12
12
|
|