com.netflix.hystrix.metric.consumer.RollingDistributionStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hystrix-core Show documentation
Show all versions of hystrix-core Show documentation
hystrix-core developed by Netflix
/**
* Copyright 2015 Netflix, Inc.
*
* 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.netflix.hystrix.metric.consumer;
import com.netflix.hystrix.metric.CachedValuesHistogram;
import com.netflix.hystrix.metric.HystrixEvent;
import com.netflix.hystrix.metric.HystrixEventStream;
import org.HdrHistogram.Histogram;
import rx.Observable;
import rx.Subscription;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.subjects.BehaviorSubject;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* Maintains a stream of distributions for a given Command.
* There is a rolling window abstraction on this stream.
* The latency distribution object is calculated over a window of t1 milliseconds. This window has b buckets.
* Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds
* t1 = metricsRollingPercentileWindowInMilliseconds()
* b = metricsRollingPercentileBucketSize()
*
* These values are stable - there's no peeking into a bucket until it is emitted
*
* These values get produced and cached in this class.
*/
public class RollingDistributionStream {
private AtomicReference rollingDistributionSubscription = new AtomicReference(null);
private final BehaviorSubject rollingDistribution = BehaviorSubject.create(CachedValuesHistogram.backedBy(CachedValuesHistogram.getNewHistogram()));
private final Observable rollingDistributionStream;
private static final Func2 distributionAggregator = new Func2() {
@Override
public Histogram call(Histogram initialDistribution, Histogram distributionToAdd) {
initialDistribution.add(distributionToAdd);
return initialDistribution;
}
};
private static final Func1, Observable> reduceWindowToSingleDistribution = new Func1, Observable>() {
@Override
public Observable call(Observable window) {
return window.reduce(distributionAggregator);
}
};
private static final Func1 cacheHistogramValues = new Func1() {
@Override
public CachedValuesHistogram call(Histogram histogram) {
return CachedValuesHistogram.backedBy(histogram);
}
};
private static final Func1, Observable>> convertToList =
new Func1, Observable>>() {
@Override
public Observable> call(Observable windowOf2) {
return windowOf2.toList();
}
};
protected RollingDistributionStream(final HystrixEventStream stream, final int numBuckets, final int bucketSizeInMs,
final Func2 addValuesToBucket) {
final List emptyDistributionsToStart = new ArrayList();
for (int i = 0; i < numBuckets; i++) {
emptyDistributionsToStart.add(CachedValuesHistogram.getNewHistogram());
}
final Func1, Observable> reduceBucketToSingleDistribution = new Func1, Observable>() {
@Override
public Observable call(Observable bucket) {
return bucket.reduce(CachedValuesHistogram.getNewHistogram(), addValuesToBucket);
}
};
rollingDistributionStream = stream
.observe()
.window(bucketSizeInMs, TimeUnit.MILLISECONDS) //stream of unaggregated buckets
.flatMap(reduceBucketToSingleDistribution) //stream of aggregated Histograms
.startWith(emptyDistributionsToStart) //stream of aggregated Histograms that starts with n empty
.window(numBuckets, 1) //windowed stream: each OnNext is a stream of n Histograms
.flatMap(reduceWindowToSingleDistribution) //reduced stream: each OnNext is a single Histogram
.map(cacheHistogramValues) //convert to CachedValueHistogram (commonly-accessed values are cached)
.share()
.onBackpressureDrop();
}
public Observable observe() {
return rollingDistributionStream;
}
public int getLatestMean() {
CachedValuesHistogram latest = getLatest();
if (latest != null) {
return latest.getMean();
} else {
return 0;
}
}
public int getLatestPercentile(double percentile) {
CachedValuesHistogram latest = getLatest();
if (latest != null) {
return latest.getValueAtPercentile(percentile);
} else {
return 0;
}
}
public void startCachingStreamValuesIfUnstarted() {
if (rollingDistributionSubscription.get() == null) {
//the stream is not yet started
Subscription candidateSubscription = observe().subscribe(rollingDistribution);
if (rollingDistributionSubscription.compareAndSet(null, candidateSubscription)) {
//won the race to set the subscription
} else {
//lost the race to set the subscription, so we need to cancel this one
candidateSubscription.unsubscribe();
}
}
}
CachedValuesHistogram getLatest() {
startCachingStreamValuesIfUnstarted();
if (rollingDistribution.hasValue()) {
return rollingDistribution.getValue();
} else {
return null;
}
}
public void unsubscribe() {
Subscription s = rollingDistributionSubscription.get();
if (s != null) {
s.unsubscribe();
rollingDistributionSubscription.compareAndSet(s, null);
}
}
}