package org.stagemonitor.core;
import com.codahale.metrics.Clock;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.JmxReporter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.graphite.Graphite;
import com.codahale.metrics.graphite.GraphiteReporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stagemonitor.configuration.ConfigurationRegistry;
import org.stagemonitor.configuration.ConfigurationOption;
import org.stagemonitor.configuration.converter.ListValueConverter;
import org.stagemonitor.configuration.converter.SetValueConverter;
import org.stagemonitor.core.elasticsearch.ElasticsearchClient;
import org.stagemonitor.core.elasticsearch.IndexSelector;
import org.stagemonitor.core.grafana.GrafanaClient;
import org.stagemonitor.core.metrics.MetricNameFilter;
import org.stagemonitor.core.metrics.MetricsAggregationReporter;
import org.stagemonitor.core.metrics.MetricsWithCountFilter;
import org.stagemonitor.core.metrics.SortedTableLogReporter;
import org.stagemonitor.core.metrics.metrics2.AndMetric2Filter;
import org.stagemonitor.core.metrics.metrics2.ElasticsearchReporter;
import org.stagemonitor.core.metrics.metrics2.InfluxDbReporter;
import org.stagemonitor.core.metrics.metrics2.Metric2Filter;
import org.stagemonitor.core.metrics.metrics2.Metric2Registry;
import org.stagemonitor.core.metrics.metrics2.MetricName;
import org.stagemonitor.core.metrics.metrics2.MetricNameValueConverter;
import org.stagemonitor.core.metrics.metrics2.ScheduledMetrics2Reporter;
import org.stagemonitor.core.util.HttpClient;
import org.stagemonitor.util.IOUtils;
import org.stagemonitor.util.StringUtils;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static com.codahale.metrics.MetricRegistry.name;
import static org.stagemonitor.core.util.GraphiteSanitizer.sanitizeGraphiteMetricSegment;
/**
* 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 static final String ELASTICSEARCH = "elasticsearch";
private static final String METRICS_STORE = "metrics-store";
private final Logger logger = LoggerFactory.getLogger(getClass());
private 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(false)
.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<Boolean> onlyLogElasticsearchMetricReports = ConfigurationOption.booleanOption()
.key("stagemonitor.reporting.elasticsearch.onlyLogElasticsearchMetricReports")
.dynamic(false)
.label("Only log Elasticsearch metric reports")
.description(String.format("If set to true, the metrics won't be reported to elasticsearch but instead logged in bulk format. " +
"The name of the logger is %s. That way you can redirect the reporting to a separate log file and use logstash or a " +
"different external process to send the metrics to elasticsearch.", ElasticsearchReporter.ES_METRICS_LOGGER))
.defaultValue(false)
.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<Integer> numberOfReplicas = ConfigurationOption.integerOption()
.key("stagemonitor.elasticsearch.numberOfReplicas")
.dynamic(false)
.label("Number of ES Replicas")
.description("Sets the number of replicas of the Elasticsearch index templates.")
.defaultValue(null)
.tags(METRICS_STORE, ELASTICSEARCH)
.configurationCategory(CORE_PLUGIN_NAME)
.build();
private final ConfigurationOption<Integer> numberOfShards = ConfigurationOption.integerOption()
.key("stagemonitor.elasticsearch.numberOfShards")
.dynamic(false)
.label("Number of ES Shards")
.description("Sets the number of shards of the Elasticsearch index templates.")
.defaultValue(null)
.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> hostName = ConfigurationOption.stringOption()
.key("stagemonitor.hostName")
.dynamic(false)
.label("Host name")
.description("The host name.\n" +
"If this property is not set, the host name will default to resolving the host name for localhost, " +
"if this fails it will be loaded from the environment, either from COMPUTERNAME or HOSTNAME.")
.defaultValue(getNameOfLocalHost())
.configurationCategory(CORE_PLUGIN_NAME)
.build();
private final ConfigurationOption<List<String>> elasticsearchUrls = ConfigurationOption.builder(ListValueConverter.STRINGS_VALUE_CONVERTER, List.class)
.key("stagemonitor.elasticsearch.url")
.dynamic(true)
.label("Elasticsearch URL")
.description("A comma separated list of the Elasticsearch URLs that store spans and metrics. " +
"If your ES cluster is secured with basic authentication, you can use urls like https://user:password@example.com.")
.defaultValue(Collections.<String>emptyList())
.configurationCategory(CORE_PLUGIN_NAME)
.tags(ELASTICSEARCH)
.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<MetricName>> excludedMetrics = ConfigurationOption
.builder(new SetValueConverter<MetricName>(new MetricNameValueConverter()), Collection.class)
.key("stagemonitor.metrics.excluded.pattern")
.dynamic(false)
.label("Excluded metric names")
.description("A comma separated list of metric names that should not be collected.")
.defaultValue(Collections.<MetricName>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(Collections.<String>emptySet())
.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. " +
"If this property is required if you want to use the profiler, the @MonitorRequests annotation, the " +
"com.codahale.metrics.annotation.* annotations or similar features. " +
"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>> exportClassesWithName = ConfigurationOption.stringsOption()
.key("stagemonitor.instrument.exportGeneratedClassesWithName")
.dynamic(false)
.label("Export generated classes with name")
.description("A list of the fully qualified class names which should be exported to the file system after they have been " +
"modified by Byte Buddy. This option is useful to debug problems inside the generated class. " +
"Classes are exported to a temporary directory. The logs contain the information where the files " +
"are stored.")
.defaultValue(Collections.<String>emptySet())
.configurationCategory(CORE_PLUGIN_NAME)
.build();
private final ConfigurationOption<Boolean> debugInstrumentation = ConfigurationOption.booleanOption()
.key("stagemonitor.instrument.debug")
.dynamic(false)
.label("Debug instrumentation")
.description("Set to true to log additional information and warnings during the instrumentation process.")
.defaultValue(false)
.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 StagemonitorByteBuddyTransformers 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 spans 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 final ConfigurationOption<String> metricsIndexTemplate = ConfigurationOption.stringOption()
.key("stagemonitor.reporting.elasticsearch.metricsIndexTemplate")
.dynamic(true)
.label("ES Metrics Index Template")
.description("The classpath location of the index template that is used for the stagemonitor-metrics-* indices. " +
"By specifying the location to your own template, you can fully customize the index template.")
.defaultValue("stagemonitor-elasticsearch-metrics-index-template.json")
.configurationCategory(CORE_PLUGIN_NAME)
.tags(METRICS_STORE, ELASTICSEARCH)
.build();
private final ConfigurationOption<Integer> elasticsearchAvailabilityCheckPeriodSec = ConfigurationOption.integerOption()
.key("stagemonitor.elasticsearch.availabilityCheckPeriodSec")
.dynamic(false)
.label("Elasticsearch availability check period (sec)")
.description("When set to a value > 0 stagemonitor periodically checks if Elasticsearch is available. " +
"When not available, stagemonitor won't try send documents to Elasticsearch which would " +
"fail anyway. This reduces heap usage as the documents won't be queued up. " +
"It also avoids the logging of warnings when the queue is filled up to the limit (see '" + POOLS_QUEUE_CAPACITY_LIMIT_KEY + "')")
.defaultValue(5)
.configurationCategory(CORE_PLUGIN_NAME)
.tags("elasticsearch", "advanced")
.build();
private MetricsAggregationReporter aggregationReporter;
private List<Closeable> reporters = new CopyOnWriteArrayList<Closeable>();
private ElasticsearchClient elasticsearchClient;
private GrafanaClient grafanaClient;
private IndexSelector indexSelector = new IndexSelector(new Clock.UserTimeClock());
private Metric2Registry metricRegistry;
private AtomicInteger accessesToElasticsearchUrl = new AtomicInteger();
public CorePlugin() {
}
public CorePlugin(ElasticsearchClient elasticsearchClient) {
this.elasticsearchClient = elasticsearchClient;
}
@Override
public void initializePlugin(InitArguments initArguments) {
this.metricRegistry = initArguments.getMetricRegistry();
final Integer reloadInterval = getReloadConfigurationInterval();
if (reloadInterval > 0) {
initArguments.getConfiguration().scheduleReloadAtRate(reloadInterval, TimeUnit.SECONDS);
}
initArguments.getMetricRegistry().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());
grafanaClient.sendGrafanaDashboardAsync("grafana/ElasticsearchCustomMetricsDashboard.json");
}
registerReporters(initArguments.getMetricRegistry(), initArguments.getConfiguration(), initArguments.getMeasurementSession());
}
void registerReporters(Metric2Registry metric2Registry, ConfigurationRegistry configuration, MeasurementSession measurementSession) {
Metric2Filter regexFilter = Metric2Filter.ALL;
Collection<MetricName> excludedMetricsPatterns = getExcludedMetricsPatterns();
if (!excludedMetricsPatterns.isEmpty()) {
regexFilter = MetricNameFilter.excludePatterns(excludedMetricsPatterns);
}
Metric2Filter allFilters = new AndMetric2Filter(regexFilter, new MetricsWithCountFilter());
MetricRegistry legacyMetricRegistry = metric2Registry.getMetricRegistry();
reportToGraphite(legacyMetricRegistry, getGraphiteReportingInterval(), measurementSession);
reportToInfluxDb(metric2Registry, reportingIntervalInfluxDb.getValue(),
measurementSession);
reportToElasticsearch(metric2Registry, reportingIntervalElasticsearch.getValue(), measurementSession);
List<ScheduledMetrics2Reporter> onShutdownReporters = new LinkedList<ScheduledMetrics2Reporter>();
onShutdownReporters.add(reportToConsole(metric2Registry, getConsoleReportingInterval(), allFilters));
registerAggregationReporter(metric2Registry, onShutdownReporters, getAggregationReportingInterval());
if (configuration.getConfig(CorePlugin.class).isReportToJMX()) {
// Because JMX reporter is on registration and not periodic only the
// regex filter is applicable here (not filtering metrics by count)
reportToJMX(legacyMetricRegistry);
}
}
private void registerAggregationReporter(Metric2Registry metricRegistry,
List<ScheduledMetrics2Reporter> onShutdownReporters, long reportingInterval) {
if (reportingInterval > 0) {
aggregationReporter = MetricsAggregationReporter.forRegistry(metricRegistry).onShutdownReporters(onShutdownReporters).build();
aggregationReporter.start(reportingInterval, TimeUnit.SECONDS);
aggregationReporter.report();
reporters.add(aggregationReporter);
}
}
private void reportToGraphite(MetricRegistry metricRegistry, long reportingInterval, MeasurementSession measurementSession) {
if (isReportToGraphite()) {
final GraphiteReporter graphiteReporter = GraphiteReporter.forRegistry(metricRegistry)
.prefixedWith(getGraphitePrefix(measurementSession))
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build(new Graphite(new InetSocketAddress(getGraphiteHostName(), 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 = InfluxDbReporter.forRegistry(metricRegistry, this)
.globalTags(measurementSession.asMap())
.build();
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) {
if (isReportToElasticsearch()) {
elasticsearchClient.sendClassPathRessourceBulkAsync("stagemonitor-metrics-kibana-index-pattern.bulk");
logger.info("Sending metrics to Elasticsearch ({}) every {}s", getElasticsearchUrls(), reportingInterval);
final String mappingJson = ElasticsearchClient.modifyIndexTemplate(
metricsIndexTemplate.getValue(), moveToColdNodesAfterDays.getValue(), getNumberOfReplicas(), getNumberOfShards());
elasticsearchClient.sendMappingTemplateAsync(mappingJson, "stagemonitor-metrics");
elasticsearchClient.scheduleIndexManagement(ElasticsearchReporter.STAGEMONITOR_METRICS_INDEX_PREFIX,
moveToColdNodesAfterDays.getValue(), deleteElasticsearchMetricsAfterDays.getValue());
}
if (isReportToElasticsearch() || isOnlyLogElasticsearchMetricReports()) {
final ElasticsearchReporter reporter = ElasticsearchReporter.forRegistry(metricRegistry, this)
.globalTags(measurementSession.asMap())
.build();
reporter.start(reportingInterval, TimeUnit.SECONDS);
reporters.add(reporter);
} else {
logger.info("Not sending metrics to Elasticsearch (url={}, interval={}s)", getElasticsearchUrls(), reportingInterval);
}
}
private String getGraphitePrefix(MeasurementSession measurementSession) {
return name("stagemonitor",
sanitizeGraphiteMetricSegment(measurementSession.getApplicationName()),
sanitizeGraphiteMetricSegment(measurementSession.getInstanceName()),
sanitizeGraphiteMetricSegment(measurementSession.getHostName()));
}
private SortedTableLogReporter reportToConsole(Metric2Registry metric2Registry, long reportingInterval, Metric2Filter filter) {
final SortedTableLogReporter reporter = SortedTableLogReporter.forRegistry(metric2Registry)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.filter(filter)
.build();
if (reportingInterval > 0) {
reporter.start(reportingInterval, TimeUnit.SECONDS);
reporters.add(reporter);
}
return reporter;
}
private void reportToJMX(MetricRegistry metricRegistry) {
final JmxReporter reporter = JmxReporter.forRegistry(metricRegistry).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);
}
}
if (elasticsearchClient != null) {
elasticsearchClient.close();
}
if (grafanaClient != null) {
grafanaClient.close();
}
}
public MeasurementSession getMeasurementSession() {
return Stagemonitor.getMeasurementSession();
}
public Metric2Registry getMetricRegistry() {
return metricRegistry;
}
public ElasticsearchClient getElasticsearchClient() {
if (elasticsearchClient == null) {
elasticsearchClient = new ElasticsearchClient(this, new HttpClient(), elasticsearchAvailabilityCheckPeriodSec.getValue());
}
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 static String getNameOfLocalHost() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (Exception e) {
return getHostNameFromEnv();
}
}
static String getHostNameFromEnv() {
// try environment properties.
String host = System.getenv("COMPUTERNAME");
if (host != null) {
return host;
}
host = System.getenv("HOSTNAME");
if (host != null) {
return host;
}
return null;
}
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 isReportToJMX() {
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 getHostName() {
return hostName.getValue();
}
/**
* Cycles through all provided Elasticsearch URLs and returns one
*
* @return One of the provided Elasticsearch URLs
*/
public String getElasticsearchUrl() {
final List<String> urls = elasticsearchUrls.getValue();
if (urls.isEmpty()) {
return null;
}
final int index = accessesToElasticsearchUrl.getAndIncrement() % urls.size();
return StringUtils.removeTrailingSlash(urls.get(index));
}
public Collection<String> getElasticsearchUrls() {
return elasticsearchUrls.getValue();
}
public Collection<String> getElasticsearchConfigurationSourceProfiles() {
return elasticsearchConfigurationSourceProfiles.getValue();
}
public boolean isDeactivateStagemonitorIfEsConfigSourceIsDown() {
return deactivateStagemonitorIfEsConfigSourceIsDown.getValue();
}
public Collection<MetricName> 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 StringUtils.removeTrailingSlash(influxDbUrl.getValue());
}
public String getInfluxDbDb() {
return influxDbDb.getValue();
}
public boolean isReportToElasticsearch() {
return !getElasticsearchUrls().isEmpty() && reportingIntervalElasticsearch.getValue() > 0;
}
public boolean isReportToGraphite() {
return StringUtils.isNotEmpty(getGraphiteHostName());
}
public String getGrafanaUrl() {
return StringUtils.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();
}
public boolean isOnlyLogElasticsearchMetricReports() {
return onlyLogElasticsearchMetricReports.getValue();
}
public boolean isDebugInstrumentation() {
return debugInstrumentation.getValue();
}
public Collection<String> getExportClassesWithName() {
return exportClassesWithName.getValue();
}
public Integer getNumberOfReplicas() {
return numberOfReplicas.getValue();
}
public Integer getNumberOfShards() {
return numberOfShards.getValue();
}
List<Closeable> getReporters() {
return reporters;
}
}