Ejemplo n.º 1
0
/** This class contains the configuration options for stagemonitor's core functionality */
public class CorePlugin extends StagemonitorPlugin {

  public static final String DEFAULT_APPLICATION_NAME = "My Application";

  private static final String CORE_PLUGIN_NAME = "Core";
  public static final String POOLS_QUEUE_CAPACITY_LIMIT_KEY =
      "stagemonitor.threadPools.queueCapacityLimit";

  private final Logger logger = LoggerFactory.getLogger(getClass());

  final ConfigurationOption<Boolean> stagemonitorActive =
      ConfigurationOption.booleanOption()
          .key("stagemonitor.active")
          .dynamic(true)
          .label("Activate stagemonitor")
          .description("If set to `false` stagemonitor will be completely deactivated.")
          .defaultValue(true)
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Boolean> internalMonitoring =
      ConfigurationOption.booleanOption()
          .key("stagemonitor.internal.monitoring")
          .dynamic(true)
          .label("Internal monitoring")
          .description("If active, stagemonitor will collect internal performance data")
          .defaultValue(false)
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Integer> reportingIntervalConsole =
      ConfigurationOption.integerOption()
          .key("stagemonitor.reporting.interval.console")
          .dynamic(false)
          .label("Reporting interval console")
          .description(
              "The amount of time between console reports (in seconds). "
                  + "To deactivate console reports, set this to a value below 1.")
          .defaultValue(0)
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Integer> reportingIntervalAggregation =
      ConfigurationOption.integerOption()
          .key("stagemonitor.reporting.interval.aggregation")
          .dynamic(false)
          .label("Metrics aggregation interval")
          .description(
              "The amount of time between all registered metrics are aggregated for a report on server "
                  + "shutdown that shows aggregated values for all metrics of the measurement session. "
                  + "To deactivate a aggregate report on shutdown, set this to a value below 1.")
          .defaultValue(30)
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Boolean> reportingJmx =
      ConfigurationOption.booleanOption()
          .key("stagemonitor.reporting.jmx")
          .dynamic(false)
          .label("Expose MBeans")
          .description("Whether or not to expose all metrics as MBeans.")
          .defaultValue(true)
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Integer> reportingIntervalGraphite =
      ConfigurationOption.integerOption()
          .key("stagemonitor.reporting.interval.graphite")
          .dynamic(false)
          .label("Reporting interval graphite")
          .description(
              "The amount of time between the metrics are reported to graphite (in seconds).\n"
                  + "To deactivate graphite reporting, set this to a value below 1, or don't provide "
                  + "stagemonitor.reporting.graphite.hostName.")
          .defaultValue(60)
          .tags("metrics-store", "graphite")
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<String> graphiteHostName =
      ConfigurationOption.stringOption()
          .key("stagemonitor.reporting.graphite.hostName")
          .dynamic(false)
          .label("Graphite host name")
          .description(
              "The name of the host where graphite is running. This setting is mandatory, if you want "
                  + "to use the grafana dashboards.")
          .defaultValue(null)
          .tags("metrics-store", "graphite")
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Integer> graphitePort =
      ConfigurationOption.integerOption()
          .key("stagemonitor.reporting.graphite.port")
          .dynamic(false)
          .label("Carbon port")
          .description("The port where carbon is listening.")
          .defaultValue(2003)
          .tags("metrics-store", "graphite")
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<String> influxDbUrl =
      ConfigurationOption.stringOption()
          .key("stagemonitor.reporting.influxdb.url")
          .dynamic(true)
          .label("InfluxDB URL")
          .description("The URL of your InfluxDB installation.")
          .defaultValue(null)
          .tags("metrics-store", "influx-db")
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<String> influxDbDb =
      ConfigurationOption.stringOption()
          .key("stagemonitor.reporting.influxdb.db")
          .dynamic(true)
          .label("InfluxDB database")
          .description("The target database")
          .defaultValue("stagemonitor")
          .tags("metrics-store", "influx-db")
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Integer> reportingIntervalInfluxDb =
      ConfigurationOption.integerOption()
          .key("stagemonitor.reporting.interval.influxdb")
          .dynamic(false)
          .label("Reporting interval InfluxDb")
          .description(
              "The amount of time between the metrics are reported to InfluxDB (in seconds).")
          .defaultValue(60)
          .tags("metrics-store", "influx-db")
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Integer> reportingIntervalElasticsearch =
      ConfigurationOption.integerOption()
          .key("stagemonitor.reporting.interval.elasticsearch")
          .dynamic(false)
          .label("Reporting interval Elasticsearch")
          .description(
              "The amount of time between the metrics are reported to Elasticsearch (in seconds).")
          .defaultValue(60)
          .tags("metrics-store", "elasticsearch")
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Integer> deleteElasticsearchMetricsAfterDays =
      ConfigurationOption.integerOption()
          .key("stagemonitor.reporting.elasticsearch.deleteMetricsAfterDays")
          .dynamic(false)
          .label("Delete ES metrics after (days)")
          .description(
              "The number of days after the metrics stored in elasticsearch should be deleted. Set below 1 to deactivate.")
          .defaultValue(-1)
          .tags("metrics-store", "elasticsearch")
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Integer> moveToColdNodesAfterDays =
      ConfigurationOption.integerOption()
          .key("stagemonitor.elasticsearch.hotColdArchitecture.moveToColdNodesAfterDays")
          .dynamic(false)
          .label("Activate Hot-Cold Architecture")
          .description(
              "Setting this to a value > 1 activates the hot-cold architecture described in https://www.elastic.co/blog/hot-warm-architecture "
                  + "where new data that is more frequently queried and updated is stored on beefy nodes (SSDs, more RAM and CPU). "
                  + "When the indexes reach a certain age, they are allocated on cold nodes. For this to work, you have to tag your "
                  + "beefy nodes with node.box_type: hot (either in elasticsearch.yml or start the node using ./bin/elasticsearch --node.box_type hot)"
                  + "and your historical nodes with node.box_type: cold.")
          .defaultValue(-1)
          .tags("metrics-store", "elasticsearch")
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<String> applicationName =
      ConfigurationOption.stringOption()
          .key("stagemonitor.applicationName")
          .dynamic(false)
          .label("Application name")
          .description(
              "The name of the application.\n"
                  + "Either this property or the display-name in web.xml is mandatory!")
          .defaultValue(null)
          .configurationCategory(CORE_PLUGIN_NAME)
          .tags("important")
          .build();
  private final ConfigurationOption<String> instanceName =
      ConfigurationOption.stringOption()
          .key("stagemonitor.instanceName")
          .dynamic(false)
          .label("Instance name")
          .description(
              "The instance name.\n"
                  + "If this property is not set, the instance name set to the first request's "
                  + "javax.servlet.ServletRequest#getServerName()\n"
                  + "That means that the collection of metrics does not start before the first request is executed!")
          .defaultValue(null)
          .configurationCategory(CORE_PLUGIN_NAME)
          .tags("important")
          .build();
  private final ConfigurationOption<String> elasticsearchUrl =
      ConfigurationOption.stringOption()
          .key("stagemonitor.elasticsearch.url")
          .dynamic(true)
          .label("Elasticsearch URL")
          .description(
              "The URL of the elasticsearch server that stores the call stacks. If the URL is not "
                  + "provided, the call stacks won't get stored.")
          .defaultValue(null)
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Collection<String>> elasticsearchConfigurationSourceProfiles =
      ConfigurationOption.stringsOption()
          .key("stagemonitor.elasticsearch.configurationSourceProfiles")
          .dynamic(false)
          .label("Elasticsearch configuration source profiles")
          .description(
              "Set configuration profiles of configuration stored in elasticsearch as a centralized configuration source "
                  + "that can be shared between multiple server instances. Set the profiles appropriate to the current "
                  + "environment e.g. `production,common`, `local`, `test`, ... The configuration will be stored under "
                  + "`{stagemonitor.elasticsearch.url}/stagemonitor/configuration/{configurationSourceProfile}`.")
          .defaultValue(Collections.<String>emptyList())
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Boolean> deactivateStagemonitorIfEsConfigSourceIsDown =
      ConfigurationOption.booleanOption()
          .key("stagemonitor.elasticsearch.configurationSource.deactivateStagemonitorIfEsIsDown")
          .dynamic(false)
          .label("Deactivate stagemonitor if elasticsearch configuration source is down")
          .description(
              "Set to true if stagemonitor should be deactivated if "
                  + "stagemonitor.elasticsearch.configurationSourceProfiles is set but elasticsearch can't be reached "
                  + "under stagemonitor.elasticsearch.url. Defaults to true to prevent starting stagemonitor with "
                  + "wrong configuration.")
          .defaultValue(true)
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Collection<Pattern>> excludedMetrics =
      ConfigurationOption.regexListOption()
          .key("stagemonitor.metrics.excluded.pattern")
          .dynamic(false)
          .label("Excluded metrics (regex)")
          .description("A comma separated list of metric names that should not be collected.")
          .defaultValue(Collections.<Pattern>emptyList())
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Collection<String>> disabledPlugins =
      ConfigurationOption.stringsOption()
          .key("stagemonitor.plugins.disabled")
          .dynamic(false)
          .label("Disabled plugins")
          .description(
              "A comma separated list of plugin names (the simple class name) that should not be active.")
          .defaultValue(Collections.<String>emptyList())
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Integer> reloadConfigurationInterval =
      ConfigurationOption.integerOption()
          .key("stagemonitor.configuration.reload.interval")
          .dynamic(false)
          .label("Configuration reload interval")
          .description(
              "The interval in seconds a reload of all configuration sources is performed. "
                  + "Set to a value below `1` to deactivate periodic reloading the configuration.")
          .defaultValue(60)
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Collection<String>> excludePackages =
      ConfigurationOption.stringsOption()
          .key("stagemonitor.instrument.exclude")
          .dynamic(true)
          .label("Excluded packages")
          .description(
              "Exclude packages and their sub-packages from the instrumentation (for example the profiler).")
          .defaultValue(
              new LinkedHashSet<String>() {
                {
                  add("antlr");
                  add("aopalliance");
                  add("asm");
                  add("c3p0");
                  add("ch.qos");
                  add("com.amazon");
                  add("com.codahale");
                  add("com.fasterxml");
                  add("com.github");
                  add("com.google");
                  add("com.maxmind");
                  add("com.oracle");
                  add("com.p6spy");
                  add("com.rome");
                  add("com.spartial");
                  add("com.sun");
                  add("com.thoughtworks");
                  add("com.vaadin");
                  add("commons-");
                  add("dom4j");
                  add("eclipse");
                  add("java.");
                  add("javax.");
                  add("junit");
                  add("net.java");
                  add("net.sf");
                  add("net.sourceforge");
                  add("nz.net");
                  add("ognl");
                  add("oracle");
                  add("org.antlr");
                  add("org.apache");
                  add("org.aspectj");
                  add("org.codehaus");
                  add("org.eclipse");
                  add("org.freemarker");
                  add("org.glassfish");
                  add("org.groovy");
                  add("org.hibernate");
                  add("org.hsqldb");
                  add("org.jadira");
                  add("org.javassist");
                  add("org.jboss");
                  add("org.jdom");
                  add("org.joda");
                  add("org.jsoup");
                  add("org.json");
                  add("org.unbescape");
                  add("org.elasticsearch");
                  add("org.slf4j");
                  add("org.springframework");
                  add("org.stagemonitor");
                  add("org.thymeleaf");
                  add("org.yaml");
                  add("org.wildfly");
                  add("org.zeroturnaround");
                  add("org.xml");
                  add("io.dropwizard");
                  add("freemarker");
                  add("javassist");
                  add("uadetector");
                  add("p6spy");
                  add("rome");
                  add("sun");
                  add("xerces");
                  add("xml");
                  add("xmpp");
                }
              })
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Collection<String>> excludeContaining =
      ConfigurationOption.stringsOption()
          .key("stagemonitor.instrument.excludeContaining")
          .dynamic(true)
          .label("Exclude containing")
          .description(
              "Exclude classes from the instrumentation (for example from profiling) that contain one of the "
                  + "following strings as part of their class name.")
          .defaultValue(
              new LinkedHashSet<String>() {
                {
                  add("$JaxbAccessor");
                  add("$$");
                  add("CGLIB");
                  add("EnhancerBy");
                  add("$Proxy");
                }
              })
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Collection<String>> includePackages =
      ConfigurationOption.stringsOption()
          .key("stagemonitor.instrument.include")
          .dynamic(true)
          .label("Included packages")
          .description(
              "The packages that should be included for instrumentation (for example the profiler). "
                  + "If this property is empty, all packages (except for the excluded ones) are instrumented. "
                  + "You can exclude subpackages of a included package via `stagemonitor.instrument.exclude`.")
          .defaultValue(Collections.<String>emptySet())
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Boolean> attachAgentAtRuntime =
      ConfigurationOption.booleanOption()
          .key("stagemonitor.instrument.runtimeAttach")
          .dynamic(false)
          .label("Attach agent at runtime")
          .description(
              "Attaches the agent via the Attach API at runtime and retransforms all currently loaded classes.")
          .defaultValue(true)
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<Collection<String>> excludedInstrumenters =
      ConfigurationOption.stringsOption()
          .key("stagemonitor.instrument.excludedInstrumenter")
          .dynamic(false)
          .label("Excluded Instrumenters")
          .description(
              "A list of the simple class names of StagemonitorJavassistInstrumenters that should not be applied")
          .defaultValue(Collections.<String>emptySet())
          .configurationCategory(CORE_PLUGIN_NAME)
          .build();
  private final ConfigurationOption<String> grafanaUrl =
      ConfigurationOption.stringOption()
          .key("stagemonitor.grafana.url")
          .dynamic(true)
          .label("Grafana URL")
          .description("The URL of your Grafana 2.0 installation")
          .defaultValue(null)
          .configurationCategory(CORE_PLUGIN_NAME)
          .tags("grafana")
          .build();
  private final ConfigurationOption<String> grafanaApiKey =
      ConfigurationOption.stringOption()
          .key("stagemonitor.grafana.apiKey")
          .dynamic(true)
          .label("Grafana API Key")
          .description(
              "The API Key of your Grafana 2.0 installation. "
                  + "See http://docs.grafana.org/reference/http_api/#create-api-token how to create a key. "
                  + "The key has to have the admin role. This is necessary so that stagemonitor can automatically add "
                  + "datasources and dashboards to Grafana.")
          .defaultValue(null)
          .configurationCategory(CORE_PLUGIN_NAME)
          .tags("grafana")
          .sensitive()
          .build();
  private final ConfigurationOption<Integer> threadPoolQueueCapacityLimit =
      ConfigurationOption.integerOption()
          .key(POOLS_QUEUE_CAPACITY_LIMIT_KEY)
          .dynamic(false)
          .label("Thread Pool Queue Capacity Limit")
          .description(
              "Sets a limit to the number of pending tasks in the ExecutorServices stagemonitor uses. "
                  + "These are thread pools that are used for example to report request traces to elasticsearch. "
                  + "If elasticsearch is unreachable or your application encounters a spike in incoming requests this limit could be reached. "
                  + "It is used to prevent the queue from growing indefinitely. ")
          .defaultValue(1000)
          .configurationCategory(CORE_PLUGIN_NAME)
          .tags("advanced")
          .build();

