|
5 | 5 | from rich.table import Table |
6 | 6 |
|
7 | 7 | from dstack._internal.cli.utils.common import NO_OFFERS_WARNING, add_row_from_dict, console |
8 | | -from dstack._internal.core.models.configurations import DevEnvironmentConfiguration |
| 8 | +from dstack._internal.core.models.configurations import DevEnvironmentConfiguration, ProbeConfig |
9 | 9 | from dstack._internal.core.models.instances import InstanceAvailability |
10 | 10 | from dstack._internal.core.models.profiles import ( |
11 | 11 | DEFAULT_RUN_TERMINATION_IDLE_TIME, |
12 | 12 | TerminationPolicy, |
13 | 13 | ) |
14 | 14 | from dstack._internal.core.models.runs import ( |
| 15 | + JobStatus, |
| 16 | + Probe, |
15 | 17 | RunPlan, |
16 | 18 | ) |
17 | 19 | from dstack._internal.core.services.profiles import get_termination |
18 | 20 | from dstack._internal.utils.common import ( |
19 | 21 | DateFormatter, |
| 22 | + batched, |
20 | 23 | format_duration_multiunit, |
21 | 24 | format_pretty_duration, |
22 | 25 | pretty_date, |
@@ -156,6 +159,12 @@ def get_runs_table( |
156 | 159 | table.add_column("INSTANCE TYPE", no_wrap=True, ratio=1) |
157 | 160 | table.add_column("PRICE", style="grey58", ratio=1) |
158 | 161 | table.add_column("STATUS", no_wrap=True, ratio=1) |
| 162 | + if verbose or any( |
| 163 | + run._run.is_deployment_in_progress() |
| 164 | + and any(job.job_submissions[-1].probes for job in run._run.jobs) |
| 165 | + for run in runs |
| 166 | + ): |
| 167 | + table.add_column("PROBES", ratio=1) |
159 | 168 | table.add_column("SUBMITTED", style="grey58", no_wrap=True, ratio=1) |
160 | 169 | if verbose: |
161 | 170 | table.add_column("ERROR", no_wrap=True, ratio=2) |
@@ -198,6 +207,9 @@ def get_runs_table( |
198 | 207 | else "" |
199 | 208 | ), |
200 | 209 | "STATUS": latest_job_submission.status_message, |
| 210 | + "PROBES": _format_job_probes( |
| 211 | + job.job_spec.probes, latest_job_submission.probes, latest_job_submission.status |
| 212 | + ), |
201 | 213 | "SUBMITTED": format_date(latest_job_submission.submitted_at), |
202 | 214 | "ERROR": latest_job_submission.error, |
203 | 215 | } |
@@ -226,3 +238,21 @@ def get_runs_table( |
226 | 238 | add_row_from_dict(table, job_row, style="secondary" if len(run.jobs) != 1 else None) |
227 | 239 |
|
228 | 240 | return table |
| 241 | + |
| 242 | + |
| 243 | +def _format_job_probes( |
| 244 | + probe_configs: list[ProbeConfig], probes: list[Probe], job_status: JobStatus |
| 245 | +) -> str: |
| 246 | + if not probes or job_status != JobStatus.RUNNING: |
| 247 | + return "" |
| 248 | + statuses = [] |
| 249 | + for probe_config, probe in zip(probe_configs, probes): |
| 250 | + if probe.success_streak >= probe_config.ready_after: |
| 251 | + status = "[code]✓[/]" |
| 252 | + elif probe.success_streak > 0: |
| 253 | + status = "[warning]~[/]" |
| 254 | + else: |
| 255 | + status = "[error]×[/]" |
| 256 | + statuses.append(status) |
| 257 | + # split into whitespace-delimited batches to allow column wrapping |
| 258 | + return " ".join("".join(batch) for batch in batched(statuses, 5)) |
0 commit comments