com.netflix.hystrix.contrib.rxnetty.metricsstream.HystrixMetricsStreamHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hystrix-rx-netty-metrics-stream Show documentation
Show all versions of hystrix-rx-netty-metrics-stream Show documentation
hystrix-rx-netty-metrics-stream
/**
* 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.contrib.rxnetty.metricsstream;
import com.netflix.hystrix.HystrixCollapserMetrics;
import com.netflix.hystrix.HystrixCommandMetrics;
import com.netflix.hystrix.HystrixThreadPoolMetrics;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.reactivex.netty.protocol.http.server.HttpServerRequest;
import io.reactivex.netty.protocol.http.server.HttpServerResponse;
import io.reactivex.netty.protocol.http.server.RequestHandler;
import rx.Observable;
import rx.Subscription;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
import rx.subjects.PublishSubject;
import rx.subjects.Subject;
import rx.subscriptions.MultipleAssignmentSubscription;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import static com.netflix.hystrix.contrib.rxnetty.metricsstream.JsonMappers.*;
/**
* Streams Hystrix metrics in Server Sent Event (SSE) format. RxNetty application handlers shall
* be wrapped by this handler. It transparently intercepts HTTP requests at a configurable path
* (default "/hystrix.stream"), and sends unbounded SSE streams back to the client. All other requests
* are transparently forwarded to the application handlers.
*
* For RxNetty client tapping into SSE stream: remember to use unpooled HTTP connections. If not, the pooled HTTP
* connection will not be closed on unsubscribe event and the event stream will continue to flow towards the client
* (unless the client is shutdown).
*
* @author Tomasz Bak
* @author Christian Schmitt
*/
public class HystrixMetricsStreamHandler implements RequestHandler {
public static final String DEFAULT_HYSTRIX_PREFIX = "/hystrix.stream";
public static final int DEFAULT_INTERVAL = 2000;
private static final byte[] HEADER = "data: ".getBytes(Charset.defaultCharset());
private static final byte[] FOOTER = {10, 10};
private static final int EXTRA_SPACE = HEADER.length + FOOTER.length;
private final String hystrixPrefix;
private final long interval;
private final RequestHandler appHandler;
public HystrixMetricsStreamHandler(RequestHandler appHandler) {
this(DEFAULT_HYSTRIX_PREFIX, DEFAULT_INTERVAL, appHandler);
}
HystrixMetricsStreamHandler(String hystrixPrefix, long interval, RequestHandler appHandler) {
this.hystrixPrefix = hystrixPrefix;
this.interval = interval;
this.appHandler = appHandler;
}
@Override
public Observable handle(HttpServerRequest request, HttpServerResponse response) {
if (request.getPath().startsWith(hystrixPrefix)) {
return handleHystrixRequest(response);
}
return appHandler.handle(request, response);
}
private Observable handleHystrixRequest(final HttpServerResponse response) {
writeHeaders(response);
final Subject subject = PublishSubject.create();
final MultipleAssignmentSubscription subscription = new MultipleAssignmentSubscription();
Subscription actionSubscription = Observable.timer(0, interval, TimeUnit.MILLISECONDS, Schedulers.computation())
.subscribe(new Action1() {
@Override
public void call(Long tick) {
if (!response.getChannel().isOpen()) {
subscription.unsubscribe();
return;
}
try {
for (HystrixCommandMetrics commandMetrics : HystrixCommandMetrics.getInstances()) {
writeMetric(toJson(commandMetrics), response);
}
for (HystrixThreadPoolMetrics threadPoolMetrics : HystrixThreadPoolMetrics.getInstances()) {
writeMetric(toJson(threadPoolMetrics), response);
}
for (HystrixCollapserMetrics collapserMetrics : HystrixCollapserMetrics.getInstances()) {
writeMetric(toJson(collapserMetrics), response);
}
} catch (Exception e) {
subject.onError(e);
}
}
});
subscription.set(actionSubscription);
return subject;
}
private void writeHeaders(HttpServerResponse response) {
response.getHeaders().add("Content-Type", "text/event-stream;charset=UTF-8");
response.getHeaders().add("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
response.getHeaders().add("Pragma", "no-cache");
}
@SuppressWarnings("unchecked")
private void writeMetric(String json, HttpServerResponse response) {
byte[] bytes = json.getBytes(Charset.defaultCharset());
ByteBuf byteBuf = UnpooledByteBufAllocator.DEFAULT.buffer(bytes.length + EXTRA_SPACE);
byteBuf.writeBytes(HEADER);
byteBuf.writeBytes(bytes);
byteBuf.writeBytes(FOOTER);
response.writeAndFlush((O) byteBuf);
}
}