/*
* Copyright 2013-2015 the original author or authors.
*
* 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 org.springframework.cloud.netflix.hystrix.stream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.LinkedBlockingQueue;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.netflix.hystrix.HystrixCircuitBreaker;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandMetrics;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolMetrics;
import com.netflix.hystrix.util.HystrixRollingNumberEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.annotation.Scheduled;
/**
* @author Spencer Gibb
*
* @see com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsPoller (nested
* private class MetricsPoller)
*/
public class HystrixStreamTask implements ApplicationContextAware {
private static Log log = LogFactory.getLog(HystrixStreamTask.class);
private MessageChannel outboundChannel;
private DiscoveryClient discoveryClient;
private HystrixStreamProperties properties;
private ApplicationContext context;
// Visible for testing
final LinkedBlockingQueue<String> jsonMetrics;
private final JsonFactory jsonFactory = new JsonFactory();
public HystrixStreamTask(MessageChannel outboundChannel, DiscoveryClient discoveryClient, HystrixStreamProperties properties) {
this.outboundChannel = outboundChannel;
this.discoveryClient = discoveryClient;
this.properties = properties;
this.jsonMetrics = new LinkedBlockingQueue<>(properties.getSize());
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.context = applicationContext;
}
// TODO: use integration to split this up?
@Scheduled(fixedRateString = "${hystrix.stream.queue.sendRate:500}")
public void sendMetrics() {
ArrayList<String> metrics = new ArrayList<>();
this.jsonMetrics.drainTo(metrics);
if (!metrics.isEmpty()) {
if (log.isTraceEnabled()) {
log.trace("sending stream metrics size: " + metrics.size());
}
for (String json : metrics) {
// TODO: batch all metrics to one message
try {
// TODO: remove the explicit content type when s-c-stream can handle that for us
this.outboundChannel.send(MessageBuilder.withPayload(json)
.setHeader(MessageHeaders.CONTENT_TYPE,
this.properties.getContentType())
.build());
}
catch (Exception ex) {
if (log.isTraceEnabled()) {
log.trace("failed sending stream metrics: " + ex.getMessage());
}
}
}
}
}
@Scheduled(fixedRateString = "${hystrix.stream.queue.gatherRate:500}")
public void gatherMetrics() {
try {
// command metrics
Collection<HystrixCommandMetrics> instances = HystrixCommandMetrics
.getInstances();
if (!instances.isEmpty()) {
log.trace("gathering metrics size: " + instances.size());
}
ServiceInstance localService = this.discoveryClient.getLocalServiceInstance();
for (HystrixCommandMetrics commandMetrics : instances) {
HystrixCommandKey key = commandMetrics.getCommandKey();
HystrixCircuitBreaker circuitBreaker = HystrixCircuitBreaker.Factory
.getInstance(key);
StringWriter jsonString = new StringWriter();
JsonGenerator json = this.jsonFactory.createGenerator(jsonString);
json.writeStartObject();
addServiceData(json, localService);
json.writeObjectFieldStart("data");
json.writeStringField("type", "HystrixCommand");
String name = key.name();
if (this.properties.isPrefixMetricName()) {
name = localService.getServiceId() + "." + name;
}
json.writeStringField("name", name);
json.writeStringField("group", commandMetrics.getCommandGroup().name());
json.writeNumberField("currentTime", System.currentTimeMillis());
// circuit breaker
if (circuitBreaker == null) {
// circuit breaker is disabled and thus never open
json.writeBooleanField("isCircuitBreakerOpen", false);
}
else {
json.writeBooleanField("isCircuitBreakerOpen",
circuitBreaker.isOpen());
}
HystrixCommandMetrics.HealthCounts healthCounts = commandMetrics
.getHealthCounts();
json.writeNumberField("errorPercentage",
healthCounts.getErrorPercentage());
json.writeNumberField("errorCount", healthCounts.getErrorCount());
json.writeNumberField("requestCount", healthCounts.getTotalRequests());
// rolling counters
json.writeNumberField("rollingCountCollapsedRequests", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.COLLAPSED));
json.writeNumberField("rollingCountExceptionsThrown", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.EXCEPTION_THROWN));
json.writeNumberField("rollingCountFailure", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.FAILURE));
json.writeNumberField("rollingCountFallbackFailure", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.FALLBACK_FAILURE));
json.writeNumberField("rollingCountFallbackRejection", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.FALLBACK_REJECTION));
json.writeNumberField("rollingCountFallbackSuccess", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.FALLBACK_SUCCESS));
json.writeNumberField("rollingCountResponsesFromCache", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.RESPONSE_FROM_CACHE));
json.writeNumberField("rollingCountSemaphoreRejected", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.SEMAPHORE_REJECTED));
json.writeNumberField("rollingCountShortCircuited", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.SHORT_CIRCUITED));
json.writeNumberField("rollingCountSuccess", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.SUCCESS));
json.writeNumberField("rollingCountThreadPoolRejected", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.THREAD_POOL_REJECTED));
json.writeNumberField("rollingCountTimeout", commandMetrics
.getRollingCount(HystrixRollingNumberEvent.TIMEOUT));
json.writeNumberField("currentConcurrentExecutionCount",
commandMetrics.getCurrentConcurrentExecutionCount());
// latency percentiles
json.writeNumberField("latencyExecute_mean",
commandMetrics.getExecutionTimeMean());
json.writeObjectFieldStart("latencyExecute");
json.writeNumberField("0", commandMetrics.getExecutionTimePercentile(0));
json.writeNumberField("25",
commandMetrics.getExecutionTimePercentile(25));
json.writeNumberField("50",
commandMetrics.getExecutionTimePercentile(50));
json.writeNumberField("75",
commandMetrics.getExecutionTimePercentile(75));
json.writeNumberField("90",
commandMetrics.getExecutionTimePercentile(90));
json.writeNumberField("95",
commandMetrics.getExecutionTimePercentile(95));
json.writeNumberField("99",
commandMetrics.getExecutionTimePercentile(99));
json.writeNumberField("99.5",
commandMetrics.getExecutionTimePercentile(99.5));
json.writeNumberField("100",
commandMetrics.getExecutionTimePercentile(100));
json.writeEndObject();
//
json.writeNumberField("latencyTotal_mean",
commandMetrics.getTotalTimeMean());
json.writeObjectFieldStart("latencyTotal");
json.writeNumberField("0", commandMetrics.getTotalTimePercentile(0));
json.writeNumberField("25", commandMetrics.getTotalTimePercentile(25));
json.writeNumberField("50", commandMetrics.getTotalTimePercentile(50));
json.writeNumberField("75", commandMetrics.getTotalTimePercentile(75));
json.writeNumberField("90", commandMetrics.getTotalTimePercentile(90));
json.writeNumberField("95", commandMetrics.getTotalTimePercentile(95));
json.writeNumberField("99", commandMetrics.getTotalTimePercentile(99));
json.writeNumberField("99.5",
commandMetrics.getTotalTimePercentile(99.5));
json.writeNumberField("100", commandMetrics.getTotalTimePercentile(100));
json.writeEndObject();
// property values for reporting what is actually seen by the command
// rather than what was set somewhere
HystrixCommandProperties commandProperties = commandMetrics
.getProperties();
json.writeNumberField(
"propertyValue_circuitBreakerRequestVolumeThreshold",
commandProperties.circuitBreakerRequestVolumeThreshold().get());
json.writeNumberField(
"propertyValue_circuitBreakerSleepWindowInMilliseconds",
commandProperties.circuitBreakerSleepWindowInMilliseconds()
.get());
json.writeNumberField(
"propertyValue_circuitBreakerErrorThresholdPercentage",
commandProperties.circuitBreakerErrorThresholdPercentage().get());
json.writeBooleanField("propertyValue_circuitBreakerForceOpen",
commandProperties.circuitBreakerForceOpen().get());
json.writeBooleanField("propertyValue_circuitBreakerForceClosed",
commandProperties.circuitBreakerForceClosed().get());
json.writeBooleanField("propertyValue_circuitBreakerEnabled",
commandProperties.circuitBreakerEnabled().get());
json.writeStringField("propertyValue_executionIsolationStrategy",
commandProperties.executionIsolationStrategy().get().name());
json.writeNumberField(
"propertyValue_executionIsolationThreadTimeoutInMilliseconds",
commandProperties.executionIsolationThreadTimeoutInMilliseconds()
.get());
json.writeBooleanField(
"propertyValue_executionIsolationThreadInterruptOnTimeout",
commandProperties.executionIsolationThreadInterruptOnTimeout()
.get());
json.writeStringField(
"propertyValue_executionIsolationThreadPoolKeyOverride",
commandProperties.executionIsolationThreadPoolKeyOverride()
.get());
json.writeNumberField(
"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests",
commandProperties
.executionIsolationSemaphoreMaxConcurrentRequests()
.get());
json.writeNumberField(
"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests",
commandProperties
.fallbackIsolationSemaphoreMaxConcurrentRequests().get());
// TODO
/*
* The following are commented out as these rarely change and are verbose
* for streaming for something people don't change. We could perhaps allow
* a property or request argument to include these.
*/
// json.put("propertyValue_metricsRollingPercentileEnabled",
// commandProperties.metricsRollingPercentileEnabled().get());
// json.put("propertyValue_metricsRollingPercentileBucketSize",
// commandProperties.metricsRollingPercentileBucketSize().get());
// json.put("propertyValue_metricsRollingPercentileWindow",
// commandProperties.metricsRollingPercentileWindowInMilliseconds().get());
// json.put("propertyValue_metricsRollingPercentileWindowBuckets",
// commandProperties.metricsRollingPercentileWindowBuckets().get());
// json.put("propertyValue_metricsRollingStatisticalWindowBuckets",
// commandProperties.metricsRollingStatisticalWindowBuckets().get());
json.writeNumberField(
"propertyValue_metricsRollingStatisticalWindowInMilliseconds",
commandProperties.metricsRollingStatisticalWindowInMilliseconds()
.get());
json.writeBooleanField("propertyValue_requestCacheEnabled",
commandProperties.requestCacheEnabled().get());
json.writeBooleanField("propertyValue_requestLogEnabled",
commandProperties.requestLogEnabled().get());
json.writeNumberField("reportingHosts", 1); // this will get summed across
// all instances in a cluster
json.writeEndObject(); // end data attribute
json.writeEndObject();
json.close();
// output
this.jsonMetrics.add(jsonString.getBuffer().toString());
}
// thread pool metrics
for (HystrixThreadPoolMetrics threadPoolMetrics : HystrixThreadPoolMetrics
.getInstances()) {
HystrixThreadPoolKey key = threadPoolMetrics.getThreadPoolKey();
StringWriter jsonString = new StringWriter();
JsonGenerator json = this.jsonFactory.createGenerator(jsonString);
json.writeStartObject();
addServiceData(json, localService);
json.writeObjectFieldStart("data");
json.writeStringField("type", "HystrixThreadPool");
json.writeStringField("name", key.name());
json.writeNumberField("currentTime", System.currentTimeMillis());
json.writeNumberField("currentActiveCount",
threadPoolMetrics.getCurrentActiveCount().intValue());
json.writeNumberField("currentCompletedTaskCount",
threadPoolMetrics.getCurrentCompletedTaskCount().longValue());
json.writeNumberField("currentCorePoolSize",
threadPoolMetrics.getCurrentCorePoolSize().intValue());
json.writeNumberField("currentLargestPoolSize",
threadPoolMetrics.getCurrentLargestPoolSize().intValue());
json.writeNumberField("currentMaximumPoolSize",
threadPoolMetrics.getCurrentMaximumPoolSize().intValue());
json.writeNumberField("currentPoolSize",
threadPoolMetrics.getCurrentPoolSize().intValue());
json.writeNumberField("currentQueueSize",
threadPoolMetrics.getCurrentQueueSize().intValue());
json.writeNumberField("currentTaskCount",
threadPoolMetrics.getCurrentTaskCount().longValue());
json.writeNumberField("rollingCountThreadsExecuted",
threadPoolMetrics.getRollingCountThreadsExecuted());
json.writeNumberField("rollingMaxActiveThreads",
threadPoolMetrics.getRollingMaxActiveThreads());
json.writeNumberField("propertyValue_queueSizeRejectionThreshold",
threadPoolMetrics.getProperties().queueSizeRejectionThreshold()
.get());
json.writeNumberField(
"propertyValue_metricsRollingStatisticalWindowInMilliseconds",
threadPoolMetrics.getProperties()
.metricsRollingStatisticalWindowInMilliseconds().get());
json.writeNumberField("reportingHosts", 1); // this will get summed across
// all instances in a cluster
json.writeEndObject(); // end of data object
json.writeEndObject();
json.close();
// output to stream
this.jsonMetrics.add(jsonString.getBuffer().toString());
}
}
catch (Exception ex) {
log.error("Error adding metrics to queue", ex);
}
}
private void addServiceData(JsonGenerator json, ServiceInstance localService)
throws IOException {
json.writeObjectFieldStart("origin");
json.writeStringField("host", localService.getHost());
json.writeNumberField("port", localService.getPort());
json.writeStringField("serviceId", localService.getServiceId());
if (this.properties.isSendId()) {
json.writeStringField("id", this.context.getId());
}
json.writeEndObject();
}
}