  private static MetricsAggregationReporter aggregationReporter;

  private List<Closeable> reporters = new CopyOnWriteArrayList<Closeable>();

  private ElasticsearchClient elasticsearchClient;
  private GrafanaClient grafanaClient;
  private IndexSelector indexSelector = new IndexSelector(new Clock.UserTimeClock());

  @Override
  public void initializePlugin(Metric2Registry metricRegistry, Configuration configuration) {
    final Integer reloadInterval = getReloadConfigurationInterval();
    if (reloadInterval > 0) {
      configuration.scheduleReloadAtRate(reloadInterval, TimeUnit.SECONDS);
    }

    metricRegistry.register(
        MetricName.name("online").build(),
        new Gauge<Integer>() {
          @Override
          public Integer getValue() {
            return 1;
          }
        });

    ElasticsearchClient elasticsearchClient = getElasticsearchClient();
    if (isReportToGraphite()) {
      elasticsearchClient.sendGrafana1DashboardAsync("Grafana1GraphiteCustomMetrics.json");
    }
    elasticsearchClient.createIndex(
        "stagemonitor", IOUtils.getResourceAsStream("stagemonitor-elasticsearch-mapping.json"));
    if (isReportToElasticsearch()) {
      final GrafanaClient grafanaClient = getGrafanaClient();
      grafanaClient.createElasticsearchDatasource(getElasticsearchUrl());
    }
    registerReporters(metricRegistry, configuration);
  }

