SEZ9 opened a new pull request, #12631:
URL: https://github.com/apache/seatunnel/pull/12631

   Closes #12630
   
   > **Stacked on #12629.** This needs the `rest.py` added there, so the diff 
shown against `dev` includes that PR's commit until it merges. The commit to 
review here is the second one. I will rebase once #12629 lands.
   
   ## Benchmark first
   
   `benchmark/runner.py --provider bedrock --model 
us.anthropic.claude-haiku-4-5-20251001-v1:0 --preset smoke --level l1` (12 
tasks, haiku-4-5), on this branch: **pass@1 7/12 (58.3%), 0 misroutes, 0 
generation errors.**
   
   **That pass@1 number is not a result and I am not presenting it as one.** I 
measured this preset's run-to-run spread on unchanged code and it is about five 
tasks: one branch of mine scored 6/12 then 11/12 on consecutive runs with an 
identical tree, and `dev` scored 11/12. 58.3% sits inside that band.
   
   More to the point, **this change cannot move the benchmark at all.** 
Everything added here is either a new file or a new code path: 
`diagnostics/jobs.py` is new, and in `cli.py` the additions are one new 
function plus one argparse flag that returns before any provider is 
constructed. Nothing on the config-generation path is touched. So the run above 
is a no-regression check on code I did not modify, and the honest summary is 
"the numbers moved within noise, and there is no mechanism by which they could 
have moved."
   
   The real verification is 26 new tests, described below.
   
   ## Purpose of this pull request
   
   When a Zeta job is not making progress, the CLI has nothing to say about it. 
Finding out why means reading the server log on the master node -- and the two 
most common answers are already in the REST API.
   
   `/job-info/<id>` has carried a `diagnostics` block since 
#11982/#11983/#11984: per-state entry timestamps at both job and pipeline 
level, plus each pipeline's restore counter and limit. `seatunnel --diagnose 
<jobId>` reads that and answers by arithmetic:
   
   | | |
   |---|---|
   | `job_failed` | error code and innermost `Caused by`, via the existing 
`parse_error` from #12535 |
   | `pipeline_crash_loop` | `restoreCount >= 2` -- the pipeline keeps dying 
and being restored, so the job reports RUNNING while making no progress |
   | `pipeline_restore_exhausted` | `restoreCount >= maxRestoreCount` -- 
escalated to error, because the next failure ends the job |
   | `stuck_before_running` | too long in 
CREATED/PENDING/SCHEDULED/INITIALIZING: the cluster did not grant the slots |
   | `stuck_tearing_down` | too long in FAILING/CANCELING/DOING_SAVEPOINT: a 
task is ignoring the stop request |
   | `no_rows_read` / `rows_read_none_written` | `SourceReceivedCount` / 
`SinkWriteCount` |
   | `running_duration` | info only -- a streaming job running for hours is 
doing its job |
   
   ```
   $ seatunnel --diagnose 852362670771666945
   Job 852362670771666945 mysql-to-doris — RUNNING
     [warning] Pipeline 1 has restarted 4 times. It is failing and being 
restored
               repeatedly, so the job looks alive while making no progress. 
(restoreCount=4/10)
   ```
   
   ### Three deliberate properties
   
   **No model.** These conclusions are derived, not inferred -- a restore count 
of 7 is a crash loop whatever a model thinks. It also means the flag is handled 
before any provider setup and works with no API key and no provider configured, 
which is the state an operator is usually in when something breaks 
unexpectedly. An LLM explanation over the same evidence is a sensible 
follow-up, on top of this rather than instead of it.
   
   **Read-only.** One GET. No submit, no stop, no savepoint. That is what makes 
it safe to point at a live production job.
   
   **Non-zero exit only for a genuinely `FAILED` job.** A stuck or 
crash-looping job is still a live job, and exiting non-zero for it would break 
`seatunnel --diagnose x && ...` for a job that is merely slow.
   
   ### Three engine details that are easy to get wrong
   
   I checked each of these against the engine source rather than assuming, and 
each is pinned by a test, because all three fail silently:
   
   1. **Row counts are strings.** `BaseService.metricsToJsonObject` calls 
`toString()` on every metric leaf, so `metrics["SourceReceivedCount"]` is 
`"0"`, not `0`. An int comparison would simply never match and the rule would 
never fire.
   2. **`Table`-prefixed metrics are not scalars.** `TableSourceReceivedCount` 
is an object keyed by table name. Reading it as a count would either raise or 
coerce to something meaningless -- a false "no rows read" on a healthy job. The 
job-wide scalars are the unprefixed names.
   3. **`stateTimestamps` is a state-name map, not a sequence.** The engine 
records the last entry into *each* state, so the current state's own key is the 
right answer, and the fallback when it is absent must be the newest entry -- 
not the last key, which is arbitrary and can be days stale.
   
   Also worth noting: `restoreCount` is read with a guard against `bool`, since 
`bool` subclasses `int` in Python and an unguarded `int()` would turn a stray 
`true` into a restore count of 1.
   
   ## Does this PR introduce _any_ user-facing change?
   
   Yes, a new flag: `seatunnel --diagnose <JOB_ID>`. Documented in `README.md` 
and `README.zh-CN.md`. No existing behaviour changes.
   
   While documenting it I noticed the README's CLI-arguments block is already 
out of date -- it lists a `--sync-catalog` flag that does not exist and omits 
`--init` and `--export-metadata`. I left that alone to keep this diff to one 
feature; happy to fix it in a separate PR.
   
   ## How was this patch tested or verified?
   
   26 new tests, 245 passing in total.
   
   `tests/test_diagnose_job.py` (19) covers the rules as pure functions with an 
injected clock, including the three engine details above, the severity 
ordering, a single restore *not* being flagged (one restore is ordinary 
recovery; flagging it would make healthy clusters look sick), a terminal job 
not being measured for stuckness, and a sweep of twelve malformed payloads that 
must yield no findings rather than raise -- crashing while diagnosing is 
strictly worse than saying nothing.
   
   `tests/test_diagnose_cli.py` (7) drives the flag end to end against a real 
loopback `HTTPServer`: URL construction, the 404-versus-other-error split, the 
exit codes, an engine that is genuinely not listening, and that a password in a 
JDBC URL inside `errorMsg` is redacted before printing -- this output routinely 
gets pasted into bug reports. No LLM provider is constructed anywhere in that 
file, which is itself part of what is being asserted.
   
   ## Check list
   
   * [x] If any new Jar binary package adding in your PR, please add License 
Notice according [New License 
Guide](https://github.com/apache/seatunnel/blob/dev/docs/en/contribution/new-license.md)
   * [x] If necessary, please update the documentation to describe the new 
feature. https://github.com/apache/seatunnel/tree/dev/docs
   * [x] If you are contributing the connector code, please check that the 
following files are updated:
     1. Update change log that in connector document. For more details you can 
refer to 
[connector-v2](https://github.com/apache/seatunnel/tree/dev/docs/en/connector-v2)
     2. Update 
[plugin-mapping.properties](https://github.com/apache/seatunnel/blob/dev/plugin-mapping.properties)
 and add new connector information in it
     3. Update the pom file of 
[seatunnel-dist](https://github.com/apache/seatunnel/blob/dev/seatunnel-dist/pom.xml)
   * [x] Update the 
[`release-note`](https://github.com/apache/seatunnel/blob/dev/release-note.md).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to