com.datastax.driver.core.InsightsClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dse-java-driver-core Show documentation
Show all versions of dse-java-driver-core Show documentation
A driver for DataStax Enterprise (DSE)
and Apache Cassandra 1.2+ clusters that works exclusively with the
Cassandra Query Language version 3 (CQL3) and Cassandra's binary protocol,
supporting DSE-specific features such as geospatial types, DSE Graph and DSE authentication.
/*
* Copyright DataStax, Inc.
*
* This software can be used solely with DataStax Enterprise. Please consult the license at
* http://www.datastax.com/terms/datastax-dse-driver-license-terms
*/
package com.datastax.driver.core;
import static com.datastax.driver.core.InsightsSchema.AuthProviderType;
import static com.datastax.driver.core.InsightsSchema.Insight;
import static com.datastax.driver.core.InsightsSchema.InsightType;
import static com.datastax.driver.core.InsightsSchema.InsightsMetadata;
import static com.datastax.driver.core.InsightsSchema.InsightsStartupData;
import static com.datastax.driver.core.InsightsSchema.InsightsStatusData;
import static com.datastax.driver.core.InsightsSchema.PoolSizeByHostDistance;
import static com.datastax.driver.core.InsightsSchema.SSL;
import static com.datastax.driver.core.InsightsSchema.SessionStateForNode;
import static com.datastax.driver.core.PackageUtil.getNamespace;
import com.datastax.driver.core.exceptions.DriverException;
import com.datastax.driver.core.exceptions.InsightEventFormatException;
import com.datastax.driver.dse.DseCluster;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class InsightsClient {
private static final Logger LOGGER = LoggerFactory.getLogger(InsightsClient.class);
private static final String STARTUP_MESSAGE_NAME = "driver.startup";
private static final String STATUS_MESSAGE_NAME = "driver.status";
private static final String REPORT_INSIGHT_RPC = "CALL InsightsRpc.reportInsight(?)";
private static final Map TAGS = ImmutableMap.of("language", "java");
private static final String STARTUP_VERSION_1_ID = "v1";
private static final String STATUS_VERSION_1_ID = "v1";
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final int MAX_NUMBER_OF_STATUS_ERROR_LOGS = 5;
private static final int MAX_LOG_MESSAGE_LENGTH = 500;
static final String DEFAULT_JAVA_APPLICATION = "Default Java Application";
private final String id = UUID.randomUUID().toString();
private final InsightsConfiguration insightsConfiguration;
private AtomicInteger numberOfStatusEventErrors = new AtomicInteger();
private final Cluster cluster;
private final Session session;
private final Supplier timestampSupplier;
private final PlatformInfoFinder platformInfoFinder;
private final ReconnectionPolicyInfoFinder reconnectionPolicyInfoInfoFinder;
private final ExecutionProfilesInfoFinder executionProfilesInfoFinder;
private final ConfigAntiPatternsFinder configAntiPatternsFinder;
private final DataCentersFinder dataCentersFinder;
private final StackTraceElement[] initCallerStackTrace;
private ScheduledFuture> scheduleInsightsTask;
static InsightsClient createInsightsClient(
Cluster cluster,
InsightsConfiguration insightsConfiguration,
Session session,
StackTraceElement[] initCallerStackTrace) {
SpeculativeExecutionInfoFinder speculativeExecutionInfoFinder =
new SpeculativeExecutionInfoFinder();
DataCentersFinder dataCentersFinder = new DataCentersFinder();
return new InsightsClient(
cluster,
session,
new Supplier() {
@Override
public Long get() {
return new Date().getTime();
}
},
insightsConfiguration,
new PlatformInfoFinder(),
new ReconnectionPolicyInfoFinder(),
new ExecutionProfilesInfoFinder(speculativeExecutionInfoFinder, dataCentersFinder),
new ConfigAntiPatternsFinder(),
dataCentersFinder,
initCallerStackTrace);
}
InsightsClient(
Cluster cluster,
Session session,
Supplier timestampSupplier,
InsightsConfiguration insightsConfiguration,
PlatformInfoFinder platformInfoFinder,
ReconnectionPolicyInfoFinder reconnectionPolicyInfoInfoFinder,
ExecutionProfilesInfoFinder executionProfilesInfoFinder,
ConfigAntiPatternsFinder configAntiPatternsFinder,
DataCentersFinder dataCentersFinder,
StackTraceElement[] initCallerStackTrace) {
this.cluster = cluster;
this.session = session;
this.timestampSupplier = timestampSupplier;
this.insightsConfiguration = insightsConfiguration;
this.platformInfoFinder = platformInfoFinder;
this.reconnectionPolicyInfoInfoFinder = reconnectionPolicyInfoInfoFinder;
this.executionProfilesInfoFinder = executionProfilesInfoFinder;
this.configAntiPatternsFinder = configAntiPatternsFinder;
this.dataCentersFinder = dataCentersFinder;
this.initCallerStackTrace = initCallerStackTrace;
}
ListenableFuture sendStartupMessage() {
if (!shouldSendEvent()) return Futures.immediateCancelledFuture();
final String startupMessage = createStartupMessage();
final Connection.Future write = sendJsonMessage(startupMessage);
if (write == null) {
return Futures.immediateCancelledFuture();
}
return GuavaCompatibility.INSTANCE.transformAsync(
write,
new AsyncFunction() {
@Override
public ListenableFuture apply(Message.Response response) {
if (response.type == Message.Response.Type.ERROR) {
DriverException ex = ((Responses.Error) response).asException(write.getAddress());
LOGGER.debug(
"Error while sending: "
+ trimToFirst500characters(startupMessage)
+ " to insights. Aborting sending all future: "
+ STARTUP_MESSAGE_NAME
+ " events",
ex);
}
return Futures.immediateFuture(null);
}
});
}
void scheduleStatusMessageSend() {
if (!shouldSendEvent()) return;
scheduleInsightsTask =
scheduleInsightsTask(
insightsConfiguration.getStatusEventDelayMillis(),
cluster.getManager().scheduledTasksExecutor,
new Runnable() {
@Override
public void run() {
sendStatusMessage();
}
});
}
void shutdown() {
if (scheduleInsightsTask != null) {
scheduleInsightsTask.cancel(false);
}
}
@VisibleForTesting
ListenableFuture sendStatusMessage() {
final String statusMessage = createStatusMessage();
final Connection.Future write = sendJsonMessage(statusMessage);
if (write == null) {
return Futures.immediateCancelledFuture();
}
return GuavaCompatibility.INSTANCE.transformAsync(
write,
new AsyncFunction() {
@Override
public ListenableFuture apply(Message.Response response) {
if (response.type == Message.Response.Type.ERROR) {
DriverException ex = ((Responses.Error) response).asException(write.getAddress());
if (numberOfStatusEventErrors.getAndIncrement() < MAX_NUMBER_OF_STATUS_ERROR_LOGS)
LOGGER.debug(
"Error while sending: "
+ trimToFirst500characters(statusMessage)
+ " to insights.",
ex);
}
return Futures.immediateFuture(null);
}
});
}
private Connection.Future sendJsonMessage(String jsonMessage) {
Requests.QueryProtocolOptions options = createQueryOptionsWithJson(jsonMessage);
Requests.Query query = new Requests.Query(REPORT_INSIGHT_RPC, options, false);
Connection connection = getControlConnection().connectionRef.get();
if (connection != null) {
LOGGER.trace("Sending message: {}", jsonMessage);
return connection.write(query);
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Could not send message: {} – control connection is not initialized",
trimToFirst500characters(jsonMessage));
}
return null;
}
}
private boolean shouldSendEvent() {
return insightsConfiguration.isMonitorReportingEnabled()
&& InsightsSupportVerifier.supportsInsights(cluster);
}
private Requests.QueryProtocolOptions createQueryOptionsWithJson(String json) {
ByteBuffer startupMessageSerialized =
TypeCodec.varchar().serialize(json, ProtocolVersion.DSE_V2);
return Requests.QueryProtocolOptions.DEFAULT.copy(new ByteBuffer[] {startupMessageSerialized});
}
@VisibleForTesting
String createStartupMessage() {
InsightsMetadata insightsMetadata = createMetadata(STARTUP_MESSAGE_NAME, STARTUP_VERSION_1_ID);
InsightsStartupData data = createStartupData();
try {
return OBJECT_MAPPER.writeValueAsString(
new Insight(insightsMetadata, data));
} catch (JsonProcessingException e) {
throw new InsightEventFormatException("Could not create: " + STARTUP_MESSAGE_NAME, e);
}
}
@VisibleForTesting
String createStatusMessage() {
InsightsMetadata insightsMetadata = createMetadata(STATUS_MESSAGE_NAME, STATUS_VERSION_1_ID);
InsightsStatusData data = createStatusData();
try {
return OBJECT_MAPPER.writeValueAsString(
new Insight(insightsMetadata, data));
} catch (JsonProcessingException e) {
throw new InsightEventFormatException("Could not create: " + STATUS_MESSAGE_NAME, e);
}
}
private InsightsStatusData createStatusData() {
Map startupOptions = cluster.getManager().getStartupOptions();
return InsightsStatusData.builder()
.withClientId(getClientId(startupOptions))
.withSessionId(id)
.withControlConnection(getControlConnectionSocketAddress())
.withConnectedNodes(getConnectedNodes())
.build();
}
private Map getConnectedNodes() {
Map sessionStates =
new LinkedHashMap();
Session.State state = session.getState();
Collection connectedHosts = state.getConnectedHosts();
for (Host h : connectedHosts) {
int inFlightQueries = state.getInFlightQueries(h);
int openConnections = state.getOpenConnections(h);
sessionStates.put(
AddressFormatter.nullSafeToString(h.getSocketAddress()),
new SessionStateForNode(openConnections, inFlightQueries));
}
return sessionStates;
}
private InsightsStartupData createStartupData() {
Map startupOptions = cluster.getManager().getStartupOptions();
return InsightsStartupData.builder()
.withClientId(getClientId(startupOptions))
.withSessionId(id)
.withApplicationName(getApplicationName(startupOptions))
.withApplicationVersion(getApplicationVersion(startupOptions))
.withDriverName(Requests.Startup.DRIVER_NAME)
.withDriverVersion(Cluster.getDriverVersion())
.withContactPoints(cluster.getManager().getResolvedContactPoints())
.withInitialControlConnection(getControlConnectionSocketAddress())
.withProtocolVersion(cluster.getManager().protocolVersion().toInt())
.withLocalAddress(getLocalAddress())
.withExecutionProfiles(executionProfilesInfoFinder.getExecutionProfilesInfo(cluster))
.withPoolSizeByHostDistance(getPoolSizeByHostDistance())
.withHeartbeatInterval(
TimeUnit.SECONDS.toMillis(
cluster.getConfiguration().getPoolingOptions().getHeartbeatIntervalSeconds()))
.withCompression(cluster.getConfiguration().getProtocolOptions().getCompression())
.withReconnectionPolicy(
reconnectionPolicyInfoInfoFinder.getReconnectionPolicyInfo(
cluster.getManager().reconnectionPolicy()))
.withSsl(getSsl())
.withAuthProvider(getAuthProvider())
.withOtherOptions(Collections.emptyMap())
.withPlatformInfo(platformInfoFinder.getInsightsPlatformInfo())
.withConfigAntiPatterns(configAntiPatternsFinder.findAntiPatterns(cluster))
.withPeriodicStatusInterval(
TimeUnit.MILLISECONDS.toSeconds(insightsConfiguration.getStatusEventDelayMillis()))
.withHostName(getLocalHostName())
.withApplicationNameWasGenerated(isApplicationNameGenerated(startupOptions))
.withDataCenters(dataCentersFinder.getDataCenters(cluster))
.build();
}
private AuthProviderType getAuthProvider() {
Class> authProviderClass =
cluster.getConfiguration().getProtocolOptions().getAuthProvider().getClass();
return new AuthProviderType(authProviderClass.getSimpleName(), getNamespace(authProviderClass));
}
private String getClientId(Map startupOptions) {
return startupOptions.get(Cluster.Manager.CLIENT_ID);
}
private boolean isApplicationNameGenerated(Map startupOptions) {
return startupOptions.get(Cluster.Manager.APPLICATION_NAME) == null;
}
private String getApplicationVersion(Map startupOptions) {
String applicationVersion = startupOptions.get(Cluster.Manager.APPLICATION_VERSION);
if (applicationVersion == null) return "";
return applicationVersion;
}
private String getApplicationName(Map startupOptions) {
String applicationName = startupOptions.get(Cluster.Manager.APPLICATION_NAME);
if (applicationName == null || applicationName.isEmpty()) {
return getClusterCreateCaller(initCallerStackTrace);
}
return applicationName;
}
@VisibleForTesting
static String getClusterCreateCaller(StackTraceElement[] stackTrace) {
for (int i = 0; i < stackTrace.length - 1; i++) {
if (isClusterStackTrace(stackTrace[i])) {
int nextElement = i + 1;
if (!isClusterStackTrace(stackTrace[nextElement])) {
return stackTrace[nextElement].getClassName();
}
}
}
return DEFAULT_JAVA_APPLICATION;
}
private static boolean isClusterStackTrace(StackTraceElement stackTraceElement) {
return stackTraceElement.getClassName().equals(Cluster.class.getName())
|| stackTraceElement.getClassName().equals(DelegatingCluster.class.getName())
|| stackTraceElement.getClassName().equals(DseCluster.class.getName());
}
private String getLocalHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
LOGGER.warn("Can not resolve the name of a host, returning null", e);
return null;
}
}
private SSL getSsl() {
boolean isSslDefined = cluster.getConfiguration().getProtocolOptions().getSSLOptions() != null;
return new SSL(isSslDefined);
}
private PoolSizeByHostDistance getPoolSizeByHostDistance() {
return new PoolSizeByHostDistance(
cluster
.getConfiguration()
.getPoolingOptions()
.getCoreConnectionsPerHost(HostDistance.LOCAL),
cluster
.getConfiguration()
.getPoolingOptions()
.getCoreConnectionsPerHost(HostDistance.REMOTE),
cluster
.getConfiguration()
.getPoolingOptions()
.getCoreConnectionsPerHost(HostDistance.IGNORED));
}
private String getControlConnectionSocketAddress() {
return getControlConnection().connectedHost() != null
? AddressFormatter.nullSafeToString(
getControlConnection().connectedHost().getSocketAddress())
: null;
}
private ControlConnection getControlConnection() {
return cluster.getManager().getControlConnection();
}
private String getLocalAddress() {
return getControlConnection().connectedHost() != null
? AddressFormatter.nullSafeToString(getControlConnection().connectedHost().getAddress())
: null;
}
private InsightsMetadata createMetadata(String messageName, String messageVersion) {
return new InsightsMetadata(
messageName, timestampSupplier.get(), TAGS, InsightType.EVENT, messageVersion);
}
@VisibleForTesting
static ScheduledFuture> scheduleInsightsTask(
long statusEventDelayMillis,
ScheduledExecutorService scheduledTasksExecutor,
Runnable runnable) {
long initialDelay =
(long) Math.floor(statusEventDelayMillis - zeroToTenPercentRandom(statusEventDelayMillis));
return scheduledTasksExecutor.scheduleWithFixedDelay(
runnable, initialDelay, statusEventDelayMillis, TimeUnit.MILLISECONDS);
}
private static double zeroToTenPercentRandom(Long statusEventDelayMillis) {
return 0.1 * statusEventDelayMillis * Math.random();
}
private static String trimToFirst500characters(String startupMessage) {
return startupMessage.substring(0, Math.min(startupMessage.length(), MAX_LOG_MESSAGE_LENGTH));
}
}