  private void registerReporters(Metric2Registry metric2Registry, Configuration configuration) {
    RegexMetricFilter regexFilter = new RegexMetricFilter(getExcludedMetricsPatterns());
    MetricFilter allFilters = new OrMetricFilter(regexFilter, new MetricsWithCountFilter());
    MetricRegistry metricRegistry = metric2Registry.getMetricRegistry();

    reportToGraphite(
        metricRegistry,
        getGraphiteReportingInterval(),
        Stagemonitor.getMeasurementSession(),
        allFilters);
    reportToInfluxDb(
        metric2Registry,
        reportingIntervalInfluxDb.getValue(),
        Stagemonitor.getMeasurementSession());
    reportToElasticsearch(
        metric2Registry,
        reportingIntervalElasticsearch.getValue(),
        Stagemonitor.getMeasurementSession(),
        configuration.getConfig(CorePlugin.class));

    List<ScheduledReporter> onShutdownReporters = new LinkedList<ScheduledReporter>();
    onShutdownReporters.add(
        new SimpleElasticsearchReporter(
            getElasticsearchClient(), metricRegistry, "simple-es-reporter", allFilters));

    reportToConsole(metricRegistry, getConsoleReportingInterval(), allFilters, onShutdownReporters);
    registerAggregationReporter(
        metricRegistry, allFilters, onShutdownReporters, getAggregationReportingInterval());
    if (reportToJMX()) {
      reportToJMX(metricRegistry, allFilters);
    }
  }

