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

dev.responsive.kafka.internal.metrics.CassandraMetricsFactory Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2024 Responsive Computing, Inc.
 *
 * This source code is licensed under the Responsive Business Source License Agreement v1.0
 * available at:
 *
 * https://www.responsive.dev/legal/responsive-bsl-10
 *
 * This software requires a valid Commercial License Key for production use. Trial and commercial
 * licenses can be obtained at https://www.responsive.dev
 */

package dev.responsive.kafka.internal.metrics;

import com.datastax.oss.driver.api.core.context.DriverContext;
import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric;
import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric;
import com.datastax.oss.driver.api.core.metrics.Metrics;
import com.datastax.oss.driver.api.core.metrics.NodeMetric;
import com.datastax.oss.driver.api.core.metrics.SessionMetric;
import com.datastax.oss.driver.internal.core.context.InternalDriverContext;
import com.datastax.oss.driver.internal.core.metrics.MetricUpdater;
import com.datastax.oss.driver.internal.core.metrics.MetricsFactory;
import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater;
import com.datastax.oss.driver.internal.core.metrics.NoopNodeMetricUpdater;
import com.datastax.oss.driver.internal.core.metrics.NoopSessionMetricUpdater;
import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.apache.kafka.common.MetricName;
import org.apache.kafka.common.metrics.Sensor;
import org.apache.kafka.common.metrics.stats.CumulativeCount;
import org.apache.kafka.common.metrics.stats.CumulativeSum;

