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