dmsolr opened a new pull request, #7674:
URL: https://github.com/apache/shenyu/pull/7674
## Which issue is this PR closes?
Closes #7647.
Tracker: #7650.
## What does this PR change?
`UpstreamCheckTask` schedules fixed-delay rounds, submits every registered
upstream to a separate executor and waits for the round inside a synchronized
section. Large batches or slow endpoints can increase round duration and delay
subsequent checks, but today there is no way to tell from the outside:
- whether a round is falling behind the configured `checkInterval`;
- whether the executor or the endpoints are the bottleneck;
- how stale the published health state actually is.
Issue #7647 asks for exactly these signals to be recorded **before** any
scheduling change is evaluated ("Record round duration, submitted/pending
checks, endpoint latency and health-state age before changing scheduling").
This PR adds the observability layer only — no scheduling, locking, threshold
or race-condition change. Bounding in-flight work, batching or staggering stays
measurement-gated and can be a follow-up once operators have real numbers.
### Per-round signals
All readable via new public getters on `UpstreamCheckTask`:
| Getter | Meaning |
| --- | --- |
| `getLastRoundDurationMillis()` | Monotonic `System.nanoTime()` elapsed
across the whole synchronized round (submit + wait). Reflects real time to
converge, not publisher-construction time. |
| `getLastRoundSubmittedChecks()` | How many probes were dispatched this
round. |
| `getLastRoundPendingChecks()` | How many of those were still pending when
the round entered `waitFinish()`. A persistently high value points at the
executor, not the endpoints. |
| `getLastRoundMaxEndpointLatencyMillis()` | Worst single-probe latency
observed this round. Only probes with health-check enabled contribute (probes
short-circuited by `!upstream.isHealthCheckEnabled()` are not measured). |
| `getLastRoundOldestHealthStateMillis()` | Max age across healthy and
unhealthy buckets, computed from `Upstream.getLastHealthTimestamp()` /
`getLastUnhealthyTimestamp()`. Grows when rounds fall behind `checkInterval`. |
| `getLastRoundCompletedAtMillis()` | Wall-clock finish timestamp. |
### Cumulative counters
`getTotalRounds()`, `getTotalSubmittedChecks()`,
`getTotalEndpointLatencyMillis()` let operators compute long-run averages
without a metrics backend.
### Logging
A single DEBUG-level summary line per round:
```
[Health Check] round summary: durationMs=..., submitted=...,
pendingAtWait=..., maxEndpointLatencyMs=..., oldestHealthStateMs=...,
healthySelectors=..., unhealthySelectors=...
```
DEBUG (not INFO) keeps log volume aligned with #7643. The existing INFO
lines on state transitions (`server is back online` / `server is offline`) and
`print()` are unchanged.
### Implementation notes
- Per-round scratch counters (`AtomicInteger currentRoundSubmittedChecks`,
`AtomicLong currentRoundMaxEndpointLatencyMillis`) are reset at the start of
every `healthCheck()` and copied into the `volatile lastRound*` fields once the
round terminates, so callers never observe a half-updated snapshot.
- `lastRoundPendingChecks` is captured after `doHealthCheck()` and before
`waitFinish()` by walking the `futures` list once with `isDone()`.
- `computeOldestHealthStateMillis()` walks both buckets once per round; the
cost is bounded by the same population the round is already probing.
- Endpoint latency uses `System.nanoTime()` around
`UpstreamCheckUtils.checkUrl(...)` only, in a `finally` block, so the counter
is updated even when the probe throws.
- `recordRoundMetrics` runs in `finally`, so the counters still advance when
the round aborts on exception.
## Tests
New tests in `UpstreamCheckTaskTest`, all deterministic (invoke `run()`
directly instead of relying on the scheduled executor):
- `roundMetricsAreRecordedAfterRunCompletes` — after one synchronous `run()`
with four registered upstreams, all last-round fields are populated and
`submitted` covers every upstream.
- `emptyPopulationStillRecordsRoundCompletion` — `totalRounds` advances even
with zero upstreams, so operators can distinguish "task not running" from "task
running with nothing to check"; `maxEndpointLatency` stays zero.
- `oldestHealthStateAgeReflectsStaleTimestamp` — an upstream whose
`lastHealthTimestamp` is 5 s old surfaces as an oldest-age of at least 4 s, so
the metric does not collapse to zero.
- `perRoundCountersAreResetBetweenRounds` — a second `run()` resets the
per-round scratch counters instead of leaking values from the previous round.
Module run:
```
./mvnw test -pl shenyu-loadbalancer -Dtest=UpstreamCheckTaskTest
Tests run: 14, Failures: 0, Errors: 0, Skipped: 0
./mvnw checkstyle:check -pl shenyu-loadbalancer
You have 0 Checkstyle violations.
```
## Measurement status
The tracker issue requires a benchmark with increasing upstream counts and
partial slow/failing endpoint populations, plus a probe-enabled configuration
note (bootstrap currently disables this feature by default). This environment
does not have a load generator, controllable slow upstreams or JFR profiling
harness, so no numeric before/after is attached here.
The change is source-grounded and does not require a benchmark to be safe:
it only reads state that is already maintained, plus two `System.nanoTime()`
calls per round and one per probe. Once merged, operators running with
health-check enabled can collect the numbers the issue asks for directly from
these getters, and a follow-up PR can decide whether bounded in-flight work /
batching / staggering is actually justified.
## Compatibility
- No public API removed or renamed. Only new getters are added.
- No scheduling, locking, threshold or race-condition change. The
`synchronized (lock)` scope, `tryStartHealthCheck` / `finishHealthCheck` gating
and `triggerRemoveAll` race protection are unchanged.
- No new runtime dependency.
- No metric cardinality expansion: all signals are scalars per task
instance, no per-upstream or per-selector labels are added.
- Healthy/unhealthy transition semantics and the #6502 recovery scenario are
untouched.
--
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]