package rocks.inspectit.server.influx.dao;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import org.influxdb.InfluxDB;
import org.influxdb.dto.Point;
import org.influxdb.dto.Query;
import org.influxdb.dto.QueryResult;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import rocks.inspectit.server.externalservice.IExternalService;
import rocks.inspectit.server.influx.InfluxAvailabilityChecker;
import rocks.inspectit.server.influx.InfluxAvailabilityChecker.InfluxAvailabilityListener;
import rocks.inspectit.server.influx.util.InfluxClientFactory;
import rocks.inspectit.shared.all.cmr.property.spring.PropertyUpdate;
import rocks.inspectit.shared.all.externalservice.ExternalServiceStatus;
import rocks.inspectit.shared.all.externalservice.ExternalServiceType;
import rocks.inspectit.shared.all.spring.logger.Log;
import rocks.inspectit.shared.all.util.ExecutorServiceUtils;
/**
* This DAO encapsulates the HTTP connection to a influx database.
*
* @author Alexander Wert
* @author Marius Oehler
*
*/
@Component
public class InfluxDBDao implements InfluxAvailabilityListener, IExternalService {
/**
* After this duration, the batch have to be flushed.
*/
static final int BATCH_FLUSH_TIMER = 10;
/**
* Size of the {@link Point} buffer.
*/
static final int BATCH_BUFFER_SIZE = 2000;
/**
* Logger for the class.
*/
@Log
Logger log;
/**
* Indicates whether the service is connected to a influxDB instance.
*/
private volatile boolean connected = false;
/**
* Activation state of this service.
*/
@Value("${influxdb.active}")
boolean active;
/**
* Database to use.
*/
@Value("${influxdb.database}")
String database;
/**
* The retention policy to use.
*/
@Value("${influxdb.retentionPolicy}")
String retentionPolicy;
/**
* Configured {@link InfluxDB} instance.
*/
private InfluxDB influxDB;
/**
* {@link ExecutorService} instance.
*/
@Autowired
@Resource(name = "scheduledExecutorService")
private ScheduledExecutorService scheduledExecutorService;
/**
* Factory used to create {@link InfluxDB} clients.
*/
@Autowired
private InfluxClientFactory influxClientFactory;
/**
* The task which connects the InfluxDB.
*/
private final ConnectingTask connectingTask = new ConnectingTask();
/**
* {@link Future} representing the state of {@link #connectingTask}.
*/
private Future<?> connectingFuture;
/**
* Component to monitor the availability of the influxDB.
*/
@Autowired
private InfluxAvailabilityChecker availabilityChecker;
/**
* Inserts the given {@link Point} into the database.
*
* @param dataPoint
* {@link Point} to insert
*/
public void insert(Point dataPoint) {
if ((dataPoint == null) || !isConnected()) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Write data to InfluxDB: {}", dataPoint.toString());
}
influxDB.write(database, retentionPolicy, dataPoint);
}
/**
* Executes the given query on the database.
*
* @param query
* the query to execute
* @return the result of this query
*/
public QueryResult query(String query) {
if ((query == null) || !isConnected()) {
return null;
}
if (log.isDebugEnabled()) {
log.debug("Execute query on InfluxDB: {}", query);
}
return influxDB.query(new Query(query, database));
}
/**
* Indicates whether the influxDB service is connected to a running influxDB instance.
*
* @return true, if connected, otherwise false
*/
public boolean isConnected() {
return getServiceStatus() == ExternalServiceStatus.CONNECTED;
}
/**
* Connects to the InfluxDB if the feature has been enabled.
*/
@PostConstruct
@PropertyUpdate(properties = { "influxdb.host", "influxdb.port", "influxdb.user", "influxdb.passwd", "influxdb.database", "influxdb.active" })
public void propertiesUpdated() {
reset();
if (active) {
connectingFuture = scheduledExecutorService.submit(connectingTask);
}
}
/**
* Resets the service.
*/
private void reset() {
disableBatching();
if ((connectingFuture != null) && !connectingFuture.isDone()) {
connectingFuture.cancel(true);
}
availabilityChecker.deactivate();
connected = false;
}
/**
* Connects to the influxDB instance as specified by the configuration attributes.
*/
private void connect() {
try {
influxDB = influxClientFactory.createClient();
} catch (Exception e) {
if (log.isErrorEnabled()) {
log.error("InfluxDB client could not be created. Please check you configuration settings.", e);
}
return;
}
if (influxDB == null) {
if (log.isErrorEnabled()) {
log.error("InfluxDB client is null. Please check your configuration settings and try again.");
}
} else {
enableBatching();
connected = isAvailable();
if (connected) {
if (log.isInfoEnabled()) {
log.info("|-InfluxDB Service active and connected...");
}
createDatabaseIfNotExistent();
} else {
if (log.isWarnEnabled()) {
log.warn("|-InfluxDB Service was not able to connect! Check connection settings!");
}
}
activateAvailabilityChecker();
}
}
/**
* Checks if the remote influxDB instance is available.
*
* @return Returns true if the influxDB is available.
*/
private boolean isAvailable() {
try {
influxDB.ping();
return true;
} catch (Exception e) {
if (log.isTraceEnabled()) {
log.trace("Ping to the influxDB failed.", e);
}
return false;
}
}
/**
* Creates the configured database in influx if it does not exist yet.
*/
private void createDatabaseIfNotExistent() {
List<String> dbNames = influxDB.describeDatabases();
if (!dbNames.contains(database)) {
influxDB.createDatabase(database);
}
}
/**
* Starts periodic availability checks.
*/
private void activateAvailabilityChecker() {
availabilityChecker.setInflux(influxDB);
availabilityChecker.activate();
}
/**
* Enables batching of the current {@link #influxDB} client.
*/
private void enableBatching() {
if ((null != influxDB) && !influxDB.isBatchEnabled()) {
influxDB.enableBatch(BATCH_BUFFER_SIZE, BATCH_FLUSH_TIMER, TimeUnit.SECONDS);
}
}
/**
* Disables batching of the current {@link #influxDB} client.
*/
private void disableBatching() {
if ((null != influxDB) && influxDB.isBatchEnabled()) {
influxDB.disableBatch();
}
}
/**
* {@inheritDoc}
*/
@Override
public void onDisconnection() {
if (log.isWarnEnabled()) {
log.warn("|-InfluxDB Service not available anymore!");
}
// Batching will be disabled to prevent exceptions during sending of the buffered data
disableBatching();
connected = false;
}
/**
* {@inheritDoc}
*/
@Override
public void onReconnection() {
if (log.isInfoEnabled()) {
log.info("|-InfluxDB Service recovered!");
}
enableBatching();
createDatabaseIfNotExistent();
connected = true;
}
/**
* {@inheritDoc}
*/
@Override
public ExternalServiceStatus getServiceStatus() {
if (!active) {
return ExternalServiceStatus.DISABLED;
}
if (connected) {
return ExternalServiceStatus.CONNECTED;
} else {
return ExternalServiceStatus.DISCONNECTED;
}
}
/**
* {@inheritDoc}
*/
@Override
public ExternalServiceType getServiceType() {
return ExternalServiceType.INFLUXDB;
}
/**
* Shutting down the used executor service.
*/
@PreDestroy
protected void shutDownExecutorService() {
ExecutorServiceUtils.shutdownExecutor(scheduledExecutorService, 5L, TimeUnit.SECONDS);
}
/**
* Executes the connection process to the InfluxDB.
*
* @author Marius Oehler
*
*/
private class ConnectingTask implements Runnable {
/**
* {@inheritDoc}
*/
@Override
public void run() {
connect();
}
}
}