  private void registerAggregationReporter(
      MetricRegistry metricRegistry,
      MetricFilter allFilters,
      List<ScheduledReporter> onShutdownReporters,
      long reportingInterval) {
    if (reportingInterval > 0) {
      aggregationReporter =
          new MetricsAggregationReporter(metricRegistry, allFilters, onShutdownReporters);
      aggregationReporter.start(reportingInterval, TimeUnit.SECONDS);
      aggregationReporter.report();
      reporters.add(aggregationReporter);
    }
  }

  private void reportToGraphite(
      MetricRegistry metricRegistry,
      long reportingInterval,
      MeasurementSession measurementSession,
      MetricFilter filter) {
    String graphiteHostName = getGraphiteHostName();
    if (isReportToGraphite()) {
      final GraphiteReporter graphiteReporter =
          GraphiteReporter.forRegistry(metricRegistry)
              .prefixedWith(getGraphitePrefix(measurementSession))
              .convertRatesTo(TimeUnit.SECONDS)
              .convertDurationsTo(TimeUnit.MILLISECONDS)
              .filter(filter)
              .build(new Graphite(new InetSocketAddress(graphiteHostName, getGraphitePort())));

      graphiteReporter.start(reportingInterval, TimeUnit.SECONDS);
      reporters.add(graphiteReporter);
    }
  }

