diff --git a/hystrix-contrib/hystrix-servo-stream/build.gradle b/hystrix-contrib/hystrix-servo-stream/build.gradle new file mode 100644 index 000000000..9c4e62078 --- /dev/null +++ b/hystrix-contrib/hystrix-servo-stream/build.gradle @@ -0,0 +1,6 @@ + apply plugin: 'java' + dependencies { + compile project(':hystrix-core') + compile 'javax.servlet:javax.servlet-api:3.0.1' + compile 'org.json:json:20090211' + } diff --git a/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/HystrixEventStreamMetricsObserver.java b/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/HystrixEventStreamMetricsObserver.java new file mode 100644 index 000000000..c508f0bd9 --- /dev/null +++ b/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/HystrixEventStreamMetricsObserver.java @@ -0,0 +1,124 @@ +package com.netflix.hystrix.contrib.servostream; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import javax.servlet.http.HttpServletResponse; + +import org.json.JSONException; +import org.json.JSONObject; + +import com.netflix.servo.Metric; +import com.netflix.servo.publish.MetricObserver; +import com.netflix.servo.tag.Tag; +import com.netflix.servo.tag.TagList; + +/** + * Groups Servo metrics into a MetricGroup and then writes them to HttpServletResponse in text/event-stream format. + */ +public class HystrixEventStreamMetricsObserver implements MetricObserver { + + private final HashMap metricsGroups = new HashMap(); + private final HashMap metricsCache = new HashMap(); + private final String CurrentTime = "currentTime"; + private final HttpServletResponse response; + private volatile boolean isRunning = true; + + HystrixEventStreamMetricsObserver(HttpServletResponse response) { + this.response = response; + } + + public boolean isRunning() { + return isRunning; + } + + @Override + public void update(List metrics) { + try { + List groupedJson = getServoMetricsGroupedAsJson(metrics); + + for (JSONObject json : groupedJson) { + response.getWriter().println("data: " + json.toString() + "\n"); + } + response.flushBuffer(); + } catch (Exception e) { + HystrixServoPoller.logger.error("Failed to write metric group.", e); + // the servlet itself will handle closing the connection using 'isRunning' + isRunning = false; + } + } + + @Override + public String getName() { + return HystrixEventStreamMetricsObserver.class.getSimpleName(); + } + + private List getServoMetricsGroupedAsJson(List metrics) { + List events = new ArrayList(); + + for (Metric metric : metrics) { + + String type = null; + String id = null; + TagList tagList = metric.getConfig().getTags(); + if (tagList != null) { + Tag tag = tagList.getTag("type"); + if (tag != null) { + type = tag.getValue(); + } + tag = tagList.getTag("instance"); + if (tag != null) { + id = tag.getValue(); + } + } + + String cacheKey = type + id; + + MetricGroup group = metricsGroups.get(cacheKey); + if (group == null) { + group = new MetricGroup(type, id); + metricsGroups.put(cacheKey, group); + } + + try { + group.addMetricFields(metric); + } catch (Throwable t) { + HystrixServoPoller.logger.error("Caught failure when adding metric: " + metric + " to group: " + group, t); + } + } + + for (MetricGroup mg : metricsGroups.values()) { + try { + if (mg.isDirty()) { + // ok we have data in the metric group e.g HystrixCommand : CinematchGetRatings + // but we should check with a cache and see if the metric has changed. Do not send data that has not changed + + JSONObject json = mg.getJsonObject(); + + long currentTime = -1; + if (json.has(CurrentTime)) { + currentTime = (Long) json.remove(CurrentTime); + } + String jsonString = json.toString(); + String cacheKey = mg.getCacheKey(); + String prev = metricsCache.get(cacheKey); + + if (prev == null || (prev != null && !prev.equals(jsonString))) { + metricsCache.put(cacheKey, jsonString); + if (currentTime != -1) { + json.put(CurrentTime, currentTime); + } + events.add(json); + } + + mg.clear(); + } + } catch (JSONException e) { + HystrixServoPoller.logger.error("Caught failure when getting json from group: " + mg, e); + } + } + return events; + } + +} \ No newline at end of file diff --git a/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/HystrixMetricsStreamServlet.java b/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/HystrixMetricsStreamServlet.java new file mode 100644 index 000000000..d66f80595 --- /dev/null +++ b/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/HystrixMetricsStreamServlet.java @@ -0,0 +1,103 @@ +package com.netflix.hystrix.contrib.servostream; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.config.DynamicIntProperty; +import com.netflix.config.DynamicPropertyFactory; + +/** + * Streams Hystrix metrics in text/event-stream format. + */ +public class HystrixMetricsStreamServlet extends HttpServlet { + + private static final long serialVersionUID = -7548505095303313237L; + + private static final Logger logger = LoggerFactory.getLogger(HystrixMetricsStreamServlet.class); + + /* used to track number of connections and throttle */ + private static AtomicInteger concurrentConnections = new AtomicInteger(0); + private static DynamicIntProperty maxConcurrentConnections = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.servo.stream.maxConcurrentConnections", 5); + + /** + * Handle incoming GETs + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + handleRequest(request, response); + } + + /** + * - maintain an open connection with the client + * - on initial connection send latest data of each requested event type + * - subsequently send all changes for each requested event type + * + * @param request + * @param response + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + private void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + /* wrap so we synchronize writes since the response object will be shared across multiple threads for async writing */ + response = new SynchronizedHttpServletResponse(response); + + /* ensure we aren't allowing more connections than we want */ + int numberConnections = concurrentConnections.incrementAndGet(); + + int delay = 500; + try { + String d = request.getParameter("delay"); + if (d != null) { + delay = Integer.parseInt(d); + } + } catch (Exception e) { + // ignore if it's not a number + } + + HystrixServoPoller poller = null; + try { + if (numberConnections > maxConcurrentConnections.get()) { + response.sendError(503, "MaxConcurrentConnections reached: " + maxConcurrentConnections.get()); + } else { + + /* initialize response */ + response.setHeader("Content-Type", "text/event-stream"); + response.setHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate"); + response.setHeader("Pragma", "no-cache"); + + poller = new HystrixServoPoller(delay); + // start polling and it will write directly to the output stream + HystrixEventStreamMetricsObserver observer = new HystrixEventStreamMetricsObserver(response); + poller.start(observer); + logger.info("Starting poller"); + + try { + while (true && observer.isRunning()) { + response.getWriter().println(":ping\n"); + response.flushBuffer(); + Thread.sleep(2000); + } + } catch (Exception e) { + // do nothing on interruptions. + logger.error("Failed to write", e); + } + logger.error("Stopping Turbine stream to connection"); + } + } catch (Exception e) { + logger.error("Error initializing servlet for Servo event stream.", e); + } finally { + concurrentConnections.decrementAndGet(); + if (poller != null) { + poller.stop(); + } + } + } +} diff --git a/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/HystrixServoPoller.java b/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/HystrixServoPoller.java new file mode 100644 index 000000000..6ae844211 --- /dev/null +++ b/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/HystrixServoPoller.java @@ -0,0 +1,86 @@ +package com.netflix.hystrix.contrib.servostream; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.servo.DefaultMonitorRegistry; +import com.netflix.servo.monitor.MonitorConfig; +import com.netflix.servo.publish.MetricFilter; +import com.netflix.servo.publish.MonitorRegistryMetricPoller; +import com.netflix.servo.publish.PollRunnable; +import com.netflix.servo.tag.Tag; +import com.netflix.servo.tag.TagList; + +/** + * Polls Servo for Hystrix metrics and sends them to a MetricsObserver. + */ +public class HystrixServoPoller { + + static final Logger logger = LoggerFactory.getLogger(HystrixServoPoller.class); + private final MonitorRegistryMetricPoller monitorPoller; + private final ScheduledExecutorService executor; + private final int delay; + + public HystrixServoPoller(int delay) { + executor = new ScheduledThreadPoolExecutor(1, new TurbineMetricsPollerThreadFactory()); + monitorPoller = new MonitorRegistryMetricPoller(DefaultMonitorRegistry.getInstance(), 1, TimeUnit.MINUTES, false); + this.delay = delay; + } + + public synchronized void start(HystrixEventStreamMetricsObserver observer) { + logger.info("Starting HystrixServoPoller"); + PollRunnable task = new PollRunnable(monitorPoller, new HystrixMetricFilter(), observer); + executor.scheduleWithFixedDelay(task, 0, delay, TimeUnit.MILLISECONDS); + } + + public synchronized void stop() { + logger.info("Stopping the Servo Metrics Poller"); + executor.shutdownNow(); + if (monitorPoller != null) { + monitorPoller.shutdown(); + } + } + + private class TurbineMetricsPollerThreadFactory implements ThreadFactory { + private static final String MetricsThreadName = "ServoMetricPoller"; + + private final ThreadFactory defaultFactory = Executors.defaultThreadFactory(); + + public Thread newThread(Runnable r) { + Thread thread = defaultFactory.newThread(r); + thread.setName(MetricsThreadName); + return thread; + } + } + + private class HystrixMetricFilter implements MetricFilter { + + private HystrixMetricFilter() { + } + + @Override + public boolean matches(MonitorConfig mConfig) { + + TagList tagList = mConfig.getTags(); + if (tagList != null) { + Tag classTag = tagList.getTag("type"); + logger.info("HystrixMetricFilter matches: " + classTag); + if (classTag == null) { + return false; + } + if (classTag.getValue().startsWith("Hystrix")) { + return true; + } + } + + return false; + } + } + +} diff --git a/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/MetricGroup.java b/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/MetricGroup.java new file mode 100644 index 000000000..50dfd53c2 --- /dev/null +++ b/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/MetricGroup.java @@ -0,0 +1,226 @@ +package com.netflix.hystrix.contrib.servostream; + +import static org.junit.Assert.*; + +import java.util.HashMap; +import java.util.Map; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.servo.Metric; +import com.netflix.servo.monitor.MonitorConfig; + +/** + * Used to convert a list of Servo metrics to a list of JSON objects. + *

+ * We use Servo tags to group a set of flat metrics into groups of common metric such as + * for a HystrixCommand or HystrixThreadPool. + */ +class MetricGroup { + + private static final Logger logger = LoggerFactory.getLogger(MetricGroup.class); + + private static final String MetricType = "metric-type"; + private static final String Clazz = "class"; + private static final String Name = "name"; + private static final String True = "true"; + private static final String False = "false"; + + private String name; + private final String clazz; + private String type; + private final Map nAttrs; + private final Map sAttrs; + + // boolean to track metric group state(s) especially since we re-use these objects for performance reasons + private boolean isDirty = false; + + public MetricGroup(String clazzz, String nameString) { + this.clazz = clazzz; + this.name = nameString; + if (name == null) { + name = clazz; + } + this.type = clazz; + this.nAttrs = new HashMap(); + this.sAttrs = new HashMap(); + } + + void addMetricFields(Metric m) { + + Object value = m.getValue(); + + if (value instanceof Number) { + isDirty = true; + nAttrs.put(m.getConfig().getName(), (Number) value); + } else if (value instanceof String) { + isDirty = true; + sAttrs.put(m.getConfig().getName(), (String) value); + } else if (value instanceof Boolean) { + isDirty = true; + sAttrs.put(m.getConfig().getName(), ((Boolean) value).toString()); + } else { + try { + sAttrs.put(m.getConfig().getName(), value.toString()); + isDirty = true; + } catch (Throwable t) { + logger.warn("Could not add metric: " + m.getConfig().toString(), t); + } + } + } + + String getCacheKey() { + return clazz + name; + } + + JSONObject getJsonObject() throws JSONException { + + this.sAttrs.put(Name, name); + this.sAttrs.put(MetricType, type); + this.sAttrs.put(Clazz, clazz); + + JSONObject json = new JSONObject(); + + for (String key : nAttrs.keySet()) { + Number n = nAttrs.get(key); + if (n != null) { + try { + json.put(key, n); + } catch (JSONException e) { + //logger.error("Could not add metric attr: " + key + " " + n, e); + } + } + } + + for (String key : sAttrs.keySet()) { + String value = sAttrs.get(key); + if (value == null) { + continue; + } + if (value.equals(True) || value.equals(False)) { + json.put(key, Boolean.valueOf(value)); + } else { + json.put(key, value); + } + } + + return json; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((clazz == null) ? 0 : clazz.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + MetricGroup other = (MetricGroup) obj; + boolean equals = (this.clazz != null) ? this.clazz.equals(other.clazz) : other.clazz == null; + equals &= (this.name != null) ? this.name.equals(other.name) : other.name == null; + return equals; + } + + @Override + public String toString() { + return "MetricGroup [name=" + name + ", clazz=" + clazz + ", type=" + + type + "]"; + } + + public void clear() { + nAttrs.clear(); + sAttrs.clear(); + isDirty = false; + } + + public boolean isDirty() { + return isDirty; + } + + public static class UnitTest { + + @Test + public void testEquals() throws Exception { + + MetricGroup group1 = new MetricGroup("foo", "bar"); + MetricGroup group2 = new MetricGroup("foo", "bar"); + + assertEquals(group1, group2); + + MetricGroup group3 = new MetricGroup("foo", null); + + assertFalse(group2.equals(group3)); + + MetricGroup group4 = new MetricGroup("bar", null); + + assertFalse(group2.equals(group4)); + assertFalse(group3.equals(group4)); + + MetricGroup group5 = new MetricGroup("bar", null); + assertFalse(group2.equals(group5)); + assertFalse(group3.equals(group5)); + assertTrue(group4.equals(group5)); + } + + @Test + public void testAddMeticsThenClearAndThenReAddMetrics() throws Exception { + + MetricGroup group = new MetricGroup("foo", "bar"); + + group.addMetricFields(constructMetric("n1", 1)); + group.addMetricFields(constructMetric("n2", 2)); + group.addMetricFields(constructMetric("n3", 3)); + group.addMetricFields(constructMetric("s1", "v1")); + group.addMetricFields(constructMetric("s2", "v2")); + group.addMetricFields(constructMetric("s3", "v3")); + + JSONObject json = group.getJsonObject(); + + assertEquals("foo", json.getString("metric-type")); + assertEquals("bar", json.getString("name")); + assertEquals(1, json.getInt("n1")); + assertEquals(2, json.getInt("n2")); + assertEquals(3, json.getInt("n3")); + assertEquals("v1", json.getString("s1")); + assertEquals("v2", json.getString("s2")); + assertEquals("v3", json.getString("s3")); + + group.clear(); + json = group.getJsonObject(); + + assertEquals("foo", json.getString("metric-type")); + assertEquals("bar", json.getString("name")); + assertFalse(json.has("n1")); + assertFalse(json.has("n2")); + assertFalse(json.has("n3")); + assertFalse(json.has("s1")); + assertFalse(json.has("s2")); + assertFalse(json.has("s3")); + + group.addMetricFields(constructMetric("s1", "v1")); + json = group.getJsonObject(); + assertEquals("v1", json.getString("s1")); + } + + private Metric constructMetric(String name, Object value) { + + MonitorConfig config = MonitorConfig.builder(name).build(); + return new Metric(config, System.currentTimeMillis(), value); + } + } +} \ No newline at end of file diff --git a/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/SynchronizedHttpServletResponse.java b/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/SynchronizedHttpServletResponse.java new file mode 100644 index 000000000..e607a1913 --- /dev/null +++ b/hystrix-contrib/hystrix-servo-stream/src/main/java/com/netflix/hystrix/contrib/servostream/SynchronizedHttpServletResponse.java @@ -0,0 +1,442 @@ +package com.netflix.hystrix.contrib.servostream; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Locale; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +/** + * Thread-safe HttpResponse wrapper to allow multi-threaded services (such as progressive, asynchronous rendering) + * to have multiple threads writing to the stream. + */ +public class SynchronizedHttpServletResponse implements HttpServletResponse { + + private final HttpServletResponse actualResponse; + private SynchronizedOutputStream outputStream; + private SynchronizedPrintWriter writer; + + public SynchronizedHttpServletResponse(HttpServletResponse response) throws IOException { + this.actualResponse = response; + } + + public synchronized void addCookie(Cookie arg0) { + actualResponse.addCookie(arg0); + } + + public synchronized void addDateHeader(String arg0, long arg1) { + actualResponse.addDateHeader(arg0, arg1); + } + + public synchronized void addHeader(String arg0, String arg1) { + actualResponse.addHeader(arg0, arg1); + } + + public synchronized void addIntHeader(String arg0, int arg1) { + actualResponse.addIntHeader(arg0, arg1); + } + + public synchronized boolean containsHeader(String arg0) { + return actualResponse.containsHeader(arg0); + } + + public synchronized String encodeRedirectURL(String arg0) { + return actualResponse.encodeRedirectURL(arg0); + } + + public synchronized String encodeRedirectUrl(String arg0) { + return actualResponse.encodeRedirectUrl(arg0); + } + + public synchronized String encodeURL(String arg0) { + return actualResponse.encodeURL(arg0); + } + + public synchronized String encodeUrl(String arg0) { + return actualResponse.encodeUrl(arg0); + } + + public synchronized void flushBuffer() throws IOException { + actualResponse.flushBuffer(); + } + + public synchronized int getBufferSize() { + return actualResponse.getBufferSize(); + } + + public synchronized String getCharacterEncoding() { + return actualResponse.getCharacterEncoding(); + } + + public synchronized Locale getLocale() { + return actualResponse.getLocale(); + } + + public synchronized ServletOutputStream getOutputStream() throws IOException { + if (outputStream == null) { + outputStream = new SynchronizedOutputStream(actualResponse.getOutputStream()); + } + return outputStream; + } + + public synchronized PrintWriter getWriter() throws IOException { + if (writer == null) { + writer = new SynchronizedPrintWriter(actualResponse.getWriter()); + } + return writer; + } + + public synchronized boolean isCommitted() { + return actualResponse.isCommitted(); + } + + public synchronized void reset() { + actualResponse.reset(); + } + + public synchronized void resetBuffer() { + actualResponse.resetBuffer(); + } + + public synchronized void sendError(int arg0, String arg1) throws IOException { + actualResponse.sendError(arg0, arg1); + } + + public synchronized void sendError(int arg0) throws IOException { + actualResponse.sendError(arg0); + } + + public synchronized void sendRedirect(String arg0) throws IOException { + actualResponse.sendRedirect(arg0); + } + + public synchronized void setBufferSize(int arg0) { + actualResponse.setBufferSize(arg0); + } + + public synchronized void setContentLength(int arg0) { + actualResponse.setContentLength(arg0); + } + + public synchronized void setContentType(String arg0) { + actualResponse.setContentType(arg0); + } + + public synchronized void setDateHeader(String arg0, long arg1) { + actualResponse.setDateHeader(arg0, arg1); + } + + public synchronized void setHeader(String arg0, String arg1) { + actualResponse.setHeader(arg0, arg1); + } + + public synchronized void setIntHeader(String arg0, int arg1) { + actualResponse.setIntHeader(arg0, arg1); + } + + public synchronized void setLocale(Locale arg0) { + actualResponse.setLocale(arg0); + } + + public synchronized void setStatus(int arg0, String arg1) { + actualResponse.setStatus(arg0, arg1); + } + + public synchronized void setStatus(int arg0) { + actualResponse.setStatus(arg0); + } + + @Override + public synchronized String getContentType() { + return actualResponse.getContentType(); + } + + @Override + public synchronized void setCharacterEncoding(String arg0) { + actualResponse.setCharacterEncoding(arg0); + } + + @Override + public synchronized int getStatus() { + return actualResponse.getStatus(); + } + + @Override + public synchronized String getHeader(String name) { + return actualResponse.getHeader(name); + } + + @Override + public synchronized Collection getHeaders(String name) { + return actualResponse.getHeaders(name); + } + + @Override + public synchronized Collection getHeaderNames() { + return actualResponse.getHeaderNames(); + } + + private static class SynchronizedOutputStream extends ServletOutputStream { + + private final ServletOutputStream actual; + + public SynchronizedOutputStream(ServletOutputStream actual) { + this.actual = actual; + } + + public synchronized void close() throws IOException { + actual.close(); + } + + public synchronized boolean equals(Object obj) { + return actual.equals(obj); + } + + public synchronized void flush() throws IOException { + actual.flush(); + } + + public synchronized int hashCode() { + return actual.hashCode(); + } + + public synchronized void print(boolean b) throws IOException { + actual.print(b); + } + + public synchronized void print(char c) throws IOException { + actual.print(c); + } + + public synchronized void print(double d) throws IOException { + actual.print(d); + } + + public synchronized void print(float f) throws IOException { + actual.print(f); + } + + public synchronized void print(int i) throws IOException { + actual.print(i); + } + + public synchronized void print(long l) throws IOException { + actual.print(l); + } + + public synchronized void print(String s) throws IOException { + actual.print(s); + } + + public synchronized void println() throws IOException { + actual.println(); + } + + public synchronized void println(boolean b) throws IOException { + actual.println(b); + } + + public synchronized void println(char c) throws IOException { + actual.println(c); + } + + public synchronized void println(double d) throws IOException { + actual.println(d); + } + + public synchronized void println(float f) throws IOException { + actual.println(f); + } + + public synchronized void println(int i) throws IOException { + actual.println(i); + } + + public synchronized void println(long l) throws IOException { + actual.println(l); + } + + public synchronized void println(String s) throws IOException { + actual.println(s); + } + + public synchronized String toString() { + return actual.toString(); + } + + public synchronized void write(byte[] b, int off, int len) throws IOException { + actual.write(b, off, len); + } + + public synchronized void write(byte[] b) throws IOException { + actual.write(b); + } + + public synchronized void write(int b) throws IOException { + actual.write(b); + } + + } + + private static class SynchronizedPrintWriter extends PrintWriter { + private final PrintWriter actual; + + public SynchronizedPrintWriter(PrintWriter actual) { + super(actual); + this.actual = actual; + } + + public PrintWriter append(char c) { + return actual.append(c); + } + + public synchronized PrintWriter append(CharSequence csq, int start, int end) { + return actual.append(csq, start, end); + } + + public synchronized PrintWriter append(CharSequence csq) { + return actual.append(csq); + } + + public synchronized boolean checkError() { + return actual.checkError(); + } + + public synchronized void close() { + actual.close(); + } + + public synchronized boolean equals(Object obj) { + return actual.equals(obj); + } + + public synchronized void flush() { + actual.flush(); + } + + public synchronized PrintWriter format(Locale l, String format, Object... args) { + return actual.format(l, format, args); + } + + public synchronized PrintWriter format(String format, Object... args) { + return actual.format(format, args); + } + + public synchronized int hashCode() { + return actual.hashCode(); + } + + public synchronized void print(boolean b) { + actual.print(b); + } + + public synchronized void print(char c) { + actual.print(c); + } + + public synchronized void print(char[] s) { + actual.print(s); + } + + public synchronized void print(double d) { + actual.print(d); + } + + public synchronized void print(float f) { + actual.print(f); + } + + public synchronized void print(int i) { + actual.print(i); + } + + public synchronized void print(long l) { + actual.print(l); + } + + public synchronized void print(Object obj) { + actual.print(obj); + } + + public synchronized void print(String s) { + actual.print(s); + } + + public synchronized PrintWriter printf(Locale l, String format, Object... args) { + return actual.printf(l, format, args); + } + + public synchronized PrintWriter printf(String format, Object... args) { + return actual.printf(format, args); + } + + public synchronized void println() { + actual.println(); + } + + public synchronized void println(boolean x) { + actual.println(x); + } + + public synchronized void println(char x) { + actual.println(x); + } + + public synchronized void println(char[] x) { + actual.println(x); + } + + public synchronized void println(double x) { + actual.println(x); + } + + public synchronized void println(float x) { + actual.println(x); + } + + public synchronized void println(int x) { + actual.println(x); + } + + public synchronized void println(long x) { + actual.println(x); + } + + public synchronized void println(Object x) { + actual.println(x); + } + + public synchronized void println(String x) { + actual.println(x); + } + + public synchronized String toString() { + return actual.toString(); + } + + public synchronized void write(char[] buf, int off, int len) { + actual.write(buf, off, len); + } + + public synchronized void write(char[] buf) { + actual.write(buf); + } + + public synchronized void write(int c) { + actual.write(c); + } + + public synchronized void write(String s, int off, int len) { + actual.write(s, off, len); + } + + public synchronized void write(String s) { + actual.write(s); + } + + } + +} diff --git a/settings.gradle b/settings.gradle index f5b4e5c48..4c3425a20 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ rootProject.name='hystrix' -include 'hystrix-core', 'hystrix-examples', 'hystrix-contrib:hystrix-request-servlet' +include 'hystrix-core', 'hystrix-examples', 'hystrix-contrib:hystrix-request-servlet', 'hystrix-contrib:hystrix-servo-stream'