All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.scalar.dl.ledger.server.Stats Maven / Gradle / Ivy

The newest version!
package com.scalar.dl.ledger.server;

import static com.codahale.metrics.MetricRegistry.name;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer.Context;
import com.codahale.metrics.jmx.JmxReporter;
import com.google.common.base.CaseFormat;
import com.scalar.dl.ledger.config.ServerConfig;
import com.scalar.dl.ledger.util.CryptoUtils;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.dropwizard.DropwizardExports;
import io.prometheus.client.exporter.MetricsServlet;
import io.prometheus.client.hotspot.DefaultExports;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Stats {
  private static final Logger LOGGER = LoggerFactory.getLogger(Stats.class.getName());
  private static final String STATS_PREFIX = "stats";
  private static final String SUCCESS_SUFFIX = "success";
  private static final String FAILURE_SUFFIX = "failure";
  private static final TimerContext EMPTY_TIMER_CONTEXT = new TimerContext(null);
  private final MetricRegistry metricRegistry;
  private final String prefix;
  private final String totalSuccessLabel;
  private final String totalFailureLabel;

  public Stats(String productName, String serviceName) {
    this.metricRegistry = new MetricRegistry();
    this.prefix = name(productName, STATS_PREFIX, serviceName);
    this.totalSuccessLabel = name(prefix, "total", SUCCESS_SUFFIX);
    this.totalFailureLabel = name(prefix, "total", FAILURE_SUFFIX);
  }

  public void incrementCounter(String name, boolean isSucceeded) {
    String snake = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name);
    metricRegistry
        .counter(name(prefix, snake, isSucceeded ? SUCCESS_SUFFIX : FAILURE_SUFFIX))
        .inc();
  }

  public void incrementTotalSuccess() {
    metricRegistry.counter(totalSuccessLabel).inc();
  }

  public long getTotalSuccess() {
    return metricRegistry.counter(totalSuccessLabel).getCount();
  }

  public void incrementTotalFailure() {
    metricRegistry.counter(totalFailureLabel).inc();
  }

  public long getTotalFailure() {
    return metricRegistry.counter(totalFailureLabel).getCount();
  }

  public TimerContext measureTime(String name) {
    String snake = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name);
    return new TimerContext(metricRegistry.timer(name(prefix, snake)).time());
  }

  public void startJmxReporter() {
    JmxReporter reporter = JmxReporter.forRegistry(metricRegistry).build();
    reporter.start();
    LOGGER.info("JMX reporter started.");
    Runtime.getRuntime().addShutdownHook(new Thread(reporter::stop));
  }

  public void startPrometheusExporter(ServerConfig config) {
    try {
      CollectorRegistry.defaultRegistry.register(new DropwizardExports(metricRegistry));
      DefaultExports.initialize();

      Server server = createServer(config);
      ServletContextHandler context = new ServletContextHandler();
      context.setContextPath("/");
      server.setHandler(context);
      context.addServlet(new ServletHolder(new MetricsServlet()), "/stats/prometheus");
      server.setStopAtShutdown(true);
      server.start();
      LOGGER.info(
          "Prometheus exporter started, listening on {}"
              + (config.isServerTlsEnabled() ? " with TLS" : ""),
          config.getPrometheusExporterPort());
    } catch (Exception e) {
      LOGGER.error("Failed to start Prometheus exporter.", e);
    }
  }

  private Server createServer(ServerConfig config) {
    Server server = new Server();
    ServerConnector connector;
    if (config.isServerTlsEnabled()) {
      // Disable SNI host check to allow Prometheus to connect
      SecureRequestCustomizer secureRequestCustomizer = new SecureRequestCustomizer();
      secureRequestCustomizer.setSniHostCheck(false);
      HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory();
      httpConnectionFactory.getHttpConfiguration().addCustomizer(secureRequestCustomizer);

      connector =
          new ServerConnector(server, createSslContextFactory(config), httpConnectionFactory);
    } else {
      connector = new ServerConnector(server);
    }
    connector.setPort(config.getPrometheusExporterPort());
    server.setConnectors(new Connector[] {connector});
    return server;
  }

  private SslContextFactory createSslContextFactory(ServerConfig config) {
    // We can use a dummy password because the key store is not persisted
    String keyPassword = "Dummy_password1234!";

    SslContextFactory sslContextFactory = new SslContextFactory.Server();
    sslContextFactory.setKeyStore(
        CryptoUtils.createKeyStore(
            config.getServerTlsCertChainPath(), config.getServerTlsPrivateKeyPath(), keyPassword));
    sslContextFactory.setKeyStorePassword(keyPassword);
    return sslContextFactory;
  }

  public static TimerContext emptyTimerContext() {
    return EMPTY_TIMER_CONTEXT;
  }

  public static class TimerContext implements AutoCloseable {
    private final Context context;

    public TimerContext(Context context) {
      this.context = context;
    }

    @Override
    public void close() throws Exception {
      if (context != null) {
        context.close();
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy