diff options
Diffstat (limited to 'vespaclient-container-plugin/src/main/java/com')
3 files changed, 78 insertions, 35 deletions
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java index 06cdccd17fc..6ad6fe2b1e2 100644 --- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java +++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java @@ -116,6 +116,7 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.GET; import static com.yahoo.jdisc.http.HttpRequest.Method.OPTIONS; import static com.yahoo.jdisc.http.HttpRequest.Method.POST; import static com.yahoo.jdisc.http.HttpRequest.Method.PUT; +import static com.yahoo.vespa.http.server.Headers.CLIENT_VERSION; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.logging.Level.FINE; @@ -449,7 +450,8 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { } private ContentChannel postDocument(HttpRequest request, DocumentPath path, ResponseHandler rawHandler) { - ResponseHandler handler = new MeasuringResponseHandler(rawHandler, com.yahoo.documentapi.metrics.DocumentOperationType.PUT, clock.instant()); + ResponseHandler handler = new MeasuringResponseHandler( + request, rawHandler, com.yahoo.documentapi.metrics.DocumentOperationType.PUT, clock.instant()); if (getProperty(request, DRY_RUN, booleanParser).orElse(false)) { handleFeedOperation(path, true, handler, new com.yahoo.documentapi.Response(-1)); return ignoredContent; @@ -462,7 +464,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { DocumentOperationParameters parameters = parametersFromRequest(request, ROUTE) .withResponseHandler(response -> { outstanding.decrementAndGet(); - updatePutMetrics(response.outcome()); + updatePutMetrics(response.outcome(), latencyOf(request)); handleFeedOperation(path, put.fullyApplied(), handler, response); }); return () -> dispatchOperation(() -> asyncSession.put((DocumentPut)put.operation(), parameters)); @@ -471,7 +473,8 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { } private ContentChannel putDocument(HttpRequest request, DocumentPath path, ResponseHandler rawHandler) { - ResponseHandler handler = new MeasuringResponseHandler(rawHandler, com.yahoo.documentapi.metrics.DocumentOperationType.UPDATE, clock.instant()); + ResponseHandler handler = new MeasuringResponseHandler( + request, rawHandler, com.yahoo.documentapi.metrics.DocumentOperationType.UPDATE, clock.instant()); if (getProperty(request, DRY_RUN, booleanParser).orElse(false)) { handleFeedOperation(path, true, handler, new com.yahoo.documentapi.Response(-1)); return ignoredContent; @@ -486,7 +489,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { DocumentOperationParameters parameters = parametersFromRequest(request, ROUTE) .withResponseHandler(response -> { outstanding.decrementAndGet(); - updateUpdateMetrics(response.outcome(), update.getCreateIfNonExistent()); + updateUpdateMetrics(response.outcome(), latencyOf(request), update.getCreateIfNonExistent()); handleFeedOperation(path, parsed.fullyApplied(), handler, response); }); return () -> dispatchOperation(() -> asyncSession.update(update, parameters)); @@ -495,7 +498,8 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { } private ContentChannel deleteDocument(HttpRequest request, DocumentPath path, ResponseHandler rawHandler) { - ResponseHandler handler = new MeasuringResponseHandler(rawHandler, com.yahoo.documentapi.metrics.DocumentOperationType.REMOVE, clock.instant()); + ResponseHandler handler = new MeasuringResponseHandler( + request, rawHandler, com.yahoo.documentapi.metrics.DocumentOperationType.REMOVE, clock.instant()); if (getProperty(request, DRY_RUN, booleanParser).orElse(false)) { handleFeedOperation(path, true, handler, new com.yahoo.documentapi.Response(-1)); return ignoredContent; @@ -507,7 +511,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { DocumentOperationParameters parameters = parametersFromRequest(request, ROUTE) .withResponseHandler(response -> { outstanding.decrementAndGet(); - updateRemoveMetrics(response.outcome()); + updateRemoveMetrics(response.outcome(), latencyOf(request)); handleFeedOperation(path, true, handler, response); }); return () -> dispatchOperation(() -> asyncSession.remove(remove, parameters)); @@ -1090,33 +1094,50 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { handle(path, null, handler, response, (document, jsonResponse) -> jsonResponse.commit(Response.Status.OK, fullyApplied)); } - private void updatePutMetrics(Outcome outcome) { + private static double latencyOf(HttpRequest r) { return (System.nanoTime() - r.relativeCreatedAtNanoTime()) / 1e+9d; } + + private void updatePutMetrics(Outcome outcome, double latency) { + incrementMetricNumOperations(); incrementMetricNumPuts(); sampleLatency(latency); switch (outcome) { - case SUCCESS -> metric.add(MetricNames.SUCCEEDED, 1, null); - case CONDITION_FAILED -> metric.add(MetricNames.CONDITION_NOT_MET, 1, null); - default -> metric.add(MetricNames.FAILED, 1, null); + case SUCCESS -> incrementMetricSucceeded(); + case CONDITION_FAILED -> { incrementMetricSucceeded(); incrementMetricConditionNotMet(); } + default -> incrementMetricFailed(); } } - private void updateUpdateMetrics(Outcome outcome, boolean create) { + private void updateUpdateMetrics(Outcome outcome, double latency, boolean create) { if (create && outcome == Outcome.NOT_FOUND) outcome = Outcome.SUCCESS; // >_< + incrementMetricNumOperations(); incrementMetricNumUpdates(); sampleLatency(latency); switch (outcome) { - case SUCCESS -> metric.add(MetricNames.SUCCEEDED, 1, null); - case NOT_FOUND -> metric.add(MetricNames.NOT_FOUND, 1, null); - case CONDITION_FAILED -> metric.add(MetricNames.CONDITION_NOT_MET, 1, null); - default -> metric.add(MetricNames.FAILED, 1, null); + case SUCCESS -> incrementMetricSucceeded(); + case NOT_FOUND -> { incrementMetricSucceeded(); incrementMetricNotFound(); } + case CONDITION_FAILED -> { incrementMetricSucceeded(); incrementMetricConditionNotMet(); } + default -> incrementMetricFailed(); } } - private void updateRemoveMetrics(Outcome outcome) { + private void updateRemoveMetrics(Outcome outcome, double latency) { + incrementMetricNumOperations(); incrementMetricNumRemoves(); sampleLatency(latency); switch (outcome) { - case SUCCESS: - case NOT_FOUND: metric.add(MetricNames.SUCCEEDED, 1, null); break; - case CONDITION_FAILED: metric.add(MetricNames.CONDITION_NOT_MET, 1, null); break; - default: metric.add(MetricNames.FAILED, 1, null); break; + case SUCCESS -> incrementMetricSucceeded(); + case NOT_FOUND -> { incrementMetricSucceeded(); incrementMetricNotFound(); } + case CONDITION_FAILED -> { incrementMetricSucceeded(); incrementMetricConditionNotMet(); } + default -> incrementMetricFailed(); } } + private void sampleLatency(double latency) { setMetric(MetricNames.LATENCY, latency); } + private void incrementMetricNumOperations() { incrementMetric(MetricNames.NUM_OPERATIONS); } + private void incrementMetricNumPuts() { incrementMetric(MetricNames.NUM_PUTS); } + private void incrementMetricNumRemoves() { incrementMetric(MetricNames.NUM_REMOVES); } + private void incrementMetricNumUpdates() { incrementMetric(MetricNames.NUM_UPDATES); } + private void incrementMetricFailed() { incrementMetric(MetricNames.FAILED); } + private void incrementMetricConditionNotMet() { incrementMetric(MetricNames.CONDITION_NOT_MET); } + private void incrementMetricSucceeded() { incrementMetric(MetricNames.SUCCEEDED); } + private void incrementMetricNotFound() { incrementMetric(MetricNames.NOT_FOUND); } + private void incrementMetric(String n) { metric.add(n, 1, null); } + private void setMetric(String n, Number v) { metric.set(n, v, null); } + // ------------------------------------------------- Visits ------------------------------------------------ private VisitorParameters parseGetParameters(HttpRequest request, DocumentPath path, boolean streamed) { @@ -1438,8 +1459,13 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { private final ResponseHandler delegate; private final com.yahoo.documentapi.metrics.DocumentOperationType type; private final Instant start; + private final HttpRequest request; - private MeasuringResponseHandler(ResponseHandler delegate, com.yahoo.documentapi.metrics.DocumentOperationType type, Instant start) { + private MeasuringResponseHandler(HttpRequest request, + ResponseHandler delegate, + com.yahoo.documentapi.metrics.DocumentOperationType type, + Instant start) { + this.request = request; this.delegate = delegate; this.type = type; this.start = start; @@ -1447,17 +1473,27 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { @Override public ContentChannel handleResponse(Response response) { - var statusCodeGroup = response.getStatus() / 100; - // Status code 412 - condition not met - is considered OK - if (statusCodeGroup == 2 || response.getStatus() == 412) - metrics.reportSuccessful(type, start); - else if (statusCodeGroup == 4) - metrics.reportFailure(type, DocumentOperationStatus.REQUEST_ERROR); - else if (statusCodeGroup == 5) - metrics.reportFailure(type, DocumentOperationStatus.SERVER_ERROR); + switch (response.getStatus()) { + case 200 -> report(DocumentOperationStatus.OK); + case 400 -> report(DocumentOperationStatus.REQUEST_ERROR); + case 404 -> report(DocumentOperationStatus.NOT_FOUND); + case 412 -> report(DocumentOperationStatus.CONDITION_FAILED); + case 429 -> report(DocumentOperationStatus.TOO_MANY_REQUESTS); + case 500,502,503,504,507 -> report(DocumentOperationStatus.SERVER_ERROR); + default -> throw new IllegalStateException("Unexpected status code '%s'".formatted(response.getStatus())); + } + metrics.reportHttpRequest(clientVersion()); return delegate.handleResponse(response); } + private void report(DocumentOperationStatus... status) { metrics.report(type, start, status); } + + private String clientVersion() { + return Optional.ofNullable(request.headers().get(Headers.CLIENT_VERSION)) + .filter(l -> !l.isEmpty()).map(l -> l.get(0)) + .orElse("unknown"); + } + } static class StorageCluster { diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/documentapi/metrics/DocumentApiMetrics.java b/vespaclient-container-plugin/src/main/java/com/yahoo/documentapi/metrics/DocumentApiMetrics.java index 890f06dd094..f1335351ac6 100644 --- a/vespaclient-container-plugin/src/main/java/com/yahoo/documentapi/metrics/DocumentApiMetrics.java +++ b/vespaclient-container-plugin/src/main/java/com/yahoo/documentapi/metrics/DocumentApiMetrics.java @@ -10,6 +10,7 @@ import com.yahoo.metrics.simple.Point; import java.time.Duration; import java.time.Instant; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -52,16 +53,22 @@ public class DocumentApiMetrics { feeds.add(point); } - public void reportSuccessful(DocumentOperationType documentOperationType, Instant startTime) { - double latency = Duration.between(startTime, Instant.now()).toMillis() / 1000.0d; - reportSuccessful(documentOperationType, latency); - } - public void reportFailure(DocumentOperationType documentOperationType, DocumentOperationStatus documentOperationStatus) { Point point = points.get(documentOperationStatus).get(documentOperationType); feeds.add(point); } + public void report(DocumentOperationType type, Instant start, DocumentOperationStatus... status) { + double latency = latency(start); + for (var s : status) { + var point = points.get(s).get(type); + feedLatency.sample(latency, point); + feeds.add(point); + } + } + + private static double latency(Instant start) { return Duration.between(start, Instant.now()).toMillis() / 1000d; } + public void reportHttpRequest(String clientVersion) { if (clientVersion != null) { try { diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/documentapi/metrics/DocumentOperationStatus.java b/vespaclient-container-plugin/src/main/java/com/yahoo/documentapi/metrics/DocumentOperationStatus.java index 97e5e9e63d8..f22eedeb5bc 100644 --- a/vespaclient-container-plugin/src/main/java/com/yahoo/documentapi/metrics/DocumentOperationStatus.java +++ b/vespaclient-container-plugin/src/main/java/com/yahoo/documentapi/metrics/DocumentOperationStatus.java @@ -17,7 +17,7 @@ import java.util.Set; */ public enum DocumentOperationStatus { - OK, REQUEST_ERROR, SERVER_ERROR; + OK, REQUEST_ERROR, SERVER_ERROR, CONDITION_FAILED, NOT_FOUND, TOO_MANY_REQUESTS; public static DocumentOperationStatus fromMessageBusErrorCodes(Set<Integer> errorCodes) { if (errorCodes.size() == 1 && errorCodes.contains(DocumentProtocol.ERROR_NO_SPACE)) |