package org.stagemonitor.core.metrics.metrics2;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.Timer;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stagemonitor.core.CorePlugin;
import org.stagemonitor.core.elasticsearch.ElasticsearchClient;
import org.stagemonitor.core.util.HttpClient;
import org.stagemonitor.core.util.JsonUtils;
import org.stagemonitor.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import static org.stagemonitor.core.metrics.metrics2.MetricName.name;
public class ElasticsearchReporter extends ScheduledMetrics2Reporter {
public static final String STAGEMONITOR_METRICS_INDEX_PREFIX = "stagemonitor-metrics-";
public static final String ES_METRICS_LOGGER = "ElasticsearchMetrics";
private static final String METRICS_TYPE = "metrics";
private static final MetricName reportingTimeMetricName = name("reporting_time").tag("reporter", "elasticsearch").build();
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Logger elasticsearchMetricsLogger;
private final Map<String, String> globalTags;
private final CorePlugin corePlugin;
private final HttpClient httpClient;
private final JsonFactory jfactory = new JsonFactory();
private final Metric2RegistryModule metric2RegistryModule;
private final ElasticsearchClient elasticsearchClient;
public static ElasticsearchReporter.Builder forRegistry(Metric2Registry registry, CorePlugin corePlugin) {
return new Builder(registry, corePlugin);
}
private ElasticsearchReporter(Builder builder) {
super(builder);
this.elasticsearchMetricsLogger = builder.getElasticsearchMetricsLogger();
this.globalTags = builder.getGlobalTags();
this.httpClient = builder.getHttpClient();
this.jfactory.setCodec(JsonUtils.getMapper());
this.metric2RegistryModule = new Metric2RegistryModule(builder.getRateUnit(), builder.getDurationUnit());
this.corePlugin = builder.getCorePlugin();
this.elasticsearchClient = corePlugin.getElasticsearchClient();
}
@Override
public void reportMetrics(final Map<MetricName, Gauge> gauges,
final Map<MetricName, Counter> counters,
final Map<MetricName, Histogram> histograms,
final Map<MetricName, Meter> meters,
final Map<MetricName, Timer> timers) {
long timestamp = clock.getTime();
final Timer.Context time = registry.timer(reportingTimeMetricName).time();
final MetricsOutputStreamHandler metricsOutputStreamHandler = new MetricsOutputStreamHandler(gauges, counters, histograms, meters, timers, timestamp);
if (!corePlugin.isOnlyLogElasticsearchMetricReports()) {
if (!elasticsearchClient.isElasticsearchAvailable()) {
return;
}
httpClient.send("POST", corePlugin.getElasticsearchUrl() + "/_bulk", null,
metricsOutputStreamHandler, new ElasticsearchClient.BulkErrorReportingResponseHandler());
} else {
try {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
metricsOutputStreamHandler.withHttpURLConnection(os);
elasticsearchMetricsLogger.info(os.toString("UTF-8"));
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
time.stop();
}
public void reportMetrics(Map<MetricName, Gauge> gauges, Map<MetricName, Counter> counters,
Map<MetricName, Histogram> histograms, final Map<MetricName, Meter> meters,
Map<MetricName, Timer> timers, OutputStream os, byte[] bulkActionBytes, long timestamp) throws IOException {
reportMetric(gauges, timestamp, metric2RegistryModule.getValueWriter(Gauge.class), os, bulkActionBytes);
reportMetric(counters, timestamp, metric2RegistryModule.getValueWriter(Counter.class), os, bulkActionBytes);
reportMetric(histograms, timestamp, metric2RegistryModule.getValueWriter(Histogram.class), os, bulkActionBytes);
reportMetric(meters, timestamp, metric2RegistryModule.getValueWriter(Meter.class), os, bulkActionBytes);
reportMetric(timers, timestamp, metric2RegistryModule.getValueWriter(Timer.class), os, bulkActionBytes);
}
private <T extends Metric> void reportMetric(Map<MetricName, T> metrics, long timestamp, Metric2RegistryModule.ValueWriter<T> valueWriter,
OutputStream os, byte[] bulkActionBytes) throws IOException {
for (Map.Entry<MetricName, T> entry : metrics.entrySet()) {
os.write(bulkActionBytes);
final JsonGenerator jg = jfactory.createGenerator(os);
jg.writeStartObject();
MetricName metricName = entry.getKey();
jg.writeNumberField("@timestamp", timestamp);
jg.writeStringField("name", metricName.getName());
writeMap(jg, metricName.getTags());
writeMap(jg, globalTags);
valueWriter.writeValues(entry.getValue(), jg);
jg.writeEndObject();
jg.flush();
os.write('\n');
}
}
private void writeMap(JsonGenerator jg, Map<String, String> map) throws IOException {
for (Map.Entry<String, String> entry : map.entrySet()) {
jg.writeObjectField(entry.getKey(), entry.getValue());
}
}
private class MetricsOutputStreamHandler implements HttpClient.OutputStreamHandler {
private final Map<MetricName, Gauge> gauges;
private final Map<MetricName, Counter> counters;
private final Map<MetricName, Histogram> histograms;
private final Map<MetricName, Meter> meters;
private final Map<MetricName, Timer> timers;
private final long timestamp;
public MetricsOutputStreamHandler(Map<MetricName, Gauge> gauges, Map<MetricName, Counter> counters, Map<MetricName, Histogram> histograms, Map<MetricName, Meter> meters, Map<MetricName, Timer> timers, long timestamp) {
this.gauges = gauges;
this.counters = counters;
this.histograms = histograms;
this.meters = meters;
this.timers = timers;
this.timestamp = timestamp;
}
@Override
public void withHttpURLConnection(OutputStream os) throws IOException {
String bulkAction = ElasticsearchClient.getBulkHeader("index", STAGEMONITOR_METRICS_INDEX_PREFIX + StringUtils.getLogstashStyleDate(), METRICS_TYPE);
byte[] bulkActionBytes = bulkAction.getBytes("UTF-8");
reportMetrics(gauges, counters, histograms, meters, timers, os, bulkActionBytes, timestamp);
os.close();
}
}
public static class Builder extends ScheduledMetrics2Reporter.Builder<ElasticsearchReporter, Builder> {
private HttpClient httpClient = new HttpClient();
private Logger elasticsearchMetricsLogger = LoggerFactory.getLogger(ES_METRICS_LOGGER);
private final CorePlugin corePlugin;
private Builder(Metric2Registry registry, CorePlugin corePlugin) {
super(registry, "stagemonitor-elasticsearch-reporter");
this.corePlugin = corePlugin;
}
@Override
public ElasticsearchReporter build() {
return new ElasticsearchReporter(this);
}
public HttpClient getHttpClient() {
return httpClient;
}
public Logger getElasticsearchMetricsLogger() {
return elasticsearchMetricsLogger;
}
public Builder httpClient(HttpClient httpClient) {
this.httpClient = httpClient;
return this;
}
public Builder elasticsearchMetricsLogger(Logger elasticsearchMetricsLogger) {
this.elasticsearchMetricsLogger = elasticsearchMetricsLogger;
return this;
}
public CorePlugin getCorePlugin() {
return corePlugin;
}
}
}