  private void reportToInfluxDb(
      Metric2Registry metricRegistry,
      int reportingInterval,
      MeasurementSession measurementSession) {

    if (StringUtils.isNotEmpty(getInfluxDbUrl()) && reportingInterval > 0) {
      logger.info(
          "Sending metrics to InfluxDB ({}) every {}s", getInfluxDbUrl(), reportingInterval);
      final InfluxDbReporter reporter =
          new InfluxDbReporter(
              metricRegistry,
              Metric2Filter.ALL,
              TimeUnit.SECONDS,
              TimeUnit.MILLISECONDS,
              measurementSession.asMap(),
              new HttpClient(),
              this);

      reporter.start(reportingInterval, TimeUnit.SECONDS);
      reporters.add(reporter);
    } else {
      logger.info(
          "Not sending metrics to InfluxDB (url={}, interval={}s)",
          getInfluxDbUrl(),
          reportingInterval);
    }
  }

  private void reportToElasticsearch(
      Metric2Registry metricRegistry,
      int reportingInterval,
      final MeasurementSession measurementSession,
      CorePlugin corePlugin) {
    if (isReportToElasticsearch()) {
      elasticsearchClient.sendBulkAsync("KibanaConfig.bulk");
      logger.info(
          "Sending metrics to Elasticsearch ({}) every {}s",
          getElasticsearchUrl(),
          reportingInterval);
      final String mappingJson =
          ElasticsearchClient.requireBoxTypeHotIfHotColdAritectureActive(
              "stagemonitor-elasticsearch-metrics-index-template.json",
              corePlugin.moveToColdNodesAfterDays.getValue());
      elasticsearchClient.sendMappingTemplateAsync(mappingJson, "stagemonitor-metrics");
      final ElasticsearchReporter reporter =
          new ElasticsearchReporter(
              metricRegistry,
              Metric2Filter.ALL,
              TimeUnit.SECONDS,
              TimeUnit.MILLISECONDS,
              measurementSession.asMap(),
              new HttpClient(),
              this);

      reporter.start(reportingInterval, TimeUnit.SECONDS);
      reporters.add(reporter);
      elasticsearchClient.scheduleIndexManagement(
          ElasticsearchReporter.STAGEMONITOR_METRICS_INDEX_PREFIX,
          moveToColdNodesAfterDays.getValue(),
          deleteElasticsearchMetricsAfterDays.getValue());
    } else {
      logger.info(
          "Not sending metrics to Elasticsearch (url={}, interval={}s)",
          getElasticsearchUrl(),
          reportingInterval);
    }
  }