/**
 * This class adapts the metrics generated by the Cassandra client to be exported through
 * Kafka metrics.
 * 

* See here * for a description of metrics usage in the Cassandra driver. *

*

* For a description of each metric exposed by the driver see the * reference documentation. *

*/ public class CassandraMetricsFactory implements MetricsFactory { static final String CASSANDRA_METRICS_GROUP = "cassandra-client"; private final SessionMetricUpdater sessionMetricUpdater; private final NodeMetricUpdater nodeMetricUpdater; public CassandraMetricsFactory(final DriverContext driverContext) { final Optional metricsOpt = fetchMetrics(driverContext); if (metricsOpt.isPresent()) { final ResponsiveMetrics metrics = metricsOpt.get(); this.sessionMetricUpdater = new ResponsiveSessionMetricUpdater(metrics); this.nodeMetricUpdater = new ResponsiveNodeMetricUpdater(metrics); } else { this.sessionMetricUpdater = NoopSessionMetricUpdater.INSTANCE; this.nodeMetricUpdater = NoopNodeMetricUpdater.INSTANCE; } } private static Optional fetchMetrics(DriverContext driverContext) { // Note that there seems to be no alternative to accessing the metric registry // than `InternalDriverContext`. Each of the included metric factory classes // cast in order to get access. if (driverContext instanceof InternalDriverContext) { final InternalDriverContext internalDriverContext = (InternalDriverContext) driverContext; final Object metricRegistry = internalDriverContext.getMetricRegistry(); if (metricRegistry instanceof ResponsiveMetrics) { return Optional.of((ResponsiveMetrics) internalDriverContext.getMetricRegistry()); } } return Optional.empty(); } @Override public Optional getMetrics() { return Optional.empty(); } @Override public SessionMetricUpdater getSessionUpdater() { return sessionMetricUpdater; } @Override public NodeMetricUpdater newNodeUpdater(final Node node) { return nodeMetricUpdater; } private abstract static class ResponsiveMetricUpdater implements MetricUpdater { protected abstract Sensor getOrCreateSensor(T metric); @Override public void incrementCounter( final T metric, @Nullable final String profileName, final long amount ) { getOrCreateSensor(metric).record(amount); } @Override public void updateHistogram( final T metric, @Nullable final String s, final long value ) { getOrCreateSensor(metric).record(value); } @Override public void markMeter( final T metric, @Nullable final String profileName, final long amount ) { getOrCreateSensor(metric).record(amount); } @Override public void updateTimer( final T metric, @Nullable final String profileName, final long duration, final TimeUnit timeUnit ) { getOrCreateSensor(metric) .record(TimeUnit.MILLISECONDS.convert(duration, timeUnit)); } } private static class ResponsiveSessionMetricUpdater extends ResponsiveMetricUpdater implements SessionMetricUpdater { private final ResponsiveMetrics metrics; private ResponsiveSessionMetricUpdater(final ResponsiveMetrics metrics) { this.metrics = metrics; } @Override public boolean isEnabled( final SessionMetric metric, @Nullable final String profileName ) { switch (asDefaultSessionMetric(metric)) { case BYTES_SENT: case BYTES_RECEIVED: case CQL_REQUESTS: case CQL_CLIENT_TIMEOUTS: case THROTTLING_DELAY: case THROTTLING_ERRORS: return true; default: return false; } } private String sensorName(SessionMetric metric) { return "cassandra-" + metric.getPath(); } private DefaultSessionMetric asDefaultSessionMetric(SessionMetric metric) { if (metric instanceof DefaultSessionMetric) { return (DefaultSessionMetric) metric; } else { return DefaultSessionMetric.fromPath(metric.getPath()); } } @Override protected Sensor getOrCreateSensor(SessionMetric metric) { final DefaultSessionMetric sessionMetric = asDefaultSessionMetric(metric); final String sensorName = sensorName(sessionMetric); Sensor sensor = metrics.getSensor(sensorName); if (sensor == null) { sensor = metrics.addSensor(sensorName); switch (sessionMetric) { case BYTES_SENT: // Exposed as a Meter sensor.add(new MetricName( "bytes-sent", CASSANDRA_METRICS_GROUP, "Cumulative bytes sent", Collections.emptyMap() ), new CumulativeSum()); break; case BYTES_RECEIVED: // Exposed as a Meter sensor.add(new MetricName( "bytes-received", CASSANDRA_METRICS_GROUP, "Cumulative bytes received", Collections.emptyMap() ), new CumulativeSum()); break; case CQL_REQUESTS: // Exposed as a Timer sensor.add(new MetricName( "cql-requests-count", CASSANDRA_METRICS_GROUP, "Cumulative CQL request count", Collections.emptyMap() ), new CumulativeCount()); sensor.add(new MetricName( "cql-requests-cumulative-latency", CASSANDRA_METRICS_GROUP, "Cumulative CQL request latency in milliseconds", Collections.emptyMap() ), new CumulativeSum()); break; case CQL_CLIENT_TIMEOUTS: // Exposed as a Counter sensor.add(new MetricName( "cql-request-timeouts-count", CASSANDRA_METRICS_GROUP, "Cumulative count of CQL request timeouts", Collections.emptyMap() ), new CumulativeSum()); break; case THROTTLING_DELAY: // Exposed as a Timer sensor.add(new MetricName( "throttling-cumulative-delay", CASSANDRA_METRICS_GROUP, "Cumulative throttling delay in milliseconds", Collections.emptyMap() ), new CumulativeSum()); break; case THROTTLING_ERRORS: // Exposed as a Counter sensor.add(new MetricName( "throttling-errors-count", CASSANDRA_METRICS_GROUP, "Cumulative count of throttling errors", Collections.emptyMap() ), new CumulativeSum()); break; default: break; } } return sensor; } } // Note: we use a `NodeMetricUpdater` to get access to some of the other client // metrics, but we do not expose node-level information. Each exposed metric // is rolled up into a session-level metric to avoid exploding the number of // metrics collected. Similarly, we roll up related metrics into totals. private static class ResponsiveNodeMetricUpdater extends ResponsiveMetricUpdater implements NodeMetricUpdater { private final ResponsiveMetrics metrics; private ResponsiveNodeMetricUpdater(final ResponsiveMetrics metrics) { this.metrics = metrics; } private DefaultNodeMetric asDefaultNodeMetric(NodeMetric metric) { if (metric instanceof DefaultNodeMetric) { return (DefaultNodeMetric) metric; } else { return DefaultNodeMetric.fromPath(metric.getPath()); } } @Override public boolean isEnabled( final NodeMetric metric, @Nullable final String profileName ) { switch (asDefaultNodeMetric(metric)) { case AUTHENTICATION_ERRORS: case CONNECTION_INIT_ERRORS: case UNSENT_REQUESTS: case ABORTED_REQUESTS: case WRITE_TIMEOUTS: case READ_TIMEOUTS: case UNAVAILABLES: case OTHER_ERRORS: case RETRIES: case IGNORES: return true; default: return false; } } private String sensorName(DefaultNodeMetric metric) { switch (metric) { case AUTHENTICATION_ERRORS: case CONNECTION_INIT_ERRORS: return "cassandra-connection-errors"; case UNSENT_REQUESTS: case ABORTED_REQUESTS: case WRITE_TIMEOUTS: case READ_TIMEOUTS: case UNAVAILABLES: case OTHER_ERRORS: return "cassandra-request-errors"; case RETRIES: case IGNORES: default: return "cassandra-" + metric.getPath(); } } @Override protected Sensor getOrCreateSensor(final NodeMetric metric) { final DefaultNodeMetric nodeMetric = asDefaultNodeMetric(metric); final String sensorName = sensorName(nodeMetric); Sensor sensor = metrics.getSensor(sensorName); if (sensor == null) { sensor = metrics.addSensor(sensorName); switch (nodeMetric) { case AUTHENTICATION_ERRORS: case CONNECTION_INIT_ERRORS: // Exposed as a Counter sensor.add(new MetricName( "connection-errors-count", CASSANDRA_METRICS_GROUP, "Cumulative count of all connection errors", Collections.emptyMap() ), new CumulativeSum()); break; case UNSENT_REQUESTS: case ABORTED_REQUESTS: case WRITE_TIMEOUTS: case READ_TIMEOUTS: case UNAVAILABLES: case OTHER_ERRORS: // Exposed as a Counter // Unlike ignores and retries, the driver does not expose an error total, // so we do the work here instead. sensor.add(new MetricName( "cql-request-errors-count", CASSANDRA_METRICS_GROUP, "Cumulative count of all request errors", Collections.emptyMap() ), new CumulativeSum()); break; case RETRIES: // From reference doc: // # The total number of errors on this node that caused the RetryPolicy to trigger a // # retry (exposed as a Counter). // # // # This is a sum of all the other retries.* metrics. sensor.add(new MetricName( "cql-request-retries-count", CASSANDRA_METRICS_GROUP, "Cumulative count of all request retries", Collections.emptyMap() ), new CumulativeSum()); break; case IGNORES: // From reference doc: // # The total number of errors on this node that were ignored by the RetryPolicy // (exposed as a Counter). // # // # This is a sum of all the other ignores.* metrics. sensor.add(new MetricName( "cql-request-ignores-count", CASSANDRA_METRICS_GROUP, "Cumulative count of all request ignores", Collections.emptyMap() ), new CumulativeSum()); break; default: break; } } return sensor; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy