Skip to content

Instantly share code, notes, and snippets.

@fastnsilver
Last active March 14, 2016 17:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save fastnsilver/ed6948e0f2e464b52d5f to your computer and use it in GitHub Desktop.
Save fastnsilver/ed6948e0f2e464b52d5f to your computer and use it in GitHub Desktop.
A possible way to bring together AWS CloudWatch and ServoMetrics observers in a Jetty Container
@Configuration
public class JettyConfig {
@Autowired
private MetricRegistry metricRegistry;
public Server jettyServer(int maxThreads, int minThreads, int idleTimeout) {
QueuedThreadPool threadPool = new InstrumentedQueuedThreadPool(metricRegistry);
threadPool.setMaxThreads(maxThreads);
threadPool.setMinThreads(minThreads);
threadPool.setIdleTimeout(idleTimeout);
return new Server(threadPool);
}
// @see http://wiki.eclipse.org/Jetty/Howto/High_Load
// @see http://jdpgrailsdev.github.io/blog/2014/10/07/spring_boot_jetty_thread_pool.html
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory(
@Value("${server.port:8080}") final int port,
@Value("${jetty.threadPool.maxThreads:1000}") final int maxThreads,
@Value("${jetty.threadPool.minThreads:100}") final int minThreads,
@Value("${jetty.threadPool.idleTimeout:60000}") final int idleTimeout) {
final JettyEmbeddedServletContainerFactory factory = new JettyEmbeddedServletContainerFactory(port);
factory.addServerCustomizers(customizer -> { jettyServer(maxThreads, minThreads, idleTimeout); });
return factory;
}
}
/**
* {@link MetricObserver} to convert Servo metrics into Spring Boot {@link Metric}
* instances.
*/
public final class ServoMetricObserver extends BaseMetricObserver {
private final MetricWriter metrics;
public ServoMetricObserver(MetricWriter metrics) {
super("spring-boot");
this.metrics = metrics;
}
@Override
public void updateImpl(List<com.netflix.servo.Metric> servoMetrics) {
for (com.netflix.servo.Metric servoMetric : servoMetrics) {
MonitorConfig config = servoMetric.getConfig();
String type = config.getTags().getValue("type");
String key = new StringBuilder(type).append(".servo.")
.append(config.getName()).toString().toLowerCase();
if (servoMetric.hasNumberValue()) {
this.metrics.set(new Metric<Number>(key,
servoMetric.getNumberValue(), new Date(servoMetric
.getTimestamp())));
}
}
}
}
/**
* Modeled after {@link org.springframework.cloud.netflix.servo.ServoMetricCollector}
* Scans for {@link com.netflix.servo.publish.MetricObserver} implementations on classpath and
* adds them to {@link com.netflix.servo.publish.PollRunnable} making this
* implementation open for extension
*/
public class UnifiedMetricCollector {
private final Logger log = LoggerFactory.getLogger(getClass());
@Value("${spring.aws.cloudwatch.metrics.polling.timeUnit:SECONDS}")
private String timeUnit;
@Value("${spring.aws.cloudwatch.metrics.polling.interval:5}")
private int pollingInterval;
@Autowired
private ListableBeanFactory beanFactory;
public UnifiedMetricCollector() {
PollRunnable task = new PollRunnable(new MonitorRegistryMetricPoller(),
BasicMetricFilter.MATCH_ALL, true, beanFactory.getBeansOfType(MetricObserver.class).values());
if (!PollScheduler.getInstance().isStarted()) {
try {
PollScheduler.getInstance().start();
}
catch (Exception e) {
// Can fail due to race condition with evil singletons (if more than one
// app in same JVM)
log.error("Could not start servo metrics poller", e);
return;
}
}
PollScheduler.getInstance().addPoller(task, pollingInterval, TimeUnit.valueOf(timeUnit.toUpperCase()));
}
@PreDestroy
public void destroy() throws Exception {
log.info("Stopping Servo PollScheduler");
if (PollScheduler.getInstance().isStarted()) {
try {
PollScheduler.getInstance().stop();
}
catch (Exception e) {
log.error("Could not stop servo metrics poller", e);
}
}
}
}
@Configuration
@ConditionalOnMissingBean(value={ServoMetricsAutoConfiguration.class})
@ConditionalOnWebApplication
@ConditionalOnClass({ Monitors.class, MetricReader.class })
@ConditionalOnBean(MetricReader.class)
@AutoConfigureBefore(EndpointAutoConfiguration.class)
@AutoConfigureAfter({ MetricRepositoryAutoConfiguration.class })
public class UnifiedMetricsConfig {
@Configuration
@ConditionalOnAwsCloudEnvironment
@EnableContextCredentials
static class AwsCloudWatchMetricsConfig {
@Value("${spring.application.name}")
private String applicationName;
@Autowired
private AWSCredentials awsCredentials;
@Bean
@ConditionalOnMissingBean
MetricObserver cloudWatchMetricObserver() {
return new CloudWatchMetricObserver(applicationName, "servo.cloudwatch", new AmazonCloudWatchAsyncClient(awsCredentials));
}
}
@Bean
@ConditionalOnMissingBean
public MetricObserver servoMetricObserver(MetricWriter metrics) {
return new ServoMetricObserver(metrics);
}
@Bean
@ConditionalOnMissingBean
public UnifiedMetricCollector unifiedMetricCollector() {
return new UnifiedMetricCollector();
}
}
@fastnsilver
Copy link
Author

InstrumentedQueuedThreadPool in JettyConfig above is from

<dependency>
            <groupId>io.dropwizard.metrics</groupId>
            <artifactId>metrics-jetty9</artifactId>
            <version>${dropwizard-metrics.version}</version>
        </dependency>

which allows us to capture thread stats from Jetty.

@fastnsilver
Copy link
Author

Note how UnifiedMetricsConfig gets instantiated only when ServoMetricsAutoConfiguration is not on classpath (i.e., not defined as bean).

@fastnsilver
Copy link
Author

UnifiedMetricCollector is a drop-in replacement for ServoMetricCollector when one or more MetricObserver implementations are declared as Spring beans.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment