diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index c0f4a7835..a7e83d9ca 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -83,7 +83,7 @@ jobs: if: ${{ cancelled() || failure() }} uses: actions/upload-artifact@master with: - name: logs.tgz + name: logs-${{github.job}}.tgz path: ./logs.tgz # test encodeURIComponent() and normalize('NFC') comparing to Javascript behavior diff --git a/src/main/java/com/arangodb/ArangoDB.java b/src/main/java/com/arangodb/ArangoDB.java index 027750d4a..c30c74a05 100644 --- a/src/main/java/com/arangodb/ArangoDB.java +++ b/src/main/java/com/arangodb/ArangoDB.java @@ -326,6 +326,17 @@ public Builder loadBalancingStrategy(final LoadBalancingStrategy loadBalancingSt return this; } + /** + * Setting the amount of samples kept for queue time metrics + * + * @param responseQueueTimeSamples amount of samples to keep + * @return {@link ArangoDB.Builder} + */ + public Builder responseQueueTimeSamples(final Integer responseQueueTimeSamples) { + setResponseQueueTimeSamples(responseQueueTimeSamples); + return this; + } + /** * Register a custom {@link VPackSerializer} for a specific type to be used within the internal serialization * process. @@ -658,7 +669,8 @@ public synchronized ArangoDB build() { protocol, hostResolver, hostHandler, - new ArangoContext()); + new ArangoContext(), + responseQueueTimeSamples, timeout); } } @@ -705,6 +717,11 @@ default ArangoDatabase db(String name) { */ ArangoDatabase db(DbName dbName); + /** + * @return entry point for accessing client metrics + */ + ArangoMetrics metrics(); + /** * Creates a new database with the given name. * diff --git a/src/main/java/com/arangodb/ArangoMetrics.java b/src/main/java/com/arangodb/ArangoMetrics.java new file mode 100644 index 000000000..21d659b53 --- /dev/null +++ b/src/main/java/com/arangodb/ArangoMetrics.java @@ -0,0 +1,34 @@ +/* + * DISCLAIMER + * + * Copyright 2016 ArangoDB GmbH, Cologne, Germany + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is ArangoDB GmbH, Cologne, Germany + */ + +package com.arangodb; + +/** + * Interface for accessing metrics. + * + * @author Michele Rastelli + * @since ArangoDB 3.9 + */ +public interface ArangoMetrics { + /** + * @return queue time metrics + */ + QueueTimeMetrics getQueueTime(); +} diff --git a/src/main/java/com/arangodb/QueueTimeMetrics.java b/src/main/java/com/arangodb/QueueTimeMetrics.java new file mode 100644 index 000000000..333d14997 --- /dev/null +++ b/src/main/java/com/arangodb/QueueTimeMetrics.java @@ -0,0 +1,45 @@ +/* + * DISCLAIMER + * + * Copyright 2016 ArangoDB GmbH, Cologne, Germany + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is ArangoDB GmbH, Cologne, Germany + */ + +package com.arangodb; + +import com.arangodb.model.QueueTimeSample; + +/** + * Interface for accessing queue time latency metrics, reported by the "X-Arango-Queue-Time-Seconds" response header. + * This header contains the most recent request (de)queuing time (in seconds) as tracked by the server’s scheduler. + * + * @author Michele Rastelli + * @see API Documentation + * @since ArangoDB 3.9 + */ +public interface QueueTimeMetrics { + + /** + * @return all the n values observed + */ + QueueTimeSample[] getValues(); + + /** + * @return the average of the last n values observed, 0.0 if no value has been observed (i.e. in ArangoDB versions + * prior to 3.9). + */ + double getAvg(); +} diff --git a/src/main/java/com/arangodb/async/ArangoDBAsync.java b/src/main/java/com/arangodb/async/ArangoDBAsync.java index 99e3cbe37..81849d776 100644 --- a/src/main/java/com/arangodb/async/ArangoDBAsync.java +++ b/src/main/java/com/arangodb/async/ArangoDBAsync.java @@ -20,10 +20,7 @@ package com.arangodb.async; -import com.arangodb.ArangoDBException; -import com.arangodb.ArangoSerializationAccessor; -import com.arangodb.DbName; -import com.arangodb.Protocol; +import com.arangodb.*; import com.arangodb.async.internal.ArangoDBAsyncImpl; import com.arangodb.async.internal.velocystream.VstCommunicationAsync; import com.arangodb.async.internal.velocystream.VstConnectionFactoryAsync; @@ -110,6 +107,11 @@ default ArangoDatabaseAsync db(final String name) { */ ArangoDatabaseAsync db(final DbName dbName); + /** + * @return entry point for accessing client metrics + */ + ArangoMetrics metrics(); + /** * Creates a new database * @@ -497,6 +499,17 @@ public Builder acquireHostList(final Boolean acquireHostList) { return this; } + /** + * Setting the amount of samples kept for queue time metrics + * + * @param responseQueueTimeSamples amount of samples to keep + * @return {@link ArangoDBAsync.Builder} + */ + public Builder responseQueueTimeSamples(final Integer responseQueueTimeSamples) { + setResponseQueueTimeSamples(responseQueueTimeSamples); + return this; + } + /** * Sets the load balancing strategy to be used in an ArangoDB cluster setup. * In case of Active-Failover deployment set to {@link LoadBalancingStrategy#NONE} or not set at all, since that @@ -838,7 +851,9 @@ public synchronized ArangoDBAsync build() { syncHostResolver, asyncHostHandler, syncHostHandler, - new ArangoContext()); + new ArangoContext(), + responseQueueTimeSamples, + timeout); } private VstCommunicationAsync.Builder asyncBuilder(final HostHandler hostHandler) { diff --git a/src/main/java/com/arangodb/async/internal/ArangoDBAsyncImpl.java b/src/main/java/com/arangodb/async/internal/ArangoDBAsyncImpl.java index 15a4acd5a..170e4d1a7 100644 --- a/src/main/java/com/arangodb/async/internal/ArangoDBAsyncImpl.java +++ b/src/main/java/com/arangodb/async/internal/ArangoDBAsyncImpl.java @@ -21,6 +21,7 @@ package com.arangodb.async.internal; import com.arangodb.ArangoDBException; +import com.arangodb.ArangoMetrics; import com.arangodb.DbName; import com.arangodb.async.ArangoDBAsync; import com.arangodb.async.ArangoDatabaseAsync; @@ -68,10 +69,13 @@ public ArangoDBAsyncImpl( final HostResolver syncHostResolver, final HostHandler asyncHostHandler, final HostHandler syncHostHandler, - final ArangoContext context + final ArangoContext context, + final int responseQueueTimeSamples, + final int timeoutMs ) { - super(new ArangoExecutorAsync(asyncCommBuilder.build(util.get(Serializer.INTERNAL)), util, new DocumentCache()), util, context); + super(new ArangoExecutorAsync(asyncCommBuilder.build(util.get(Serializer.INTERNAL)), util, new DocumentCache(), + new QueueTimeMetricsImpl(responseQueueTimeSamples), timeoutMs), util, context); final VstCommunication cacheCom = syncCommBuilder.build(util.get(Serializer.INTERNAL)); @@ -79,7 +83,8 @@ public ArangoDBAsyncImpl( this.asyncHostHandler = asyncHostHandler; this.syncHostHandler = syncHostHandler; - ArangoExecutorSync arangoExecutorSync = new ArangoExecutorSync(cp, util, new DocumentCache()); + ArangoExecutorSync arangoExecutorSync = new ArangoExecutorSync(cp, util, new DocumentCache(), + new QueueTimeMetricsImpl(responseQueueTimeSamples), timeoutMs); asyncHostResolver.init(arangoExecutorSync, util.get(Serializer.INTERNAL)); syncHostResolver.init(arangoExecutorSync, util.get(Serializer.INTERNAL)); @@ -121,6 +126,11 @@ public ArangoDatabaseAsync db(final DbName name) { return new ArangoDatabaseAsyncImpl(this, name); } + @Override + public ArangoMetrics metrics() { + return new ArangoMetricsImpl(executor.getQueueTimeMetrics()); + } + @Override public CompletableFuture createDatabase(final DbName name) { return createDatabase(new DBCreateOptions().name(name)); diff --git a/src/main/java/com/arangodb/async/internal/ArangoExecutorAsync.java b/src/main/java/com/arangodb/async/internal/ArangoExecutorAsync.java index 860c185de..58d7659c5 100644 --- a/src/main/java/com/arangodb/async/internal/ArangoExecutorAsync.java +++ b/src/main/java/com/arangodb/async/internal/ArangoExecutorAsync.java @@ -24,6 +24,7 @@ import com.arangodb.async.internal.velocystream.VstCommunicationAsync; import com.arangodb.internal.ArangoExecutor; import com.arangodb.internal.DocumentCache; +import com.arangodb.internal.QueueTimeMetricsImpl; import com.arangodb.internal.net.HostHandle; import com.arangodb.internal.util.ArangoSerializationFactory; import com.arangodb.velocystream.Request; @@ -44,8 +45,8 @@ public class ArangoExecutorAsync extends ArangoExecutor { private final ExecutorService outgoingExecutor = Executors.newSingleThreadExecutor(); public ArangoExecutorAsync(final VstCommunicationAsync communication, final ArangoSerializationFactory util, - final DocumentCache documentCache) { - super(util, documentCache); + final DocumentCache documentCache, final QueueTimeMetricsImpl qtMetrics, final int timeoutMs) { + super(util, documentCache, qtMetrics, timeoutMs); this.communication = communication; } @@ -67,8 +68,11 @@ private CompletableFuture execute( final HostHandle hostHandle) { return CompletableFuture.completedFuture(null) - .thenComposeAsync((it) -> communication.execute(request, hostHandle), outgoingExecutor) - .thenApplyAsync(responseDeserializer::deserialize); + .thenComposeAsync((it) -> communication.execute(interceptRequest(request), hostHandle), outgoingExecutor) + .thenApplyAsync(response -> { + interceptResponse(response); + return responseDeserializer.deserialize(response); + }); } public void disconnect() { diff --git a/src/main/java/com/arangodb/entity/CursorEntity.java b/src/main/java/com/arangodb/entity/CursorEntity.java index 4bb2a3d26..66a9a7010 100644 --- a/src/main/java/com/arangodb/entity/CursorEntity.java +++ b/src/main/java/com/arangodb/entity/CursorEntity.java @@ -97,6 +97,7 @@ public Map getMeta() { public Map cleanupMeta(Map meta) { meta.remove("Content-Length"); meta.remove("Transfer-Encoding"); + meta.remove("X-Arango-Queue-Time-Seconds"); return meta; } diff --git a/src/main/java/com/arangodb/internal/ArangoDBImpl.java b/src/main/java/com/arangodb/internal/ArangoDBImpl.java index b297f9c43..be0ffd48c 100644 --- a/src/main/java/com/arangodb/internal/ArangoDBImpl.java +++ b/src/main/java/com/arangodb/internal/ArangoDBImpl.java @@ -61,12 +61,12 @@ public class ArangoDBImpl extends InternalArangoDB implement public ArangoDBImpl(final VstCommunicationSync.Builder vstBuilder, final HttpCommunication.Builder httpBuilder, final ArangoSerializationFactory util, final Protocol protocol, final HostResolver hostResolver, - final HostHandler hostHandler, final ArangoContext context) { + final HostHandler hostHandler, final ArangoContext context, int responseQueueTimeSamples, final int timeoutMs) { super(new ArangoExecutorSync( createProtocol(vstBuilder, httpBuilder, util.get(Serializer.INTERNAL), protocol), util, - new DocumentCache()), + new DocumentCache(), new QueueTimeMetricsImpl(responseQueueTimeSamples), timeoutMs), util, context); @@ -140,6 +140,11 @@ public ArangoDatabase db(final DbName dbName) { return new ArangoDatabaseImpl(this, dbName).setCursorInitializer(cursorInitializer); } + @Override + public ArangoMetrics metrics() { + return new ArangoMetricsImpl(executor.getQueueTimeMetrics()); + } + @Override public Boolean createDatabase(final DbName dbName) throws ArangoDBException { return createDatabase(new DBCreateOptions().name(dbName)); diff --git a/src/main/java/com/arangodb/internal/ArangoDefaults.java b/src/main/java/com/arangodb/internal/ArangoDefaults.java index b81626fa5..ad8f1ed36 100644 --- a/src/main/java/com/arangodb/internal/ArangoDefaults.java +++ b/src/main/java/com/arangodb/internal/ArangoDefaults.java @@ -50,5 +50,6 @@ private ArangoDefaults() { public static final boolean DEFAULT_ACQUIRE_HOST_LIST = false; public static final int DEFAULT_ACQUIRE_HOST_LIST_INTERVAL = 60 * 60 * 1000; // hour public static final LoadBalancingStrategy DEFAULT_LOAD_BALANCING_STRATEGY = LoadBalancingStrategy.NONE; + public static final int DEFAULT_RESPONSE_QUEUE_TIME_SAMPLES = 10; } diff --git a/src/main/java/com/arangodb/internal/ArangoErrors.java b/src/main/java/com/arangodb/internal/ArangoErrors.java index c3d71ce6f..ba2bc5f57 100644 --- a/src/main/java/com/arangodb/internal/ArangoErrors.java +++ b/src/main/java/com/arangodb/internal/ArangoErrors.java @@ -32,5 +32,6 @@ private ArangoErrors() { public static final Integer ERROR_ARANGO_DATA_SOURCE_NOT_FOUND = 1203; public static final Integer ERROR_ARANGO_DATABASE_NOT_FOUND = 1228; public static final Integer ERROR_GRAPH_NOT_FOUND = 1924; + public static final Integer QUEUE_TIME_VIOLATED = 21004; } diff --git a/src/main/java/com/arangodb/internal/ArangoExecutor.java b/src/main/java/com/arangodb/internal/ArangoExecutor.java index 39cfe72f3..1dff75c10 100644 --- a/src/main/java/com/arangodb/internal/ArangoExecutor.java +++ b/src/main/java/com/arangodb/internal/ArangoExecutor.java @@ -20,10 +20,12 @@ package com.arangodb.internal; +import com.arangodb.QueueTimeMetrics; import com.arangodb.entity.Entity; import com.arangodb.internal.util.ArangoSerializationFactory; import com.arangodb.internal.util.ArangoSerializationFactory.Serializer; import com.arangodb.velocypack.exception.VPackException; +import com.arangodb.velocystream.Request; import com.arangodb.velocystream.Response; import java.lang.reflect.ParameterizedType; @@ -69,12 +71,17 @@ private boolean isInternal(final Type type) { } private final DocumentCache documentCache; + private final QueueTimeMetricsImpl qtMetrics; private final ArangoSerializationFactory util; + private final String timeoutS; - protected ArangoExecutor(final ArangoSerializationFactory util, final DocumentCache documentCache) { + protected ArangoExecutor(final ArangoSerializationFactory util, final DocumentCache documentCache, + final QueueTimeMetricsImpl qtMetrics, final int timeoutMs) { super(); this.documentCache = documentCache; + this.qtMetrics = qtMetrics; this.util = util; + timeoutS = timeoutMs >= 1000 ? Integer.toString(timeoutMs / 1000) : null; } public DocumentCache documentCache() { @@ -85,4 +92,19 @@ public interface ResponseDeserializer { T deserialize(Response response) throws VPackException; } + protected final void interceptResponse(Response response) { + String queueTime = response.getMeta().get("X-Arango-Queue-Time-Seconds"); + if (queueTime != null) { + qtMetrics.add(Double.parseDouble(queueTime)); + } + } + + protected final Request interceptRequest(Request request) { + request.putHeaderParam("X-Arango-Queue-Time-Seconds", timeoutS); + return request; + } + + public QueueTimeMetrics getQueueTimeMetrics() { + return qtMetrics; + } } diff --git a/src/main/java/com/arangodb/internal/ArangoExecutorSync.java b/src/main/java/com/arangodb/internal/ArangoExecutorSync.java index 31c18d797..2f60d2b56 100644 --- a/src/main/java/com/arangodb/internal/ArangoExecutorSync.java +++ b/src/main/java/com/arangodb/internal/ArangoExecutorSync.java @@ -44,8 +44,8 @@ public class ArangoExecutorSync extends ArangoExecutor { private final CommunicationProtocol protocol; public ArangoExecutorSync(final CommunicationProtocol protocol, final ArangoSerializationFactory util, - final DocumentCache documentCache) { - super(util, documentCache); + final DocumentCache documentCache, final QueueTimeMetricsImpl qtMetrics, final int timeoutMs) { + super(util, documentCache, qtMetrics, timeoutMs); this.protocol = protocol; } @@ -68,7 +68,8 @@ public T execute( try { - final Response response = protocol.execute(request, hostHandle); + final Response response = protocol.execute(interceptRequest(request), hostHandle); + interceptResponse(response); T deserialize = responseDeserializer.deserialize(response); if (deserialize instanceof MetaAware) { diff --git a/src/main/java/com/arangodb/internal/ArangoMetricsImpl.java b/src/main/java/com/arangodb/internal/ArangoMetricsImpl.java new file mode 100644 index 000000000..dd13dea2f --- /dev/null +++ b/src/main/java/com/arangodb/internal/ArangoMetricsImpl.java @@ -0,0 +1,42 @@ +/* + * DISCLAIMER + * + * Copyright 2016 ArangoDB GmbH, Cologne, Germany + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is ArangoDB GmbH, Cologne, Germany + */ + +package com.arangodb.internal; + +import com.arangodb.ArangoMetrics; +import com.arangodb.QueueTimeMetrics; + +/** + * @author Michele Rastelli + */ +public class ArangoMetricsImpl implements ArangoMetrics { + + private final QueueTimeMetrics queueTimeMetrics; + + public ArangoMetricsImpl(QueueTimeMetrics queueTimeMetrics) { + this.queueTimeMetrics = queueTimeMetrics; + } + + @Override + public QueueTimeMetrics getQueueTime() { + return queueTimeMetrics; + } + +} diff --git a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java index 3d8bb4907..6abc81bfa 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java +++ b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java @@ -76,6 +76,7 @@ public abstract class InternalArangoDBBuilder { private static final String PROPERTY_KEY_ACQUIRE_HOST_LIST = "arangodb.acquireHostList"; private static final String PROPERTY_KEY_ACQUIRE_HOST_LIST_INTERVAL = "arangodb.acquireHostList.interval"; private static final String PROPERTY_KEY_LOAD_BALANCING_STRATEGY = "arangodb.loadBalancingStrategy"; + private static final String PROPERTY_KEY_RESPONSE_QUEUE_TIME_SAMPLES = "arangodb.metrics.responseQueueTimeSamples"; private static final String DEFAULT_PROPERTY_FILE = "/arangodb.properties"; protected final List hosts; @@ -101,6 +102,7 @@ public abstract class InternalArangoDBBuilder { protected Integer acquireHostListInterval; protected LoadBalancingStrategy loadBalancingStrategy; protected ArangoSerialization customSerializer; + protected Integer responseQueueTimeSamples; public InternalArangoDBBuilder() { @@ -152,6 +154,7 @@ protected void loadProperties(final Properties properties) { acquireHostList = loadAcquireHostList(properties, acquireHostList); acquireHostListInterval = loadAcquireHostListInterval(properties, acquireHostListInterval); loadBalancingStrategy = loadLoadBalancingStrategy(properties, loadBalancingStrategy); + responseQueueTimeSamples = loadResponseQueueTimeSamples(properties, responseQueueTimeSamples); } protected void setHost(final String host, final int port) { @@ -218,6 +221,10 @@ protected void setLoadBalancingStrategy(final LoadBalancingStrategy loadBalancin this.loadBalancingStrategy = loadBalancingStrategy; } + protected void setResponseQueueTimeSamples(final Integer responseQueueTimeSamples) { + this.responseQueueTimeSamples = responseQueueTimeSamples; + } + protected void serializer(final ArangoSerializer serializer) { this.serializer = serializer; } @@ -357,6 +364,11 @@ private static int loadAcquireHostListInterval(final Properties properties, fina ArangoDefaults.DEFAULT_ACQUIRE_HOST_LIST_INTERVAL)); } + private static int loadResponseQueueTimeSamples(final Properties properties, final Integer currentValue) { + return Integer.parseInt(getProperty(properties, PROPERTY_KEY_RESPONSE_QUEUE_TIME_SAMPLES, currentValue, + ArangoDefaults.DEFAULT_RESPONSE_QUEUE_TIME_SAMPLES)); + } + private static LoadBalancingStrategy loadLoadBalancingStrategy( final Properties properties, final LoadBalancingStrategy currentValue) { diff --git a/src/main/java/com/arangodb/internal/QueueTimeMetricsImpl.java b/src/main/java/com/arangodb/internal/QueueTimeMetricsImpl.java new file mode 100644 index 000000000..42abe46e9 --- /dev/null +++ b/src/main/java/com/arangodb/internal/QueueTimeMetricsImpl.java @@ -0,0 +1,137 @@ +/* + * DISCLAIMER + * + * Copyright 2016 ArangoDB GmbH, Cologne, Germany + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright holder is ArangoDB GmbH, Cologne, Germany + */ + +package com.arangodb.internal; + +import com.arangodb.QueueTimeMetrics; +import com.arangodb.model.QueueTimeSample; + +import java.util.Arrays; + +/** + * @author Michele Rastelli + */ +public class QueueTimeMetricsImpl implements QueueTimeMetrics { + private final CircularFifoQueue samples; + + public QueueTimeMetricsImpl(int queueSize) { + samples = new CircularFifoQueue(queueSize); + } + + @Override + public QueueTimeSample[] getValues() { + return samples.getElements(); + } + + @Override + public double getAvg() { + return samples.getAvg(); + } + + void add(double value) { + add(new QueueTimeSample(System.currentTimeMillis(), value)); + } + + void add(QueueTimeSample value) { + samples.add(value); + } + + void clear() { + samples.clear(); + } + + private static class CircularFifoQueue { + private final QueueTimeSample[] elements; + + /** + * Array index of the oldest queue element. + */ + private int start; + + /** + * Capacity of the queue. + */ + private final int size; + + /** + * Amount of elements in the queue. + */ + private int count; + + /** + * Sum of the values in the queue. + */ + private double sum; + + CircularFifoQueue(final int size) { + elements = new QueueTimeSample[size]; + this.size = elements.length; + clear(); + } + + /** + * @return the average of the values in the queue, 0.0 if the queue is empty. + */ + synchronized double getAvg() { + if (count == 0) return 0.0; + return sum / count; + } + + synchronized void clear() { + start = 0; + count = 0; + sum = 0.0; + Arrays.fill(elements, null); + } + + /** + * Adds the given element to this queue. If the queue is full, the least recently added + * element is replaced with the given one. + * + * @param element the element to add + */ + synchronized void add(final QueueTimeSample element) { + if (count < size) { + count++; + } + QueueTimeSample overridden = elements[start]; + if (overridden != null) { + sum -= overridden.value; + } + elements[start++] = element; + if (start >= size) { + start = 0; + } + sum += element.value; + } + + synchronized QueueTimeSample[] getElements() { + QueueTimeSample[] out = new QueueTimeSample[count]; + if (count < size) { + System.arraycopy(elements, 0, out, 0, count); + } else { + System.arraycopy(elements, start, out, 0, size - start); + System.arraycopy(elements, 0, out, size - start, start); + } + return out; + } + + } +} diff --git a/src/main/java/com/arangodb/internal/util/ResponseUtils.java b/src/main/java/com/arangodb/internal/util/ResponseUtils.java index a7edaa179..0cc5e38cb 100644 --- a/src/main/java/com/arangodb/internal/util/ResponseUtils.java +++ b/src/main/java/com/arangodb/internal/util/ResponseUtils.java @@ -22,11 +22,14 @@ import com.arangodb.ArangoDBException; import com.arangodb.entity.ErrorEntity; +import com.arangodb.internal.ArangoErrors; import com.arangodb.internal.net.ArangoDBRedirectException; import com.arangodb.util.ArangoSerialization; import com.arangodb.velocypack.exception.VPackParserException; import com.arangodb.velocystream.Response; +import java.util.concurrent.TimeoutException; + /** * @author Mark Vollmary */ @@ -49,7 +52,11 @@ public static void checkError(final ArangoSerialization util, final Response res response.getMeta().get(HEADER_ENDPOINT)); } else if (response.getBody() != null) { final ErrorEntity errorEntity = util.deserialize(response.getBody(), ErrorEntity.class); - throw new ArangoDBException(errorEntity); + ArangoDBException e = new ArangoDBException(errorEntity); + if(ArangoErrors.QUEUE_TIME_VIOLATED.equals(e.getErrorNum())){ + throw new ArangoDBException(new TimeoutException().initCause(e)); + } + throw e; } else { throw new ArangoDBException(String.format("Response Code: %s", responseCode), responseCode); } diff --git a/src/main/java/com/arangodb/model/QueueTimeSample.java b/src/main/java/com/arangodb/model/QueueTimeSample.java new file mode 100644 index 000000000..841221761 --- /dev/null +++ b/src/main/java/com/arangodb/model/QueueTimeSample.java @@ -0,0 +1,41 @@ +package com.arangodb.model; + +import java.util.Objects; + +/** + * Represents an observed value of the server queue latency, as returned from the "X-Arango-Queue-Time-Seconds" response + * header. + * This header contains the most recent request (de)queuing time (in seconds) as tracked by the server’s scheduler. + * + * @author Michele Rastelli + * @see API Documentation + */ +public class QueueTimeSample { + /** + * Unix-timestamp in milliseconds, recorded at client side. + */ + public final long timestamp; + + /** + * Observed value. + */ + public final double value; + + public QueueTimeSample(long timestamp, double value) { + this.timestamp = timestamp; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QueueTimeSample that = (QueueTimeSample) o; + return timestamp == that.timestamp && Double.compare(that.value, value) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(timestamp, value); + } +} diff --git a/src/test/java/com/arangodb/ArangoDBTest.java b/src/test/java/com/arangodb/ArangoDBTest.java index 8e65d1d63..ca05e060f 100644 --- a/src/test/java/com/arangodb/ArangoDBTest.java +++ b/src/test/java/com/arangodb/ArangoDBTest.java @@ -21,13 +21,8 @@ package com.arangodb; import com.arangodb.entity.*; -import com.arangodb.model.DBCreateOptions; -import com.arangodb.model.DatabaseOptions; -import com.arangodb.model.DatabaseUsersOptions; -import com.arangodb.model.LogOptions; +import com.arangodb.model.*; import com.arangodb.model.LogOptions.SortOrder; -import com.arangodb.model.UserCreateOptions; -import com.arangodb.model.UserUpdateOptions; import com.arangodb.util.TestUtils; import com.arangodb.velocypack.exception.VPackException; import com.arangodb.velocystream.Request; @@ -50,6 +45,7 @@ import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.IntStream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -727,4 +723,34 @@ public void accessMultipleDatabases() { final ArangoDBVersion version2 = db2.getVersion(); assertThat(version2, is(notNullValue())); } + + @Test + public void queueTime() throws InterruptedException { + List threads = IntStream.range(0, 80) + .mapToObj(__ -> new Thread(() -> arangoDB.db().query("RETURN SLEEP(1)", Void.class))) + .collect(Collectors.toList()); + threads.forEach(Thread::start); + for (Thread it : threads) { + it.join(); + } + + QueueTimeMetrics qt = arangoDB.metrics().getQueueTime(); + double avg = qt.getAvg(); + QueueTimeSample[] values = qt.getValues(); + if (isAtLeastVersion(3, 9)) { + assertThat(values.length, is(20)); + for (int i = 0; i < values.length; i++) { + assertThat(values[i], is(notNullValue())); + assertThat(values[i].value, is(greaterThanOrEqualTo(0.0))); + if (i > 0) { + assertThat(values[i].timestamp, greaterThanOrEqualTo(values[i - 1].timestamp)); + } + } + assertThat(avg, is(greaterThan(0.0))); + } else { + assertThat(avg, is(0.0)); + assertThat(values, is(emptyArray())); + } + + } } diff --git a/src/test/java/com/arangodb/async/ArangoDBTest.java b/src/test/java/com/arangodb/async/ArangoDBTest.java index 02a389ae6..581b81615 100644 --- a/src/test/java/com/arangodb/async/ArangoDBTest.java +++ b/src/test/java/com/arangodb/async/ArangoDBTest.java @@ -20,14 +20,9 @@ package com.arangodb.async; -import com.arangodb.ArangoDB; -import com.arangodb.ArangoDBException; -import com.arangodb.ArangoDatabase; -import com.arangodb.DbName; +import com.arangodb.*; import com.arangodb.entity.*; -import com.arangodb.model.DBCreateOptions; -import com.arangodb.model.DatabaseOptions; -import com.arangodb.model.LogOptions; +import com.arangodb.model.*; import com.arangodb.model.LogOptions.SortOrder; import com.arangodb.model.UserCreateOptions; import com.arangodb.model.UserUpdateOptions; @@ -37,11 +32,11 @@ import com.arangodb.velocystream.RequestType; import org.junit.Test; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import java.util.stream.IntStream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -59,19 +54,19 @@ public class ArangoDBTest { private static final String PW = "machts der hund"; private static Boolean extendedNames; + private final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); + private final ArangoDB arangoDBSync = new ArangoDB.Builder().build(); + private boolean isEnterprise() { - final ArangoDB arangoDB = new ArangoDB.Builder().build(); - return arangoDB.getVersion().getLicense() == License.ENTERPRISE; + return arangoDBSync.getVersion().getLicense() == License.ENTERPRISE; } private boolean isCluster() { - final ArangoDB arangoDB = new ArangoDB.Builder().build(); - return arangoDB.getRole() == ServerRole.COORDINATOR; + return arangoDBSync.getRole() == ServerRole.COORDINATOR; } private boolean isAtLeastVersion(final int major, final int minor) { - final ArangoDB arangoDB = new ArangoDB.Builder().build(); - return com.arangodb.util.TestUtils.isAtLeastVersion(arangoDB.getVersion().getVersion(), major,minor,0); + return com.arangodb.util.TestUtils.isAtLeastVersion(arangoDBSync.getVersion().getVersion(), major, minor, 0); } private boolean supportsExtendedNames() { @@ -91,7 +86,6 @@ private boolean supportsExtendedNames() { @Test public void getVersion() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.getVersion() .whenComplete((version, ex) -> { assertThat(version, is(notNullValue())); @@ -103,7 +97,6 @@ public void getVersion() throws InterruptedException, ExecutionException { @Test(timeout = 2000) public void nestedGetVersion() { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); for (int i = 0; i < 100; i++) { try { arangoDB.getVersion() @@ -146,7 +139,6 @@ public void nestedGetVersion() { @Test public void createDatabase() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.createDatabase(BaseTest.TEST_DB) .whenComplete((result, ex) -> assertThat(result, is(true))) .get(); @@ -158,7 +150,6 @@ public void createDatabaseWithOptions() throws ExecutionException, InterruptedEx assumeTrue(isCluster()); assumeTrue(isAtLeastVersion(3, 6)); - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); final DbName dbName = DbName.of("testDB-" + TestUtils.generateRandomDbName(20, supportsExtendedNames())); final Boolean resultCreate = arangoDB.createDatabase(new DBCreateOptions() .name(dbName) @@ -186,7 +177,6 @@ public void createDatabaseWithOptionsSatellite() throws ExecutionException, Inte assumeTrue(isEnterprise()); assumeTrue(isAtLeastVersion(3, 6)); - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); final DbName dbName = DbName.of("testDB-" + TestUtils.generateRandomDbName(20, supportsExtendedNames())); final Boolean resultCreate = arangoDB.createDatabase(new DBCreateOptions() .name(dbName) @@ -210,7 +200,6 @@ public void createDatabaseWithOptionsSatellite() throws ExecutionException, Inte @Test public void deleteDatabase() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); final Boolean resultCreate = arangoDB.createDatabase(BaseTest.TEST_DB).get(); assertThat(resultCreate, is(true)); arangoDB.db(BaseTest.TEST_DB).drop() @@ -220,7 +209,6 @@ public void deleteDatabase() throws InterruptedException, ExecutionException { @Test public void getDatabases() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); Collection dbs = arangoDB.getDatabases().get(); assertThat(dbs, is(notNullValue())); assertThat(dbs.size(), is(greaterThan(0))); @@ -236,7 +224,6 @@ public void getDatabases() throws InterruptedException, ExecutionException { @Test public void getAccessibleDatabases() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.getAccessibleDatabases() .whenComplete((dbs, ex) -> { assertThat(dbs, is(notNullValue())); @@ -248,7 +235,6 @@ public void getAccessibleDatabases() throws InterruptedException, ExecutionExcep @Test public void getAccessibleDatabasesFor() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.getAccessibleDatabasesFor("root") .whenComplete((dbs, ex) -> { assertThat(dbs, is(notNullValue())); @@ -261,7 +247,6 @@ public void getAccessibleDatabasesFor() throws InterruptedException, ExecutionEx @Test public void createUser() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); try { arangoDB.createUser(USER, PW, null) .whenComplete((result, ex) -> { @@ -276,14 +261,12 @@ public void createUser() throws InterruptedException, ExecutionException { @Test public void deleteUser() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.createUser(USER, PW, null).get(); arangoDB.deleteUser(USER).get(); } @Test public void getUserRoot() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.getUser(ROOT) .whenComplete((user, ex) -> { assertThat(user, is(notNullValue())); @@ -294,7 +277,6 @@ public void getUserRoot() throws InterruptedException, ExecutionException { @Test public void getUser() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); try { arangoDB.createUser(USER, PW, null).get(); arangoDB.getUser(USER) @@ -308,7 +290,6 @@ public void getUser() throws InterruptedException, ExecutionException { @Test public void getUsersOnlyRoot() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.getUsers() .whenComplete((users, ex) -> { assertThat(users, is(notNullValue())); @@ -319,7 +300,6 @@ public void getUsersOnlyRoot() throws InterruptedException, ExecutionException { @Test public void getUsers() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); try { arangoDB.createUser(USER, PW, null).get(); arangoDB.getUsers() @@ -339,7 +319,6 @@ public void getUsers() throws InterruptedException, ExecutionException { @Test public void updateUserNoOptions() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); try { arangoDB.createUser(USER, PW, null).get(); arangoDB.updateUser(USER, null).get(); @@ -350,7 +329,6 @@ public void updateUserNoOptions() throws InterruptedException, ExecutionExceptio @Test public void updateUser() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); try { final Map extra = new HashMap<>(); extra.put("hund", false); @@ -381,7 +359,6 @@ public void updateUser() throws InterruptedException, ExecutionException { @Test public void replaceUser() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); try { final Map extra = new HashMap<>(); extra.put("hund", false); @@ -414,7 +391,6 @@ public void replaceUser() throws InterruptedException, ExecutionException { @Test public void updateUserDefaultDatabaseAccess() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); try { arangoDB.createUser(USER, PW).get(); arangoDB.grantDefaultDatabaseAccess(USER, Permissions.RW).get(); @@ -425,7 +401,6 @@ public void updateUserDefaultDatabaseAccess() throws InterruptedException, Execu @Test public void updateUserDefaultCollectionAccess() throws InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); try { arangoDB.createUser(USER, PW).get(); arangoDB.grantDefaultCollectionAccess(USER, Permissions.RW).get(); @@ -458,7 +433,6 @@ public void authenticationFailUser() throws InterruptedException { @Test public void execute() throws VPackException, InterruptedException, ExecutionException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB .execute(new Request(DbName.SYSTEM, RequestType.GET, "/_api/version")) .whenComplete((response, ex) -> { @@ -483,7 +457,6 @@ public void execute_acquireHostList_enabled() throws VPackException, Interrupted @Test public void getLogs() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 7)); // it fails in 3.6 active-failover (BTS-362) - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.getLogs(null) .whenComplete((logs, ex) -> { assertThat(logs, is(notNullValue())); @@ -499,7 +472,6 @@ public void getLogs() throws InterruptedException, ExecutionException { @Test public void getLogsUpto() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 7)); // it fails in 3.6 active-failover (BTS-362) - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); final LogEntity logs = arangoDB.getLogs(null).get(); arangoDB.getLogs(new LogOptions().upto(LogLevel.WARNING)) .whenComplete((logsUpto, ex) -> { @@ -513,7 +485,6 @@ public void getLogsUpto() throws InterruptedException, ExecutionException { @Test public void getLogsLevel() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 7)); // it fails in 3.6 active-failover (BTS-362) - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); final LogEntity logs = arangoDB.getLogs(null).get(); arangoDB.getLogs(new LogOptions().level(LogLevel.INFO)) .whenComplete((logsInfo, ex) -> { @@ -527,7 +498,6 @@ public void getLogsLevel() throws InterruptedException, ExecutionException { @Test public void getLogsStart() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 7)); // it fails in 3.6 active-failover (BTS-362) - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); final LogEntity logs = arangoDB.getLogs(null).get(); assertThat(logs.getLid(), not(empty())); arangoDB.getLogs(new LogOptions().start(logs.getLid().get(0) + 1)) @@ -541,7 +511,6 @@ public void getLogsStart() throws InterruptedException, ExecutionException { @Test public void getLogsSize() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 7)); // it fails in 3.6 active-failover (BTS-362) - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); final LogEntity logs = arangoDB.getLogs(null).get(); assertThat(logs.getLid().size(), greaterThan(0)); arangoDB.getLogs(new LogOptions().size(logs.getLid().size() - 1)) @@ -555,7 +524,6 @@ public void getLogsSize() throws InterruptedException, ExecutionException { @Test public void getLogsOffset() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 7)); // it fails in 3.6 active-failover (BTS-362) - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); final LogEntity logs = arangoDB.getLogs(null).get(); assertThat(logs.getTotalAmount(), greaterThan(0L)); arangoDB.getLogs(new LogOptions().offset((int) (logs.getTotalAmount() - 1))) @@ -569,7 +537,6 @@ public void getLogsOffset() throws InterruptedException, ExecutionException { @Test public void getLogsSearch() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 7)); // it fails in 3.6 active-failover (BTS-362) - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); final LogEntity logs = arangoDB.getLogs(null).get(); arangoDB.getLogs(new LogOptions().search(BaseTest.TEST_DB.get())) .whenComplete((logsSearch, ex) -> { @@ -582,7 +549,6 @@ public void getLogsSearch() throws InterruptedException, ExecutionException { @Test public void getLogsSortAsc() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 7)); // it fails in 3.6 active-failover (BTS-362) - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.getLogs(new LogOptions().sort(SortOrder.asc)) .whenComplete((logs, ex) -> { assertThat(logs, is(notNullValue())); @@ -598,7 +564,6 @@ public void getLogsSortAsc() throws InterruptedException, ExecutionException { @Test public void getLogsSortDesc() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 7)); // it fails in 3.6 active-failover (BTS-362) - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.getLogs(new LogOptions().sort(SortOrder.desc)) .whenComplete((logs, ex) -> { assertThat(logs, is(notNullValue())); @@ -614,7 +579,6 @@ public void getLogsSortDesc() throws InterruptedException, ExecutionException { @Test public void getLogEntries() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 8)); - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.getLogEntries(null) .whenComplete((logs, ex) -> { assertThat(logs, is(notNullValue())); @@ -627,7 +591,6 @@ public void getLogEntries() throws InterruptedException, ExecutionException { @Test public void getLogEntriesSearch() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 8)); - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); final LogEntriesEntity logs = arangoDB.getLogEntries(null).get(); arangoDB.getLogs(new LogOptions().search(BaseTest.TEST_DB.get())) .whenComplete((logsSearch, ex) -> { @@ -640,7 +603,6 @@ public void getLogEntriesSearch() throws InterruptedException, ExecutionExceptio @Test public void getLogLevel() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 7)); // it fails in 3.6 active-failover (BTS-362) - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); arangoDB.getLogLevel() .whenComplete((logLevel, ex) -> { assertThat(logLevel, is(notNullValue())); @@ -652,7 +614,6 @@ public void getLogLevel() throws InterruptedException, ExecutionException { @Test public void setLogLevel() throws InterruptedException, ExecutionException { assumeTrue(isAtLeastVersion(3, 7)); // it fails in 3.6 active-failover (BTS-362) - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().build(); final LogLevelEntity entity = new LogLevelEntity(); try { entity.setAgency(LogLevelEntity.LogLevel.ERROR); @@ -667,4 +628,33 @@ public void setLogLevel() throws InterruptedException, ExecutionException { arangoDB.setLogLevel(entity).get(); } } + + @Test + public void queueTime() throws InterruptedException, ExecutionException { + List>> reqs = IntStream.range(0, 80) + .mapToObj(__ -> arangoDB.db().query("RETURN SLEEP(1)", Void.class)) + .collect(Collectors.toList()); + for (CompletableFuture> req : reqs) { + req.get(); + } + + QueueTimeMetrics qt = arangoDB.metrics().getQueueTime(); + double avg = qt.getAvg(); + QueueTimeSample[] values = qt.getValues(); + if (isAtLeastVersion(3, 9)) { + assertThat(values.length, is(20)); + for (int i = 0; i < values.length; i++) { + assertThat(values[i], is(notNullValue())); + assertThat(values[i].value, is(greaterThanOrEqualTo(0.0))); + if (i > 0) { + assertThat(values[i].timestamp, greaterThanOrEqualTo(values[i - 1].timestamp)); + } + } + assertThat(avg, is(greaterThan(0.0))); + } else { + assertThat(avg, is(0.0)); + assertThat(values, is(emptyArray())); + } + } + } diff --git a/src/test/java/com/arangodb/internal/QueueTimeMetricsImplTest.java b/src/test/java/com/arangodb/internal/QueueTimeMetricsImplTest.java new file mode 100644 index 000000000..f816eae5f --- /dev/null +++ b/src/test/java/com/arangodb/internal/QueueTimeMetricsImplTest.java @@ -0,0 +1,59 @@ +package com.arangodb.internal; + +import com.arangodb.model.QueueTimeSample; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Random; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class QueueTimeMetricsImplTest { + private final static int QSIZE = 1024; + private final Random rnd = new Random(); + private final QueueTimeMetricsImpl q = new QueueTimeMetricsImpl(QSIZE); + + @Test + public void halfSizeTest() { + testQueue(QSIZE / 2); + } + + @Test + public void fullSizeTest() { + testQueue(QSIZE); + } + + @Test + public void emptySizeTest() { + testQueue(0); + } + + @Test + public void overSizeTest() { + testQueue((int) (QSIZE * 1.2)); + testQueue((int) (QSIZE * 3000.4)); + } + + private void testQueue(int size) { + q.clear(); + for (int i = 0; i < size; i++) { + q.add(new QueueTimeSample(i, rnd.nextDouble())); + } + QueueTimeSample[] values = q.getValues(); + assertThat(values.length, is(Math.min(size, QSIZE))); + assertThat(q.getAvg(), is(closeTo(getAvg(values), 1.0E-12))); + + for (int i = 0; i < values.length; i++) { + assertThat(values[i], is(notNullValue())); + if (i > 0) { + assertThat(values[i].timestamp, greaterThan(values[i - 1].timestamp)); + } + } + } + + private double getAvg(QueueTimeSample[] elements) { + return Arrays.stream(elements).mapToDouble(it -> it.value).average().orElse(0.0); + } + +} \ No newline at end of file diff --git a/src/test/resources/arangodb.properties b/src/test/resources/arangodb.properties index c526e7e7a..f9937e51d 100644 --- a/src/test/resources/arangodb.properties +++ b/src/test/resources/arangodb.properties @@ -3,3 +3,4 @@ arangodb.connections.max=20 arangodb.acquireHostList=true arangodb.password=test arangodb.timeout=30000 +arangodb.metrics.responseQueueTimeSamples=20