  private String getGraphitePrefix(MeasurementSession measurementSession) {
    return name(
        "stagemonitor",
        sanitizeGraphiteMetricSegment(measurementSession.getApplicationName()),
        sanitizeGraphiteMetricSegment(measurementSession.getInstanceName()),
        sanitizeGraphiteMetricSegment(measurementSession.getHostName()));
  }

  private void reportToConsole(
      MetricRegistry metricRegistry,
      long reportingInterval,
      MetricFilter filter,
      List<ScheduledReporter> onShutdownReporters) {
    final SortedTableLogReporter reporter =
        SortedTableLogReporter.forRegistry(metricRegistry)
            .convertRatesTo(TimeUnit.SECONDS)
            .convertDurationsTo(TimeUnit.MILLISECONDS)
            .filter(filter)
            .build();
    onShutdownReporters.add(reporter);
    if (reportingInterval > 0) {
      reporter.start(reportingInterval, TimeUnit.SECONDS);
      reporters.add(reporter);
    }
  }

  private void reportToJMX(MetricRegistry metricRegistry, MetricFilter filter) {
    final JmxReporter reporter = JmxReporter.forRegistry(metricRegistry).filter(filter).build();
    reporter.start();
    reporters.add(reporter);
  }

  @Override
  public void onShutDown() {
    if (aggregationReporter != null) {
      logger.info(
          "\n####################################################\n"
              + "## Aggregated report for this measurement session ##\n"
              + "####################################################\n");
      aggregationReporter.onShutDown();
    }

    for (Closeable reporter : reporters) {
      try {
        reporter.close();
      } catch (IOException e) {
        logger.warn(e.getMessage(), e);
      }
    }

    getElasticsearchClient().close();
    getGrafanaClient().close();
  }

