
com.lambdaworks.redis.metrics.DefaultCommandLatencyCollector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lettuce Show documentation
Show all versions of lettuce Show documentation
Advanced and thread-safe Java Redis client for synchronous, asynchronous, and
reactive usage. Supports Cluster, Sentinel, Pipelining, Auto-Reconnect, Codecs
and much more.
The newest version!
/*
* Copyright 2011-2016 the original author or authors.
*
* 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.
*/
package com.lambdaworks.redis.metrics;
import com.lambdaworks.redis.metrics.CommandMetrics.CommandLatency;
import com.lambdaworks.redis.protocol.CommandType;
import com.lambdaworks.redis.protocol.ProtocolKeyword;
import io.netty.channel.local.LocalAddress;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.HdrHistogram.Histogram;
import org.LatencyUtils.LatencyStats;
import org.LatencyUtils.PauseDetector;
import org.LatencyUtils.SimplePauseDetector;
import java.net.SocketAddress;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import static com.lambdaworks.redis.internal.LettuceClassUtils.isPresent;
/**
* Default implementation of a {@link CommandLatencyCollector} for command latencies.
*
* @author Mark Paluch
*/
public class DefaultCommandLatencyCollector implements CommandLatencyCollector {
private static final AtomicReference PAUSE_DETECTOR = new AtomicReference<>();
private static final boolean LATENCY_UTILS_AVAILABLE = isPresent("org.LatencyUtils.PauseDetector");
private static final boolean HDR_UTILS_AVAILABLE = isPresent("org.HdrHistogram.Histogram");
private static final long MIN_LATENCY = 1000;
private static final long MAX_LATENCY = TimeUnit.MINUTES.toNanos(5);
private final CommandLatencyCollectorOptions options;
private Map latencyMetrics = new ConcurrentHashMap<>(CommandType.values().length);
public DefaultCommandLatencyCollector(CommandLatencyCollectorOptions options) {
this.options = options;
}
/**
* Record the command latency per {@code connectionPoint} and {@code commandType}.
*
* @param local the local address
* @param remote the remote address
* @param commandType the command type
* @param firstResponseLatency latency value in {@link TimeUnit#NANOSECONDS} from send to the first response
* @param completionLatency latency value in {@link TimeUnit#NANOSECONDS} from send to the command completion
*/
public void recordCommandLatency(SocketAddress local, SocketAddress remote, ProtocolKeyword commandType,
long firstResponseLatency, long completionLatency) {
if (!isEnabled()) {
return;
}
CommandLatencyId id = createId(local, remote, commandType);
Latencies latencies = latencyMetrics.get(id);
if (latencies == null) {
PauseDetectorWrapper wrapper = PAUSE_DETECTOR.get();
if (wrapper == null) {
wrapper = new PauseDetectorWrapper();
if (PAUSE_DETECTOR.compareAndSet(null, wrapper)) {
wrapper.initialize();
}
}
latencies = new Latencies(PAUSE_DETECTOR.get().pauseDetector);
latencyMetrics.put(id, latencies);
}
latencies.firstResponse.recordLatency(rangify(firstResponseLatency));
latencies.completion.recordLatency(rangify(completionLatency));
}
private CommandLatencyId createId(SocketAddress local, SocketAddress remote, ProtocolKeyword commandType) {
return CommandLatencyId.create(options.localDistinction() ? local : LocalAddress.ANY, remote, commandType);
}
private long rangify(long latency) {
return Math.max(MIN_LATENCY, Math.min(MAX_LATENCY, latency));
}
@Override
public boolean isEnabled() {
return latencyMetrics != null && options.isEnabled();
}
@Override
public void shutdown() {
if (latencyMetrics != null) {
latencyMetrics.clear();
latencyMetrics = null;
}
}
@Override
public Map retrieveMetrics() {
Map copy = new HashMap<>();
copy.putAll(latencyMetrics);
if (options.resetLatenciesAfterEvent()) {
latencyMetrics.clear();
}
Map latencies = getMetrics(copy);
return latencies;
}
private Map getMetrics(Map latencyMetrics) {
Map latencies = new TreeMap<>();
for (Map.Entry entry : latencyMetrics.entrySet()) {
Histogram firstResponse = entry.getValue().firstResponse.getIntervalHistogram();
Histogram completion = entry.getValue().completion.getIntervalHistogram();
if (firstResponse.getTotalCount() == 0 && completion.getTotalCount() == 0) {
continue;
}
CommandLatency firstResponseLatency = getMetric(firstResponse);
CommandLatency completionLatency = getMetric(completion);
CommandMetrics metrics = new CommandMetrics(firstResponse.getTotalCount(), options.targetUnit(),
firstResponseLatency, completionLatency);
latencies.put(entry.getKey(), metrics);
}
return latencies;
}
private CommandLatency getMetric(Histogram histogram) {
Map percentiles = getPercentiles(histogram);
TimeUnit timeUnit = options.targetUnit();
CommandLatency metric = new CommandLatency(timeUnit.convert(histogram.getMinValue(), TimeUnit.NANOSECONDS),
timeUnit.convert(histogram.getMaxValue(), TimeUnit.NANOSECONDS), percentiles);
return metric;
}
private Map getPercentiles(Histogram histogram) {
Map percentiles = new TreeMap();
for (double targetPercentile : options.targetPercentiles()) {
percentiles.put(targetPercentile,
options.targetUnit().convert(histogram.getValueAtPercentile(targetPercentile), TimeUnit.NANOSECONDS));
}
return percentiles;
}
/**
* Returns {@literal true} if HdrUtils and LatencyUtils are available on the class path.
*
* @return
*/
public static boolean isAvailable() {
return LATENCY_UTILS_AVAILABLE && HDR_UTILS_AVAILABLE;
}
/**
* Returns a disabled no-op {@link CommandLatencyCollector}.
*
* @return
*/
public static CommandLatencyCollector disabled() {
return new CommandLatencyCollector() {
@Override
public void recordCommandLatency(SocketAddress local, SocketAddress remote, ProtocolKeyword commandType,
long firstResponseLatency, long completionLatency) {
}
@Override
public void shutdown() {
}
@Override
public Map retrieveMetrics() {
return Collections.emptyMap();
}
@Override
public boolean isEnabled() {
return false;
}
};
}
private static class Latencies {
public final LatencyStats firstResponse;
public final LatencyStats completion;
public Latencies(PauseDetector pauseDetector) {
firstResponse = LatencyStats.Builder.create().pauseDetector(pauseDetector).build();
completion = LatencyStats.Builder.create().pauseDetector(pauseDetector).build();
}
}
private static class PauseDetectorWrapper {
public static final AtomicLong counter = new AtomicLong();
PauseDetector pauseDetector;
public void initialize() {
if (counter.getAndIncrement() > 0) {
InternalLogger instance = InternalLoggerFactory.getInstance(getClass());
instance.info("Initialized PauseDetectorWrapper more than once.");
}
pauseDetector = new SimplePauseDetector(TimeUnit.MILLISECONDS.toNanos(10), TimeUnit.MILLISECONDS.toNanos(10), 3);
Runtime.getRuntime().addShutdownHook(new Thread("ShutdownHook for SimplePauseDetector") {
@Override
public void run() {
pauseDetector.shutdown();
}
});
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy