All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.datastax.oss.driver.internal.core.metrics.HdrReservoir Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.datastax.oss.driver.internal.core.metrics;

import com.codahale.metrics.Reservoir;
import com.codahale.metrics.Snapshot;
import java.io.OutputStream;
import java.time.Duration;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
import org.HdrHistogram.Histogram;
import org.HdrHistogram.Recorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A reservoir implementation backed by the HdrHistogram library.
 *
 * 

It uses a {@link Recorder} to capture snapshots at a configurable interval: calls to {@link * #update(long)} are recorded in a "live" histogram, while {@link #getSnapshot()} is based on a * "cached", read-only histogram. Each time the cached histogram becomes older than the interval, * the two histograms are switched (therefore statistics won't be available during the first * interval after initialization, since we don't have a cached histogram yet). * *

Note that this class does not implement {@link #size()}. * * @see HdrHistogram */ @ThreadSafe public class HdrReservoir implements Reservoir { private static final Logger LOG = LoggerFactory.getLogger(HdrReservoir.class); private final String logPrefix; private final Recorder recorder; private final long refreshIntervalNanos; // The lock only orchestrates `getSnapshot()` calls; `update()` is fed directly to the recorder, // which is lock-free. `getSnapshot()` calls are comparatively rare, so locking is not a // bottleneck. private final ReadWriteLock cacheLock = new ReentrantReadWriteLock(); @GuardedBy("cacheLock") private Histogram cachedHistogram; @GuardedBy("cacheLock") private long cachedHistogramTimestampNanos; @GuardedBy("cacheLock") private Snapshot cachedSnapshot; public HdrReservoir( Duration highestTrackableLatency, int numberOfSignificantValueDigits, Duration refreshInterval, String logPrefix) { this.logPrefix = logPrefix; // The Reservoir interface is supposed to be agnostic to the unit. However, the Metrics library // heavily leans towards nanoseconds (for example, Timer feeds nanoseconds to update(); JmxTimer // assumes that the snapshot results are in nanoseconds). // In our case, microseconds are precise enough for request metrics, and we don't want to waste // space unnecessarily. So we simply use microseconds for our internal storage, and do the // conversion when needed. this.recorder = new Recorder(highestTrackableLatency.toNanos() / 1000, numberOfSignificantValueDigits); this.refreshIntervalNanos = refreshInterval.toNanos(); this.cachedHistogramTimestampNanos = System.nanoTime(); this.cachedSnapshot = EMPTY_SNAPSHOT; } @Override public void update(long value) { try { recorder.recordValue(value / 1000); } catch (ArrayIndexOutOfBoundsException e) { LOG.warn("[{}] Recorded value ({}) is out of bounds, discarding", logPrefix, value); } } /** * Not implemented: this reservoir implementation is intended for use with a {@link * com.codahale.metrics.Histogram}, which doesn't use this method. * *

(original description: {@inheritDoc}) */ @Override public int size() { throw new UnsupportedOperationException("HdrReservoir does not implement size()"); } /** * {@inheritDoc} * *

Note that the snapshots returned from this method do not implement {@link * Snapshot#getValues()} nor {@link Snapshot#dump(OutputStream)}. In addition, due to the way that * internal data structures are recycled, you should not hold onto a snapshot for more than the * refresh interval; one way to ensure this is to never cache the result of this method. */ @Override public Snapshot getSnapshot() { long now = System.nanoTime(); cacheLock.readLock().lock(); try { if (now - cachedHistogramTimestampNanos < refreshIntervalNanos) { return cachedSnapshot; } } finally { cacheLock.readLock().unlock(); } cacheLock.writeLock().lock(); try { // Might have raced with another writer => re-check the timestamp if (now - cachedHistogramTimestampNanos >= refreshIntervalNanos) { LOG.debug("Cached snapshot is too old, refreshing"); cachedHistogram = recorder.getIntervalHistogram(cachedHistogram); cachedSnapshot = new HdrSnapshot(cachedHistogram); cachedHistogramTimestampNanos = now; } return cachedSnapshot; } finally { cacheLock.writeLock().unlock(); } } private class HdrSnapshot extends Snapshot { private final Histogram histogram; private final double meanNanos; private final double stdDevNanos; private HdrSnapshot(Histogram histogram) { this.histogram = histogram; // Cache those values because they rely on HdrHistogram's internal iterators, which are not // safe if the snapshot is accessed by concurrent reporters. // In contrast, getMin(), getMax() and getValue() are safe. this.meanNanos = histogram.getMean() * 1000; this.stdDevNanos = histogram.getStdDeviation() * 1000; } @Override public double getValue(double quantile) { return histogram.getValueAtPercentile(quantile * 100) * 1000; } /** * Not implemented: this reservoir implementation is intended for use with a {@link * com.codahale.metrics.Histogram}, which doesn't use this method. * *

(original description: {@inheritDoc}) */ @Override public long[] getValues() { // This can be implemented, but we ran into issues when accessed by concurrent reporters // because HdrHistogram uses an unsafe shared iterator. // So throwing instead since this method should be seldom used anyway. throw new UnsupportedOperationException( "HdrReservoir's snapshots do not implement getValues()"); } @Override public int size() { long longSize = histogram.getTotalCount(); // The Metrics API requires an int. It's very unlikely that we get an overflow here, unless // the refresh interval is ridiculously high (at 10k requests/s, it would have to be more than // 59 hours). However handle gracefully just in case. int size; if (longSize > Integer.MAX_VALUE) { LOG.warn("[{}] Too many recorded values, truncating", logPrefix); size = Integer.MAX_VALUE; } else { size = (int) longSize; } return size; } @Override public long getMax() { return histogram.getMaxValue() * 1000; } @Override public double getMean() { return meanNanos; } @Override public long getMin() { return histogram.getMinValue() * 1000; } @Override public double getStdDev() { return stdDevNanos; } /** * Not implemented: this reservoir implementation is intended for use with a {@link * com.codahale.metrics.Histogram}, which doesn't use this method. * *

(original description: {@inheritDoc}) */ @Override public void dump(OutputStream output) { throw new UnsupportedOperationException("HdrReservoir's snapshots do not implement dump()"); } } private static final Snapshot EMPTY_SNAPSHOT = new Snapshot() { @Override public double getValue(double quantile) { return 0; } @Override public long[] getValues() { return new long[0]; } @Override public int size() { return 0; } @Override public long getMax() { return 0; } @Override public double getMean() { return 0; } @Override public long getMin() { return 0; } @Override public double getStdDev() { return 0; } @Override public void dump(OutputStream output) { // nothing to do } }; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy