All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.datastax.driver.core.InsightsClient Maven / Gradle / Ivy

/*
 * 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.getEndPoint());
              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.getEndPoint());
              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.getEndPoint().resolve()),
          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().getEndPoint().resolve())
        : null;
  }

  private ControlConnection getControlConnection() {
    return cluster.getManager().getControlConnection();
  }

  private String getLocalAddress() {
    return getControlConnection().connectedHost() != null
        ? AddressFormatter.nullSafeToString(
            getControlConnection().connectedHost().getEndPoint().resolve().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));
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy