/* * -----------------------------------------------------------------------\ * PerfCake *   * Copyright (C) 2010 - 2016 the original author or authors. *   * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * -----------------------------------------------------------------------/ */ package org.perfcake.reporting.destination; import org.perfcake.PerfCakeConst; import org.perfcake.PerfCakeException; import org.perfcake.common.PeriodType; import org.perfcake.reporting.Measurement; import org.perfcake.reporting.Quantity; import org.perfcake.reporting.ReportingException; import org.perfcake.util.SslSocketFactoryFactory; import org.perfcake.util.StringUtil; import org.perfcake.util.properties.MandatoryProperty; import com.google.gson.JsonArray; import com.squareup.okhttp.OkHttpClient; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.influxdb.InfluxDB; import org.influxdb.InfluxDBFactory; import org.influxdb.dto.Point; import retrofit.client.OkClient; import java.util.Arrays; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLSocketFactory; /** * Writes the resulting data to InfluxDb using a simple HTTP REST client. * The reported data have information about the test progress (time in milliseconds since start, percentage and iteration), * real time of each result, and the complete results map. Quantities are stored without their unit. * Supports SSL connection. The database is by default created on connection. * * @author <a href="mailto:marvenec@gmail.com">Martin Večeřa</a> */ public class InfluxDbDestination extends AbstractDestination { private static final Logger log = LogManager.getLogger(InfluxDbDestination.class); /** * InfluxDb server including protocol and port number. Supports SSL. */ @MandatoryProperty private String serverUrl = ""; /** * Name of InfluxDb database. */ private String database = "perfcake"; /** * Creates the database when connected. Requires admin privileges in InfluxDb. */ private boolean createDatabase = true; /** * Name of the measurement in InfluxDb, serves as a database table. */ private String measurement = "results"; /** * InfluxDb user name. */ @MandatoryProperty private String userName = "admin"; /** * InfluxDb password. */ @MandatoryProperty private String password = "admin"; /** * Comma separated list of tags to be added to results. */ private String tags = ""; /** * SSL key store location. */ private String keyStore; /** * SSL key store password. */ private String keyStorePassword; /** * SSL trust store location. */ private String trustStore; /** * SSL trust store password. */ private String trustStorePassword; /** * Initialized SSL factory. */ private SSLSocketFactory sslFactory = null; /** * Cached array with tags. */ private JsonArray tagsArray = new JsonArray(); /** * InfluxDb client. */ private InfluxDB influxDb = null; @Override public void open() { Arrays.stream(tags.split(",")).map(StringUtil::trim).forEach(tagsArray::add); try { if ((keyStore != null && !"".equals(keyStore)) || (trustStore != null && !"".equals(trustStore))) { sslFactory = SslSocketFactoryFactory.newSslSocketFactory(keyStore, keyStorePassword, trustStore, trustStorePassword); } } catch (PerfCakeException e) { log.warn("Unable to initialize SSL socket factory: ", e); } try { if (sslFactory != null) { final OkHttpClient client = new OkHttpClient(); client.setSslSocketFactory(sslFactory); client.setHostnameVerifier((hostname, session) -> true); influxDb = InfluxDBFactory.connect(serverUrl, userName, password, new OkClient(client)); } else { influxDb = InfluxDBFactory.connect(serverUrl, userName, password); } if (createDatabase) { influxDb.createDatabase(database); } influxDb.enableBatch(100, 500, TimeUnit.MILLISECONDS); } catch (RuntimeException rte) { influxDb = null; log.error("Unable to connect to InfluxDb: ", rte); } } @Override public void close() { if (influxDb != null) { influxDb.disableBatch(); // flushes batch } } @Override public void report(final Measurement measurement) throws ReportingException { if (influxDb == null) { throw new ReportingException("Not connected to InfluxDb."); } Point.Builder pBuilder = Point.measurement(this.measurement).time(System.currentTimeMillis(), TimeUnit.MILLISECONDS); pBuilder.addField(PeriodType.TIME.toString().toLowerCase(), measurement.getTime()); pBuilder.addField(PeriodType.ITERATION.toString().toLowerCase(), measurement.getIteration()); pBuilder.addField(PeriodType.PERCENTAGE.toString().toLowerCase(), measurement.getPercentage()); pBuilder.addField(PerfCakeConst.TAGS_TAG, tagsArray.toString()); measurement.getAll().forEach((k, v) -> { if (v instanceof Number) { pBuilder.addField(k, (Number) v); } else if (v instanceof Quantity) { pBuilder.addField(k, ((Quantity) v).getNumber()); } else { pBuilder.addField(k, v.toString()); } }); influxDb.write(database, "default", pBuilder.build()); } /** * Gets InfluxDb server including protocol and port number. Supports SSL. * * @return InfluxDb server including protocol and port number. */ public String getServerUrl() { return serverUrl; } /** * Sets InfluxDb server including protocol and port number. Supports SSL. * * @param serverUrl * InfluxDb server including protocol and port number. * @return Instance of this to support fluent API. */ public InfluxDbDestination setServerUrl(final String serverUrl) { this.serverUrl = serverUrl; return this; } /** * Gets the name of InfluxDb database. * * @return The name of InfluxDb database. */ public String getDatabase() { return database; } /** * Sets the name of InfluxDb database. * * @param database * The name of InfluxDb database. * @return Instance of this to support fluent API. */ public InfluxDbDestination setDatabase(final String database) { this.database = database; return this; } /** * Should the database be created upon connecting to InfluxDb? Defaults to true. * * @return True if and only if the database should be created when connected to InfluxDb. */ public boolean isCreateDatabase() { return createDatabase; } /** * Sets whether the database should be created upon connecting to InfluxDb. Dafaults to true. * * @param createDatabase * True if and only if the database should be created when connected to InfluxDb. * @return Instance of this to support fluent API. */ public InfluxDbDestination setCreateDatabase(final boolean createDatabase) { this.createDatabase = createDatabase; return this; } /** * Gets the name of the measurement in InfluxDb, serves as a database table. * * @return The name of the measurement in InfluxDb, serves as a database table. */ public String getMeasurement() { return measurement; } /** * Sets the name of the measurement in InfluxDb, serves as a database table. * * @param measurement * The name of the measurement in InfluxDb, serves as a database table. * @return Instance of this to support fluent API. */ public InfluxDbDestination setMeasurement(final String measurement) { this.measurement = measurement; return this; } /** * Gets the name of InfluxDb user. * * @return The name of InfluxDb user. */ public String getUserName() { return userName; } /** * Sets the name of InfluxDb user. * * @param userName * The name of InfluxDb user. * @return Instance of this to support fluent API. */ public InfluxDbDestination setUserName(final String userName) { this.userName = userName; return this; } /** * Gets the InfluxDb user password. * * @return The InfluxDb user password. */ public String getPassword() { return password; } /** * Sets the InfluxDb user password. * * @param password * The InfluxDb user password. * @return Instance of this to support fluent API. */ public InfluxDbDestination setPassword(final String password) { this.password = password; return this; } /** * Gets a comma separated list of tags to be added to results. * * @return The comma separated list of tags to be added to results. */ public String getTags() { return tags; } /** * Sets a comma separated list of tags to be added to results. * * @param tags * The comma separated list of tags to be added to results. * @return Instance of this to support fluent API. */ public InfluxDbDestination setTags(final String tags) { this.tags = tags; return this; } /** * Gets the SSL key store location. * * @return The SSL key store location. */ public String getKeyStore() { return keyStore; } /** * Sets the SSL key store location. * * @param keyStore * The SSL key store location. * @return Instance of this to support fluent API. */ public InfluxDbDestination setKeyStore(final String keyStore) { this.keyStore = keyStore; return this; } /** * Gets the SSL key store password. * * @return The SSL key store password. */ public String getKeyStorePassword() { return keyStorePassword; } /** * Sets the SSL key store password. * * @param keyStorePassword * The SSL key store password. * @return Instance of this to support fluent API. */ public InfluxDbDestination setKeyStorePassword(final String keyStorePassword) { this.keyStorePassword = keyStorePassword; return this; } /** * Gets the SSL trust store location. * * @return The SSL trust store location. */ public String getTrustStore() { return trustStore; } /** * Sets the SSL trust store location. * * @param trustStore * The SSL trust store location. * @return Instance of this to support fluent API. */ public InfluxDbDestination setTrustStore(final String trustStore) { this.trustStore = trustStore; return this; } /** * Gets the SSL trust store password. * * @return The SSL trust store password. */ public String getTrustStorePassword() { return trustStorePassword; } /** * Sets the SSL trust store password. * * @param trustStorePassword * The SSL trust store password. * @return Instance of this to support fluent API. */ public InfluxDbDestination setTrustStorePassword(final String trustStorePassword) { this.trustStorePassword = trustStorePassword; return this; } }