queueMessageSpeed = new HashMap<>();
private final RedisProvider redisProvider;
@@ -299,6 +302,30 @@ private long getQueueSlowDownTime(String queueName) {
return queueSlowDownTime.getOrDefault(queueName, 0L);
}
+ /**
+ * Sets the {@link DequeueStatistic} for the given queue. Note that this is done in memory
+ * but as well persisted in redis.
+ *
+ * @param queueName The name of the queue for which the stats must be set.
+ * @param dequeueStatistic The {@link DequeueStatistic}
+ */
+ public void setDequeueStatistic(String queueName, DequeueStatistic dequeueStatistic) {
+ if (!dequeueStatistic.isEmpty()) {
+ dequeueStatistics.put(queueName, dequeueStatistic);
+ updateStatisticsInRedis(queueName);
+ }
+ }
+
+ /**
+ * Retrieves the current {@link DequeueStatistic} of the given queue we have in memory for this redisques instance.
+ *
+ * @param queueName The queue name for which we want to retrieve the current failure count
+ * @return The last {@link DequeueStatistic}
+ */
+ private DequeueStatistic getDequeueStatistic(String queueName) {
+ return dequeueStatistics.getOrDefault(queueName, new DequeueStatistic());
+ }
+
/**
* Write all the collected failure statistics for the given Queue to
* redis for later usage if somebody requests the queue statistics.
@@ -309,20 +336,29 @@ private void updateStatisticsInRedis(String queueName) {
long failures = getQueueFailureCount(queueName);
long slowDownTime = getQueueSlowDownTime(queueName);
long backpressureTime = getQueueBackPressureTime(queueName);
- if (failures > 0 || slowDownTime > 0 || backpressureTime > 0) {
+ DequeueStatistic dequeueStatistic = getDequeueStatistic(queueName);
+ if (failures > 0 || slowDownTime > 0 || backpressureTime > 0 || !dequeueStatistic.isEmpty()) {
JsonObject obj = new JsonObject();
obj.put(QUEUENAME, queueName);
obj.put(QUEUE_FAILURES, failures);
obj.put(QUEUE_SLOWDOWNTIME, slowDownTime);
obj.put(QUEUE_BACKPRESSURE, backpressureTime);
- redisProvider.redis().onSuccess(redisAPI -> redisAPI.hset(List.of(STATSKEY, queueName, obj.toString()),
- emptyHandler -> {
- })).onFailure(throwable -> log.error("Redis: Error in updateStatisticsInRedis", throwable));
+ obj.put(QUEUE_DEQUEUE_STATISTIC, JsonObject.mapFrom(dequeueStatistic));
+ redisProvider.redis()
+ .onSuccess(redisAPI -> {
+ redisAPI.hset(List.of(STATSKEY, queueName, obj.toString()), ev -> {
+ if( ev.failed() ) log.warn("TODO error handling", new Exception(ev.cause()));
+ });
+ })
+ .onFailure(ex -> log.error("Redis: Error in updateStatisticsInRedis", ex));
} else {
- redisProvider.redis().onSuccess(redisAPI -> redisAPI.hdel(List.of(STATSKEY, queueName),
- emptyHandler -> {
- })).onFailure(throwable -> log.error("Redis: Error in updateStatisticsInRedis", throwable));
-
+ redisProvider.redis()
+ .onSuccess(redisAPI -> {
+ redisAPI.hdel(List.of(STATSKEY, queueName), ev -> {
+ if (ev.failed()) log.warn("TODO error handling", new Exception(ev.cause()));
+ });
+ })
+ .onFailure(ex -> log.error("Redis: Error in updateStatisticsInRedis", ex));
}
}
@@ -337,6 +373,7 @@ private static class QueueStatistic {
private long backpressureTime;
private long slowdownTime;
private long speed;
+ private JsonObject dequeueStatistic;
QueueStatistic(String queueName) {
this.queueName = queueName;
@@ -382,6 +419,14 @@ void setMessageSpeed(Long speed) {
this.speed = 0;
}
+ void setDequeueStatistic(DequeueStatistic dequeueStatistic) {
+ if (dequeueStatistic != null && !dequeueStatistic.isEmpty()) {
+ this.dequeueStatistic = JsonObject.mapFrom(dequeueStatistic);
+ return;
+ }
+ this.dequeueStatistic = JsonObject.mapFrom(new DequeueStatistic());
+ }
+
JsonObject getAsJsonObject() {
return new JsonObject()
.put(MONITOR_QUEUE_NAME, queueName)
@@ -389,7 +434,8 @@ JsonObject getAsJsonObject() {
.put(STATISTIC_QUEUE_FAILURES, failures)
.put(STATISTIC_QUEUE_BACKPRESSURE, backpressureTime)
.put(STATISTIC_QUEUE_SLOWDOWN, slowdownTime)
- .put(STATISTIC_QUEUE_SPEED, speed);
+ .put(STATISTIC_QUEUE_SPEED, speed)
+ .put(QUEUE_DEQUEUE_STATISTIC, dequeueStatistic);
}
}
@@ -400,84 +446,157 @@ JsonObject getAsJsonObject() {
* for all queues requested (independent of the redisques instance for which the queues are
* registered). Therefore this method must be used with care and not be called too often!
*
- * @param event The event on which we will answer finally
* @param queues The queues for which we are interested in the statistics
+ *
+ * @return A Future
*/
- public void getQueueStatistics(Message event, final List queues) {
+
+ public Future getQueueStatistics(final List queues) {
+ final Promise promise = Promise.promise();
if (queues == null || queues.isEmpty()) {
log.debug("Queue statistics evaluation with empty queues, returning empty result");
- event.reply(new JsonObject().put(STATUS, OK).put(RedisquesAPI.QUEUES, new JsonArray()));
- return;
+ promise.complete(new JsonObject().put(STATUS, OK).put(RedisquesAPI.QUEUES, new JsonArray()));
+ return promise.future();
}
- redisProvider.connection().onSuccess(conn -> {
- List responses = queues.stream().map(queue -> conn.send(Request.cmd(Command.LLEN, queuePrefix + queue))
- ).collect(Collectors.toList());
- CompositeFuture.all(responses).onFailure(throwable -> {
- log.error("Unexepected queue length result");
- event.reply(new JsonObject().put(STATUS, ERROR));
- }).onSuccess(compositeFuture -> {
- List queueLengthList = compositeFuture.list();
- if (queueLengthList == null) {
- log.error("Unexepected queue length result null");
- event.reply(new JsonObject().put(STATUS, ERROR));
- return;
- }
- if (queueLengthList.size() != queues.size()) {
- log.error("Unexpected queue length result with unequal size {} : {}",
- queues.size(), queueLengthList.size());
- event.reply(new JsonObject().put(STATUS, ERROR));
- return;
- }
- // populate the list of queue statistics in a Hashmap for later fast merging
- final HashMap statisticsMap = new HashMap<>();
- for (int i = 0; i < queues.size(); i++) {
- QueueStatistic qs = new QueueStatistic(queues.get(i));
- qs.setSize(queueLengthList.get(i).toLong());
- qs.setMessageSpeed(getQueueSpeed(qs.queueName));
- statisticsMap.put(qs.queueName, qs);
- }
- // now retrieve all available failure statistics from Redis and merge them
- // together with the previous populated common queue statistics map
- redisProvider.redis().onSuccess(redisAPI -> redisAPI.hvals(STATSKEY, statisticsSet -> {
- if (statisticsSet == null) {
- log.error("Unexpected statistics queue evaluation result result null");
- event.reply(new JsonObject().put(STATUS, ERROR));
+ var ctx = new RequestCtx();
+ ctx.queueNames = queues;
+ step1(ctx).compose(
+ jsonObject1 -> step2(ctx).compose(
+ jsonObject2 -> step3(ctx).compose(
+ jsonObject3 -> step4(ctx).compose(
+ jsonObject4 -> step5(ctx).compose(
+ jsonObject5 -> step6(ctx))
+ )))).onComplete(promise);
+ return promise.future();
+ }
+
+ /** init redis connection.
*/
+ Future step1(RequestCtx ctx) {
+ final Promise promise = Promise.promise();
+ redisProvider.connection()
+ .onFailure(throwable -> {
+ promise.fail(new Exception("Redis: Failed to get queue length.", throwable));
+ })
+ .onSuccess(conn -> {
+ assert conn != null;
+ ctx.conn = conn;
+ promise.complete();
+ });
+ return promise.future();
+ }
+
+ /** Query queue lengths.
*/
+ Future step2(RequestCtx ctx) {
+ assert ctx.conn != null;
+ final Promise promise = Promise.promise();
+ List responses = ctx.queueNames.stream()
+ .map(queue -> ctx.conn.send(Request.cmd(Command.LLEN, queuePrefix + queue)))
+ .collect(Collectors.toList());
+ CompositeFuture.all(responses)
+ .onFailure(ex -> {
+ promise.fail("Unexpected queue length result");
+ })
+ .onSuccess(compositeFuture -> {
+ List queueLengthList = compositeFuture.list();
+ if (queueLengthList == null) {
+ promise.fail("Unexpected queue length result: null");
return;
}
- // put the received statistics data to the former prepared statistics objects
- // per queue
- for (Response response : statisticsSet.result()) {
- JsonObject jObj = new JsonObject(response.toString());
- String queueName = jObj.getString(RedisquesAPI.QUEUENAME);
- QueueStatistic queueStatistic = statisticsMap.get(queueName);
- if (queueStatistic != null) {
- // if it isn't there, there is obviously no statistic needed
- queueStatistic.setFailures(jObj.getLong(QUEUE_FAILURES, 0L));
- queueStatistic.setBackpressureTime(jObj.getLong(QUEUE_BACKPRESSURE, 0L));
- queueStatistic.setSlowdownTime(jObj.getLong(QUEUE_SLOWDOWNTIME, 0L));
- }
- }
- // build the final resulting statistics list from the former merged queue
- // values from various sources
- JsonArray result = new JsonArray();
- for (String queueName : queues) {
- QueueStatistic stats = statisticsMap.get(queueName);
- if (stats != null) {
- result.add(stats.getAsJsonObject());
- }
+ if (queueLengthList.size() != ctx.queueNames.size()) {
+ String err = "Unexpected queue length result with unequal size " +
+ ctx.queueNames.size() + " : " + queueLengthList.size();
+ promise.fail(err);
+ return;
}
- event.reply(new JsonObject().put(RedisquesAPI.STATUS, RedisquesAPI.OK)
- .put(RedisquesAPI.QUEUES, result));
- })).onFailure(throwable -> {
- log.error("Redis: Error in getQueueStatistics", throwable);
- event.reply(new JsonObject().put(STATUS, ERROR));
+ ctx.queueLengthList = queueLengthList;
+ promise.complete();
});
+ return promise.future();
+ }
+
+ /** init queue statistics.
*/
+ Future step3(RequestCtx ctx) {
+ assert ctx.queueLengthList != null;
+ final Promise promise = Promise.promise();
+ // populate the list of queue statistics in a Hashmap for later fast merging
+ ctx.statistics = new HashMap<>(ctx.queueNames.size());
+ for (int i = 0; i < ctx.queueNames.size(); i++) {
+ QueueStatistic qs = new QueueStatistic(ctx.queueNames.get(i));
+ qs.setSize(ctx.queueLengthList.get(i).toLong());
+ qs.setMessageSpeed(getQueueSpeed(qs.queueName));
+ ctx.statistics.put(qs.queueName, qs);
+ }
+ promise.complete();
+ return promise.future();
+ }
+
+ /** init a resAPI instance we need to get more details.
*/
+ Future step4(RequestCtx ctx){
+ final Promise promise = Promise.promise();
+ redisProvider.redis()
+ .onFailure(throwable -> {
+ promise.fail(new Exception("Redis: Error in getQueueStatistics", throwable));
+ })
+ .onSuccess(redisAPI -> {
+ assert redisAPI != null;
+ ctx.redisAPI = redisAPI;
+ promise.complete();
+ });
+ return promise.future();
+ }
- });
- }).onFailure(throwable -> {
- log.warn("Redis: Failed to get queue length.", throwable);
- event.reply(new JsonObject().put(STATUS, ERROR));
+ /**
+ * retrieve all available failure statistics from Redis and merge them
+ * together with the previous populated common queue statistics map
+ */
+ Future step5(RequestCtx ctx) {
+ assert ctx.redisAPI != null;
+ assert ctx.statistics != null;
+ final Promise promise = Promise.promise();
+ ctx.redisAPI.hvals(STATSKEY, statisticsSet -> {
+ if( statisticsSet == null || statisticsSet.failed() ){
+ promise.fail(new RuntimeException("statistics queue evaluation failed",
+ statisticsSet == null ? null : statisticsSet.cause()));
+ return;
+ }
+ ctx.redisFailStats = statisticsSet.result();
+ assert ctx.redisFailStats != null;
+ promise.complete();
});
+ return promise.future();
+ }
+
+ /** put received statistics data to the former prepared statistics objects per
+ * queue.
*/
+ Future step6(RequestCtx ctx){
+ assert ctx.redisFailStats != null;
+ final Promise promise = Promise.promise();
+ for (Response response : ctx.redisFailStats) {
+ JsonObject jObj = new JsonObject(response.toString());
+ String queueName = jObj.getString(QUEUENAME);
+ QueueStatistic queueStatistic = ctx.statistics.get(queueName);
+ if (queueStatistic != null) {
+ // if it isn't there, there is obviously no statistic needed
+ queueStatistic.setFailures(jObj.getLong(QUEUE_FAILURES, 0L));
+ queueStatistic.setBackpressureTime(jObj.getLong(QUEUE_BACKPRESSURE, 0L));
+ queueStatistic.setSlowdownTime(jObj.getLong(QUEUE_SLOWDOWNTIME, 0L));
+ if (jObj.containsKey(QUEUE_DEQUEUE_STATISTIC)) {
+ queueStatistic.setDequeueStatistic(jObj.getJsonObject(QUEUE_DEQUEUE_STATISTIC).mapTo(DequeueStatistic.class));
+ }
+ }
+ }
+ // build the final resulting statistics list from the former merged queue
+ // values from various sources
+ JsonArray result = new JsonArray();
+ for (String queueName : ctx.queueNames) {
+ QueueStatistic stats = ctx.statistics.get(queueName);
+ if (stats != null) {
+ result.add(stats.getAsJsonObject());
+ }
+ }
+ promise.complete(new JsonObject().put(STATUS, OK)
+ .put(RedisquesAPI.QUEUES, result));
+ return promise.future();
}
/**
@@ -503,4 +622,16 @@ public void getQueuesSpeed(Message event, final List