mlx-stack 0.1.0__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.
- mlx_stack/__init__.py +5 -0
- mlx_stack/_version.py +24 -0
- mlx_stack/cli/__init__.py +5 -0
- mlx_stack/cli/bench.py +221 -0
- mlx_stack/cli/config.py +166 -0
- mlx_stack/cli/down.py +109 -0
- mlx_stack/cli/init.py +180 -0
- mlx_stack/cli/install.py +165 -0
- mlx_stack/cli/logs.py +234 -0
- mlx_stack/cli/main.py +187 -0
- mlx_stack/cli/models.py +304 -0
- mlx_stack/cli/profile.py +65 -0
- mlx_stack/cli/pull.py +134 -0
- mlx_stack/cli/recommend.py +397 -0
- mlx_stack/cli/status.py +111 -0
- mlx_stack/cli/up.py +163 -0
- mlx_stack/cli/watch.py +252 -0
- mlx_stack/core/__init__.py +1 -0
- mlx_stack/core/benchmark.py +1182 -0
- mlx_stack/core/catalog.py +560 -0
- mlx_stack/core/config.py +471 -0
- mlx_stack/core/deps.py +323 -0
- mlx_stack/core/hardware.py +304 -0
- mlx_stack/core/launchd.py +531 -0
- mlx_stack/core/litellm_gen.py +188 -0
- mlx_stack/core/log_rotation.py +231 -0
- mlx_stack/core/log_viewer.py +386 -0
- mlx_stack/core/models.py +639 -0
- mlx_stack/core/paths.py +79 -0
- mlx_stack/core/process.py +887 -0
- mlx_stack/core/pull.py +815 -0
- mlx_stack/core/scoring.py +611 -0
- mlx_stack/core/stack_down.py +317 -0
- mlx_stack/core/stack_init.py +524 -0
- mlx_stack/core/stack_status.py +229 -0
- mlx_stack/core/stack_up.py +856 -0
- mlx_stack/core/watchdog.py +744 -0
- mlx_stack/data/__init__.py +1 -0
- mlx_stack/data/catalog/__init__.py +1 -0
- mlx_stack/data/catalog/deepseek-r1-32b.yaml +46 -0
- mlx_stack/data/catalog/deepseek-r1-8b.yaml +45 -0
- mlx_stack/data/catalog/gemma3-12b.yaml +45 -0
- mlx_stack/data/catalog/gemma3-27b.yaml +45 -0
- mlx_stack/data/catalog/gemma3-4b.yaml +45 -0
- mlx_stack/data/catalog/llama3.3-8b.yaml +44 -0
- mlx_stack/data/catalog/nemotron-49b.yaml +41 -0
- mlx_stack/data/catalog/nemotron-8b.yaml +44 -0
- mlx_stack/data/catalog/qwen3-8b.yaml +45 -0
- mlx_stack/data/catalog/qwen3.5-0.8b.yaml +45 -0
- mlx_stack/data/catalog/qwen3.5-14b.yaml +46 -0
- mlx_stack/data/catalog/qwen3.5-32b.yaml +45 -0
- mlx_stack/data/catalog/qwen3.5-3b.yaml +44 -0
- mlx_stack/data/catalog/qwen3.5-72b.yaml +42 -0
- mlx_stack/data/catalog/qwen3.5-8b.yaml +45 -0
- mlx_stack/py.typed +1 -0
- mlx_stack/utils/__init__.py +1 -0
- mlx_stack-0.1.0.dist-info/METADATA +397 -0
- mlx_stack-0.1.0.dist-info/RECORD +61 -0
- mlx_stack-0.1.0.dist-info/WHEEL +4 -0
- mlx_stack-0.1.0.dist-info/entry_points.txt +2 -0
- mlx_stack-0.1.0.dist-info/licenses/LICENSE +21 -0
mlx_stack/cli/watch.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""CLI command for the watchdog health monitor — `mlx-stack watch`.
|
|
2
|
+
|
|
3
|
+
Long-running health monitor that polls service status, auto-restarts
|
|
4
|
+
crashed services, detects flapping, and triggers log rotation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich.text import Text
|
|
16
|
+
|
|
17
|
+
from mlx_stack.core.watchdog import (
|
|
18
|
+
DEFAULT_INTERVAL,
|
|
19
|
+
DEFAULT_MAX_RESTARTS,
|
|
20
|
+
DEFAULT_RESTART_DELAY,
|
|
21
|
+
PollResult,
|
|
22
|
+
RestartRecord,
|
|
23
|
+
WatchdogError,
|
|
24
|
+
WatchdogState,
|
|
25
|
+
run_watchdog,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
console = Console(stderr=True)
|
|
29
|
+
|
|
30
|
+
# --------------------------------------------------------------------------- #
|
|
31
|
+
# Status color map
|
|
32
|
+
# --------------------------------------------------------------------------- #
|
|
33
|
+
|
|
34
|
+
_STATUS_STYLES: dict[str, str] = {
|
|
35
|
+
"healthy": "green",
|
|
36
|
+
"degraded": "yellow",
|
|
37
|
+
"down": "red",
|
|
38
|
+
"crashed": "bold red",
|
|
39
|
+
"stopped": "dim",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# --------------------------------------------------------------------------- #
|
|
44
|
+
# Display helpers
|
|
45
|
+
# --------------------------------------------------------------------------- #
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _format_status_table(result: PollResult, state: WatchdogState) -> None:
|
|
49
|
+
"""Print a Rich-formatted status table for a poll cycle.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
result: The poll cycle result.
|
|
53
|
+
state: Current watchdog state.
|
|
54
|
+
"""
|
|
55
|
+
out = Console()
|
|
56
|
+
now = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
57
|
+
|
|
58
|
+
out.print()
|
|
59
|
+
out.print(Text(f"[Cycle {state.cycle_count}] {now}", style="bold cyan"))
|
|
60
|
+
|
|
61
|
+
table = Table(
|
|
62
|
+
show_header=True,
|
|
63
|
+
header_style="bold",
|
|
64
|
+
)
|
|
65
|
+
table.add_column("Service", style="bold", min_width=12)
|
|
66
|
+
table.add_column("Status", min_width=10)
|
|
67
|
+
table.add_column("Port", justify="right", min_width=6)
|
|
68
|
+
|
|
69
|
+
for svc in result.statuses:
|
|
70
|
+
style = _STATUS_STYLES.get(svc.status, "")
|
|
71
|
+
status_text = Text(svc.status, style=style)
|
|
72
|
+
|
|
73
|
+
# Annotate flapping services
|
|
74
|
+
if svc.tier in result.flapping_services:
|
|
75
|
+
status_text.append(" [FLAPPING]", style="bold magenta")
|
|
76
|
+
|
|
77
|
+
table.add_row(
|
|
78
|
+
svc.tier,
|
|
79
|
+
status_text,
|
|
80
|
+
str(svc.port),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
out.print(table)
|
|
84
|
+
|
|
85
|
+
# Show rotation info
|
|
86
|
+
if result.rotations_performed > 0:
|
|
87
|
+
out.print(
|
|
88
|
+
Text(
|
|
89
|
+
f" Log rotation: {result.rotations_performed} file(s) rotated",
|
|
90
|
+
style="dim",
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Show restart summary
|
|
95
|
+
if result.restarts_attempted > 0:
|
|
96
|
+
out.print(
|
|
97
|
+
Text(
|
|
98
|
+
f" Restarts: {result.restarts_succeeded}/{result.restarts_attempted} succeeded",
|
|
99
|
+
style=(
|
|
100
|
+
"yellow"
|
|
101
|
+
if result.restarts_succeeded < result.restarts_attempted
|
|
102
|
+
else "green"
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _format_restart_event(record: RestartRecord) -> None:
|
|
109
|
+
"""Print a Rich-formatted restart event.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
record: The restart record.
|
|
113
|
+
"""
|
|
114
|
+
out = Console()
|
|
115
|
+
ts = datetime.fromtimestamp(record.timestamp, tz=timezone.utc).strftime(
|
|
116
|
+
"%Y-%m-%d %H:%M:%S UTC"
|
|
117
|
+
)
|
|
118
|
+
status = "✓" if record.success else "✗"
|
|
119
|
+
style = "green" if record.success else "red"
|
|
120
|
+
|
|
121
|
+
out.print(
|
|
122
|
+
Text(
|
|
123
|
+
f" [{status}] {ts} Restart {record.service} "
|
|
124
|
+
f"(attempt #{record.attempt}): {record.reason}",
|
|
125
|
+
style=style,
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# --------------------------------------------------------------------------- #
|
|
131
|
+
# Input validation
|
|
132
|
+
# --------------------------------------------------------------------------- #
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _validate_positive_int(
|
|
136
|
+
ctx: click.Context,
|
|
137
|
+
param: click.Parameter,
|
|
138
|
+
value: int,
|
|
139
|
+
) -> int:
|
|
140
|
+
"""Validate that a value is a positive integer.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
ctx: Click context.
|
|
144
|
+
param: Click parameter.
|
|
145
|
+
value: The value to validate.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The validated value.
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
click.BadParameter: If value is not positive.
|
|
152
|
+
"""
|
|
153
|
+
if value < 1:
|
|
154
|
+
raise click.BadParameter(
|
|
155
|
+
f"Must be a positive integer (got {value})."
|
|
156
|
+
)
|
|
157
|
+
return value
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# --------------------------------------------------------------------------- #
|
|
161
|
+
# CLI command
|
|
162
|
+
# --------------------------------------------------------------------------- #
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@click.command()
|
|
166
|
+
@click.option(
|
|
167
|
+
"--interval",
|
|
168
|
+
type=int,
|
|
169
|
+
default=DEFAULT_INTERVAL,
|
|
170
|
+
show_default=True,
|
|
171
|
+
callback=_validate_positive_int,
|
|
172
|
+
help="Seconds between health polls.",
|
|
173
|
+
)
|
|
174
|
+
@click.option(
|
|
175
|
+
"--max-restarts",
|
|
176
|
+
type=int,
|
|
177
|
+
default=DEFAULT_MAX_RESTARTS,
|
|
178
|
+
show_default=True,
|
|
179
|
+
callback=_validate_positive_int,
|
|
180
|
+
help="Maximum restarts before marking service as flapping.",
|
|
181
|
+
)
|
|
182
|
+
@click.option(
|
|
183
|
+
"--restart-delay",
|
|
184
|
+
type=int,
|
|
185
|
+
default=DEFAULT_RESTART_DELAY,
|
|
186
|
+
show_default=True,
|
|
187
|
+
callback=_validate_positive_int,
|
|
188
|
+
help="Base delay (seconds) before restart with exponential backoff.",
|
|
189
|
+
)
|
|
190
|
+
@click.option(
|
|
191
|
+
"--daemon",
|
|
192
|
+
is_flag=True,
|
|
193
|
+
default=False,
|
|
194
|
+
help="Run in background as a daemon.",
|
|
195
|
+
)
|
|
196
|
+
def watch(
|
|
197
|
+
interval: int,
|
|
198
|
+
max_restarts: int,
|
|
199
|
+
restart_delay: int,
|
|
200
|
+
daemon: bool,
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Monitor stack health and auto-restart crashed services.
|
|
203
|
+
|
|
204
|
+
Polls service health at regular intervals (default 30s) and
|
|
205
|
+
auto-restarts any crashed services (PID file exists, process dead).
|
|
206
|
+
Services that are intentionally stopped (no PID file) are NOT
|
|
207
|
+
restarted.
|
|
208
|
+
|
|
209
|
+
\b
|
|
210
|
+
Features:
|
|
211
|
+
• Rich-formatted status display each poll cycle
|
|
212
|
+
• Auto-restart of crashed services
|
|
213
|
+
• Flap detection (stops restarting after threshold)
|
|
214
|
+
• Exponential backoff on restart delay
|
|
215
|
+
• Log rotation each poll cycle
|
|
216
|
+
• Daemon mode with PID file
|
|
217
|
+
|
|
218
|
+
\b
|
|
219
|
+
Examples:
|
|
220
|
+
mlx-stack watch Start monitoring (foreground)
|
|
221
|
+
mlx-stack watch --daemon Start as background daemon
|
|
222
|
+
mlx-stack watch --interval 60 Poll every 60 seconds
|
|
223
|
+
mlx-stack watch --max-restarts 3 Flap after 3 restarts
|
|
224
|
+
"""
|
|
225
|
+
try:
|
|
226
|
+
if daemon:
|
|
227
|
+
console.print(
|
|
228
|
+
Text("Starting watchdog in daemon mode...", style="bold cyan")
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
run_watchdog(
|
|
232
|
+
interval=interval,
|
|
233
|
+
max_restarts=max_restarts,
|
|
234
|
+
restart_delay=restart_delay,
|
|
235
|
+
daemon=daemon,
|
|
236
|
+
status_callback=_format_status_table,
|
|
237
|
+
restart_callback=_format_restart_event,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Clean exit
|
|
241
|
+
if not daemon:
|
|
242
|
+
out = Console()
|
|
243
|
+
out.print()
|
|
244
|
+
out.print(Text("Watchdog stopped.", style="bold cyan"))
|
|
245
|
+
|
|
246
|
+
except WatchdogError as exc:
|
|
247
|
+
console.print(f"[red]Error:[/red] {exc}")
|
|
248
|
+
sys.exit(1)
|
|
249
|
+
except KeyboardInterrupt:
|
|
250
|
+
out = Console()
|
|
251
|
+
out.print()
|
|
252
|
+
out.print(Text("Watchdog stopped.", style="bold cyan"))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core business logic modules for mlx-stack."""
|