package org.sef4j.core.helpers.ext.influxdb;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import org.sef4j.core.api.EventSender;
import org.sef4j.core.helpers.senders.http.HttpPostBytesSender;
import org.sef4j.core.helpers.senders.http.HttpPostBytesSenderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* EventSender<String> to concatenate Json fragments as InfluxDB accepts them, then to delegate sending as HTTP POST to InfluxDB<br/>
*
* <PRE>
* +-----------------+ HttpPostBytesSender
* sendEvents(String...) | | sendEvents(byte[]) +------------+ HTTP POST
* -------------------------> | | --------------> | | -----------> InfluxDB Server
* json fragments | | byte[]: +------------+
* "{ ..frag1 }" +-----------------+ "[ {frag1},
* "{ ..frag2 }" {frag2}
* "{ ..frag3 }" ...
* ]"
* </PRE>
*
* <BR/>
* Each String event to send is supposed to be formatted as a Json fragment : "{ ... }"<BR/>
* This class may join multiple fragments in a single call, as"[ frag1, frag2, ... fragN ]"<BR/>
*
* see json formatter helper class, to convert stat value object to Json text
* @see org.sef4j.callstack.export.influxdb.jsonprinters.AbstractInfluxDBValuePrinter
* and all sub-classes: PerfStatsInfluxDBPrinter, BasicTimeStatsLogHistogramInfluxDBPrinter, PendingPerfCountInfluxDBPrinter
*
*/
public class InfluxDBJsonSender implements EventSender<String> {
private static final Logger LOG = LoggerFactory.getLogger(InfluxDBJsonSender.class);
/**
* displayName/url...mainly for display message...
* see real connection implementation in sub-classes
*/
protected String displayUrl;
protected URL seriesURL;
protected EventSender<byte[]> delegateHttpPostBytesSender;
private int warnElapsedThreshold = 20*1000; // 20 seconds
private int countSent = 0;
private int countSentFailed = 0;
private int countSentSlow = 0;
// ------------------------------------------------------------------------
public InfluxDBJsonSender(String url, String dbName, String username, String password) {
this(url, dbName, username, password, HttpPostBytesSenderFactory.DEFAULT_FACTORY);
}
public InfluxDBJsonSender(String url, String dbName, String username, String password,
HttpPostBytesSenderFactory httpPostBytesSenderFactory) {
this.displayUrl = url;
this.seriesURL = influxDBSeriesURL(url, dbName, username, password);
this.delegateHttpPostBytesSender = httpPostBytesSenderFactory.create(displayUrl, seriesURL, HttpPostBytesSender.CONTENT_TYPE_JSON, null);
}
// ------------------------------------------------------------------------
public static URL influxDBSeriesURL(String baseUrl, String dbName, String username, String password) {
try {
return new URL(baseUrl + "/db/" + dbName + "/series?u=" + username + "&p=" + password);
} catch (MalformedURLException ex) {
throw new RuntimeException("Bad url", ex);
}
}
protected void doSendJSonBody(byte[] json) {
delegateHttpPostBytesSender.sendEvent(json);
}
public void sendJSonBody(byte[] json) {
countSent++;
long startTime = System.currentTimeMillis();
try {
doSendJSonBody(json);
long timeMillis = System.currentTimeMillis() - startTime;
if (timeMillis > warnElapsedThreshold) {
countSentSlow++;
}
} catch(RuntimeException ex){
countSentFailed++;
LOG.warn("Failed to send json to InfluxDB '" + displayUrl + "' ... rethrow ex:" + ex.getMessage());
throw ex;
}
}
public void sendEvent(String jsonFragment) {
// wrap body with "[ ... ]"
String text = "[\n" + jsonFragment + "\n]";
sendJSonBody(text.getBytes());
}
public void sendEvents(Collection<String> jsonFragments) {
if (jsonFragments == null || jsonFragments.isEmpty()) return;
// join text with ",\n" + wrap with "[\n" .. "]\n"
// see in jdk8 (or apache commons-lang): ... String.join(",\n", jsonFragments);
ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
OutputStreamWriter out = new OutputStreamWriter(buffer);
try {
out.append("[\n");
Iterator<String> iter = jsonFragments.iterator();
out.append(iter.next());
if (iter.hasNext()) {
for(; iter.hasNext(); ) {
String e = iter.next();
out.append(",\n");
out.append(e);
}
}
out.append("\n]");
out.flush();
} catch(IOException ex) {
// in memory buffer ... IOException should not occurs!
}
byte[] bytes = buffer.toByteArray();
sendJSonBody(bytes);
}
// ------------------------------------------------------------------------
public int getWarnElapsedThreshold() {
return warnElapsedThreshold;
}
public void setWarnElapsedThreshold(int warnElapsedThreshold) {
this.warnElapsedThreshold = warnElapsedThreshold;
}
public int getCountSent() {
return countSent;
}
public void setCountSent(int countSent) {
this.countSent = countSent;
}
public int getCountSentFailed() {
return countSentFailed;
}
public void setCountSentFailed(int countSentFailed) {
this.countSentFailed = countSentFailed;
}
public int getCountSentSlow() {
return countSentSlow;
}
public void setCountSentSlow(int countSentSlow) {
this.countSentSlow = countSentSlow;
}
// ------------------------------------------------------------------------
@Override
public String toString() {
return "InfluxDBJsonSender[url=" + displayUrl + "]";
}
}