protected RollingDistributionStream( final HystrixEventStream<Event> stream, final int numBuckets, final int bucketSizeInMs, final Func2<Histogram, Event, Histogram> addValuesToBucket) { final List<Histogram> emptyDistributionsToStart = new ArrayList<Histogram>(); for (int i = 0; i < numBuckets; i++) { emptyDistributionsToStart.add(CachedValuesHistogram.getNewHistogram()); } final Func1<Observable<Event>, Observable<Histogram>> reduceBucketToSingleDistribution = new Func1<Observable<Event>, Observable<Histogram>>() { @Override public Observable<Histogram> call(Observable<Event> bucket) { return bucket.reduce(CachedValuesHistogram.getNewHistogram(), addValuesToBucket); } }; rollingDistributionStream = Observable.defer( new Func0<Observable<CachedValuesHistogram>>() { @Override public Observable<CachedValuesHistogram> call() { return 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(); // multicast }
/** * 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() * * <p>These values are stable - there's no peeking into a bucket until it is emitted * * <p>These values get produced and cached in this class. */ public class RollingDistributionStream<Event extends HystrixEvent> { private AtomicReference<Subscription> rollingDistributionSubscription = new AtomicReference<Subscription>(null); private final BehaviorSubject<CachedValuesHistogram> rollingDistribution = BehaviorSubject.create( CachedValuesHistogram.backedBy(CachedValuesHistogram.getNewHistogram())); private final Observable<CachedValuesHistogram> rollingDistributionStream; private static final Func2<Histogram, Histogram, Histogram> distributionAggregator = new Func2<Histogram, Histogram, Histogram>() { @Override public Histogram call(Histogram initialDistribution, Histogram distributionToAdd) { initialDistribution.add(distributionToAdd); return initialDistribution; } }; private static final Func1<Observable<Histogram>, Observable<Histogram>> reduceWindowToSingleDistribution = new Func1<Observable<Histogram>, Observable<Histogram>>() { @Override public Observable<Histogram> call(Observable<Histogram> window) { return window.reduce(distributionAggregator); } }; private static final Func1<Histogram, CachedValuesHistogram> cacheHistogramValues = new Func1<Histogram, CachedValuesHistogram>() { @Override public CachedValuesHistogram call(Histogram histogram) { return CachedValuesHistogram.backedBy(histogram); } }; private static final Func1< Observable<CachedValuesHistogram>, Observable<List<CachedValuesHistogram>>> convertToList = new Func1<Observable<CachedValuesHistogram>, Observable<List<CachedValuesHistogram>>>() { @Override public Observable<List<CachedValuesHistogram>> call( Observable<CachedValuesHistogram> windowOf2) { return windowOf2.toList(); } }; protected RollingDistributionStream( final HystrixEventStream<Event> stream, final int numBuckets, final int bucketSizeInMs, final Func2<Histogram, Event, Histogram> addValuesToBucket) { final List<Histogram> emptyDistributionsToStart = new ArrayList<Histogram>(); for (int i = 0; i < numBuckets; i++) { emptyDistributionsToStart.add(CachedValuesHistogram.getNewHistogram()); } final Func1<Observable<Event>, Observable<Histogram>> reduceBucketToSingleDistribution = new Func1<Observable<Event>, Observable<Histogram>>() { @Override public Observable<Histogram> call(Observable<Event> bucket) { return bucket.reduce(CachedValuesHistogram.getNewHistogram(), addValuesToBucket); } }; rollingDistributionStream = Observable.defer( new Func0<Observable<CachedValuesHistogram>>() { @Override public Observable<CachedValuesHistogram> call() { return 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(); // multicast } public Observable<CachedValuesHistogram> 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); } } }