Repository navigation
Conversation
|
Thank you for making a PR, @ykisana . |
dongjoon-hyun
left a comment
There was a problem hiding this comment.
Thank you for making a PR, @ykisana. Moving the start time into consumer(...) fixes the near-zero values for regular HTTP requests, and testResponseLatency passes locally.
I left inline comments. Here is the summary.
- Build failure: The reordered import block violates the Spotless
importOrder, so./gradlew spotlessCheck(and./gradlew buildin CI) fails. Please run./gradlew spotlessApply. - Histogram population: fabric8 7.8.0 calls
after(...)withoutconsumer(...)for WebSocket upgrades (e.g. informer watches), and callsafter(...)twice for a request re-sent afterafterFailure(...)(e.g.401withTokenRefreshInterceptor). Those responses get no latency sample, so the count ofhttp.response.latency.nanosno longer matcheshttp.response. - No-op cleanup:
afterConnectionFailure(...)receives the original request, not the rebuilt one used as the key, soremove(request)never removes anything. - Javadoc and tests: The new Javadoc implies that
consumer(...)andafter(...)are always paired, and the tests do not cover the paths above. - Migration guide: Since this metric is enabled by default in 1.0.0, please add an item to
docs/migration_guide.md. - PR description: Please use the phrase
Generated-by: Claude Opus 5.5in the last section, as the PR template asks. - Optional: fabric8 already passes the consumer returned by
consumer(...)toafter(...), so a delegating consumer which carries the start time could replace the globalsynchronizedMap(WeakHashMap).
c567d5f to
6190979
Compare
…imers ### What changes were proposed in this pull request? This PR fixes the Prometheus summaries of Dropwizard histograms and timers in `PrometheusPullModelHandler`. - Track the sum of all recorded values in the new `SummingHistogram` and `SummingTimer`, which the operator now uses, and export `_sum` only for them. - Export the median and the 99.9th percentile as the `0.5` and `0.999` quantiles of non-`nanos` histograms, instead of the mean and the 99th percentile. ### Why are the changes needed? `_sum` was the mean of the reservoir multiplied by the count. Since the mean covers only roughly the last 5 minutes, `_sum` could decrease between scrapes, which breaks `rate()`. This becomes visible once [#918](#918) records real latencies. ### Does this PR introduce _any_ user-facing change? Yes. The values of `_sum` and of the quantiles above change compared to [1.0.0](https://github.com/apache/spark-kubernetes-operator/releases/tag/1.0.0) (2026-07-26), while metric names and types are unchanged. The migration guide is updated. ### How was this patch tested? Pass the CIs with the newly added test cases. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude Opus 5.5 Closes #922 from dongjoon-hyun/SPARK-59935. Authored-by: Dongjoon Hyun <dongjoon@apache.org> Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
dongjoon-hyun
left a comment
There was a problem hiding this comment.
Thank you for updating the PR, @ykisana. The delegating TimedConsumer fixes the near-zero values of regular requests, and the linters, javadoc and the tests pass locally.
I left inline comments. Here is the summary.
- Re-sent request: fabric8 re-sends a request after
afterFailure(...)with the same consumer. So the latency of the re-sent response is measured from the start of the first attempt, and the first exchange is counted twice, e.g.401-> token refresh ->200. A5xxor429retry gets a fresh consumer instead. 401latency:after(...)of a failed response runs only after the synchronousafterFailure(...)work. So a401sample can include the token refresh, e.g. a kubeconfig exec credential plugin.- Rebase: This PR conflicts with
mainindocs/migration_guide.mdbecause of SPARK-59935. SPARK-59935 is also needed for a correct Prometheus_sumof this histogram. - Flaky test:
testResponseLatencyOfWebSocketUpgradeawaits onlyhttp.response, so it can fail with an NPE athttp.response.101. - Javadoc and docs: The re-send sentence of
after(...)does not hold for a WebSocket upgrade.after(...)gets the outermost consumer of the chain, not necessarily the one returned byconsumer(...). The latency ends when the response headers arrive. 1.0 recorded less than a microsecond. - Test coverage: No test covers the delegation in
TimedConsumer#unwrap, whichHttpLoggingInterceptorneeds at theTRACElevel. - PR description: Please add
Generated-by: Claude Opus 5.5as the PR template asks. Please also updateHow was this patch tested?, which still describes a 500 ms delay while the test uses 50 ms and does not mention the two other tests, andDoes this PR introduce any user-facing change?, which does not mention that the count no longer includes WebSocket upgrades. - Nits: an unneeded
await(), the nullableLongparameter, the anonymous403re-send interceptor, and the metric lookups in the tests.
c377607 to
0049941
Compare
Addressed these, thanks for looking into it so much. |
dongjoon-hyun
left a comment
There was a problem hiding this comment.
Thank you for addressing all the comments, @ykisana. Restarting the clock at each response fixes the re-sent request. The linters, javadoc and the tests pass locally. I also confirmed that testResponseLatencyOfResentRequest fails when getAndSet(now) is replaced with get(), and that testConsumerUnwrapsDelegate fails without the delegation in TimedConsumer#unwrap.
I left a few more inline comments. Here is the summary.
- Asynchronous token refresh: With an OIDC auth provider, fabric8 refreshes the token asynchronously, so the refresh time goes into the latency of the re-sent request, not of the
401. The new sentence indocs/configuration.mdholds only for a synchronous refresh. - PR description:
Does this PR introduce _any_ user-facing change?still does not mention two changes: WebSocket upgrades are no longer recorded, so the count can be lower than that ofkubernetes.client.http.response, and the latency ends when the response headers arrive.Why are the changes needed?still saysonly a few microseconds per response, while the migration guide saysnearly zero.How was this patch tested?liststestTimedConsumerUnwrapsDelegate, but the test istestConsumerUnwrapsDelegate.What changes were proposed in this pull request?does not mention that the start time is restarted at each response. Also,from when the request is sentdoes not hold for a re-sent request.
- Two metrics interceptors (corner case):
getAndSet(...)restarts a clock which everyKubernetesMetricsInterceptoron a client shares. So a second instance, e.g. of a subclass, records nearly zero. - Nits: The
401delay in the test relies onnullbeing serialized into a non-empty body, and the histogram lookups in the tests could be simpler.
| `PodsReady` condition, is not counted there when it gets no response. | ||
|
|
||
| `kubernetes.client.http.response.latency.nanos` ends when the response headers arrive, so it does | ||
| not include reading the body. A request re-sent after a `401` is measured from the `401`, whose |
There was a problem hiding this comment.
This holds when the token is refreshed synchronously, e.g. with a service account token file, an exec credential plugin or an OAuthTokenProvider. But with an OIDC auth provider, TokenRefreshInterceptor#afterFailure returns the pending future of OpenIDConnectionUtils#resolveOIDCTokenFromAuthConfig, which calls the IdP with sendAsync. Then fabric8 runs after(...) of the 401 before the refresh, and re-sends the request only when the refresh completes. So the refresh time goes into the latency of the re-sent request instead. In my test with a 300 ms refresh, a 401 returned after 100 ms was recorded as 105 ms, and the re-sent 200 returned after 50 ms as ~360 ms. With a synchronous refresh of the same length, they were 410 ms and 54 ms.
The sum of the two values is the same either way. How about describing that instead? e.g.
A request re-sent after a
401is recorded twice, and the two values add up to the time from sending it until the re-sent response arrives, including the token refresh.
Could you also align the Javadoc of after(...) (L132) and TimedConsumer (L262-263) with it? The same goes for from sending each HTTP request in the migration guide.
| .get() | ||
| .delay(200) | ||
| .withPath(CONFIG_MAP_PATH) | ||
| .andReturn(HTTP_UNAUTHORIZED, null) |
There was a problem hiding this comment.
nit: The .delay(200) works here only because null is serialized into the 4-byte body null. fabric8 mockwebserver 7.8.0 delays only a non-empty body (HttpServerRequestHandler). With an empty body, the delay would be silently dropped, and the assertion at L193 would fail with only expected: <true> but was: <false>. How about returning an explicit body, e.g. a Status with the code 401? Could you also add messages with the snapshot values to the timing assertions?
| client.resource(configMap).get(); | ||
|
|
||
| Map<String, Metric> map = metricsInterceptor.metricRegistry().getMetrics(); | ||
| Histogram latency = (Histogram) map.get("http.response.latency.nanos"); |
There was a problem hiding this comment.
nit: Meters now go through meterCount(...), but the three tests still keep a Map<String, Metric> only for the histogram cast, and the WebSocket test mixes both styles in one untilAsserted(...). How about metricsInterceptor.metricRegistry().getHistograms().get("http.response.latency.nanos"), or a helper next to meterCount(...)? FYI, KubernetesClientFactoryTest#meterCount has the same name and signature but creates a missing meter, so a misspelled name returns 0 there and fails here.
0f38e3d to
dcd39df
Compare
|
@dongjoon-hyun address the comments Please let me know if any more issues |
|
Updated to fix merge conflict |
|
Thank you for updating. I'm re-reviewing now, @ykisana . |
|
Removed JIRA reference here too as per: #944 |
dongjoon-hyun
left a comment
There was a problem hiding this comment.
Thank you for updating the PR, @ykisana. The owner-based lookup fixes the two-interceptor case. The linters, javadoc and the tests pass locally, and the four new latency tests passed 30 repeated runs each. I also confirmed with fabric8 7.8.0 and Vert.x 4.5.33 that the two values of a re-sent request add up to the total time, and that HttpLoggingInterceptor still finds its consumer at the TRACE level.
I left inline comments. Here is the summary.
401latency in the docs: With a synchronous token refresh, the401value ends after the refresh, not when its headers arrive. Only the sum holds.- Rejected WebSocket upgrades: With the default interceptors, they are always counted five times, also in the response code meters.
- Javadoc:
afterConnectionFailure(...): fabric8 invokes it for the last attempt of a request timeout while retries are left.after(...):{@link #afterFailure(...)}points to this class's method, which never re-sends.
- PR description:
testResponseLatencyOfTwoInterceptors, theownerlookup and the docs changes are missing, and the per-value re-send sentence holds only for an asynchronous token refresh. - Nits:
pollDelay(Duration.ZERO)in the WebSocket test, themeterCounthelper, and an optional upstream fabric8 issue for the re-send path. - Pre-existing issues (FYI, not from this PR): the metric names in the client metrics table, an
IndexOutOfBoundsExceptionfor a status code outside 100-599, and the.nanosname with values in seconds when the Prometheus name sanitizing is disabled.
|
Addressed in this PR
To be addressed separately (new issues/PRs)
|
…s recording near zero
What changes were proposed in this pull request?
Measure the response latency from a start time carried with the request, instead of taking two back-to-back
System.nanoTime()calls inafter(...).consumer(...)wraps the response body consumer in aTimedConsumerthat carries the start time and the interceptor which created it (owner).after(...)finds it withAsyncBody.Consumer#unwrap(...), skipping anyTimedConsumerof anotherKubernetesMetricsInterceptoron the same client, so that each interceptor uses its own start time.The start time is restarted at each response. A request re-sent by fabric8 after another interceptor's
afterFailure(...)returnstrue, e.g.TokenRefreshInterceptoron a401, reuses the same consumer, so it is recorded twice, and the two values add up to the time from sending it until the re-sent response arrives, including the token refresh.WebSocket upgrade responses (
101) do not go throughconsumer(...), soafter(...)finds noTimedConsumerand records no latency for them. They are still counted inkubernetes.client.http.response.TimedConsumer#unwrap(...)delegates to the wrapped consumer, so fabric8'sHttpLoggingInterceptorstill works at theTRACElevel.Also:
docs/configuration.mdand adds an item todocs/migration_guide.md.after(...)(WebSocket upgrades and re-sent requests) and ofafterConnectionFailure(...)(invoked only while the request has a retry left, even if it is not retried).meterCounttest helper toTestUtils, shared byKubernetesMetricsInterceptorTestandKubernetesClientFactoryTest.Why are the changes needed?
Since SPARK-53647 / SPARK-53648 (0.5.0),
kubernetes.client.http.response.latency.nanoshas recorded nearly zero for every response, whatever the real latency, because both timestamps were taken inafter(...).Does this PR introduce any user-facing change?
Yes.
kubernetes.client.http.response.latency.nanoschanges in these ways; its name and type are unchanged:401is recorded twice, and the two values add up to the time from sending it until the re-sent response arrives, including the token refresh.101), e.g. of a watch, are no longer recorded, so its count can be lower than that ofkubernetes.client.http.response.How was this patch tested?
Added tests to
KubernetesMetricsInterceptorTest:testResponseLatency: delays a mock server response by 50 ms and asserts that the recorded latency is at least 50 ms and below 30 s.testResponseLatencyOfResentRequest: returns a401delayed by 200 ms and then a200, which fabric8'sTokenRefreshInterceptorre-sends. Asserts two latency samples, one of at least 200 ms and one below 200 ms, i.e. the re-sent response is not measured from the first attempt.testResponseLatencyOfWebSocketUpgrade: starts an informer and asserts that the101upgrade response is counted inhttp.responsebut not in the latency histogram.testResponseLatencyOfTwoInterceptors: registers twoKubernetesMetricsInterceptors on one client, delays the response by 50 ms, and asserts that each records a latency of at least 50 ms, i.e. neither restarts the clock of the other.testConsumerUnwrapsDelegate: asserts thatTimedConsumer#unwrapdelegates to the wrapped consumer.Confirmed that
testResponseLatencyOfResentRequestfails when the start time is not restarted at each response. RanKubernetesMetricsInterceptorTest10 times in a row without a failure.Was this patch authored or co-authored using generative AI tooling?
Yes.
Generated-by: Claude Opus 5.5