  public ElasticsearchClient getElasticsearchClient() {
    if (elasticsearchClient == null) {
      elasticsearchClient = new ElasticsearchClient();
    }
    return elasticsearchClient;
  }

  public GrafanaClient getGrafanaClient() {
    if (grafanaClient == null) {
      grafanaClient = new GrafanaClient(this, new HttpClient());
    }
    return grafanaClient;
  }

  public void setElasticsearchClient(ElasticsearchClient elasticsearchClient) {
    this.elasticsearchClient = elasticsearchClient;
  }

  public boolean isStagemonitorActive() {
    return Stagemonitor.isDisabled() ? false : stagemonitorActive.getValue();
  }

  public boolean isInternalMonitoringActive() {
    return internalMonitoring.getValue();
  }

  public long getConsoleReportingInterval() {
    return reportingIntervalConsole.getValue();
  }

  public long getAggregationReportingInterval() {
    return reportingIntervalAggregation.getValue();
  }

  public boolean reportToJMX() {
    return reportingJmx.getValue();
  }

  public int getGraphiteReportingInterval() {
    return reportingIntervalGraphite.getValue();
  }

  public String getGraphiteHostName() {
    return graphiteHostName.getValue();
  }

  public int getGraphitePort() {
    return graphitePort.getValue();
  }

  public String getApplicationName() {
    return applicationName.getValue();
  }

  public String getInstanceName() {
    return instanceName.getValue();
  }

  public String getElasticsearchUrl() {
    return removeTrailingSlash(elasticsearchUrl.getValue());
  }

  private String removeTrailingSlash(String url) {
    if (url != null && url.endsWith("/")) {
      return url.substring(0, url.length() - 1);
    }
    return url;
  }

  public Collection<String> getElasticsearchConfigurationSourceProfiles() {
    return elasticsearchConfigurationSourceProfiles.getValue();
  }

  public boolean isDeactivateStagemonitorIfEsConfigSourceIsDown() {
    return deactivateStagemonitorIfEsConfigSourceIsDown.getValue();
  }

  public Collection<Pattern> getExcludedMetricsPatterns() {
    return excludedMetrics.getValue();
  }

  public Collection<String> getDisabledPlugins() {
    return disabledPlugins.getValue();
  }

  public Integer getReloadConfigurationInterval() {
    return reloadConfigurationInterval.getValue();
  }

  public Collection<String> getExcludeContaining() {
    return excludeContaining.getValue();
  }

  public Collection<String> getIncludePackages() {
    return includePackages.getValue();
  }

  public Collection<String> getExcludePackages() {
    return excludePackages.getValue();
  }

  public boolean isAttachAgentAtRuntime() {
    return attachAgentAtRuntime.getValue();
  }

  public Collection<String> getExcludedInstrumenters() {
    return excludedInstrumenters.getValue();
  }

  public String getInfluxDbUrl() {
    return removeTrailingSlash(influxDbUrl.getValue());
  }

  public String getInfluxDbDb() {
    return influxDbDb.getValue();
  }

  public boolean isReportToElasticsearch() {
    return StringUtils.isNotEmpty(getElasticsearchUrl())
        && reportingIntervalElasticsearch.getValue() > 0;
  }

  public boolean isReportToGraphite() {
    return StringUtils.isNotEmpty(getGraphiteHostName());
  }

  public String getGrafanaUrl() {
    return removeTrailingSlash(grafanaUrl.getValue());
  }

  public String getGrafanaApiKey() {
    return grafanaApiKey.getValue();
  }

  public int getThreadPoolQueueCapacityLimit() {
    return threadPoolQueueCapacityLimit.getValue();
  }

  public IndexSelector getIndexSelector() {
    return indexSelector;
  }

  public int getElasticsearchReportingInterval() {
    return reportingIntervalElasticsearch.getValue();
  }

  public Integer getMoveToColdNodesAfterDays() {
    return moveToColdNodesAfterDays.getValue();
  }
}