From f41b625e487b2f4d2fe319f6097b1b11e9f00f96 Mon Sep 17 00:00:00 2001 From: Isaac Reath Date: Fri, 23 Feb 2024 14:54:30 -0500 Subject: [PATCH] Expose Metric for Prepared Statement Cache Size (in bytes) Patch by Isaac Reath; Reviewed by Paulo Motta and guo Maxwell for CASSANDRA-20864 Closes #4345 --- CHANGES.txt | 1 + .../pages/managing/operating/metrics.adoc | 2 + .../apache/cassandra/cql3/QueryProcessor.java | 11 ++++ .../apache/cassandra/metrics/CQLMetrics.java | 2 + .../cassandra/metrics/CQLMetricsTest.java | 51 ++++++++++++++++--- 5 files changed, 59 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 08ae2c6162..f14cf1e6c1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.1 + * Expose Metric for Prepared Statement Cache Size (in bytes) (CASSANDRA-20864) * Add support for BEGIN TRANSACTION to allow mutations that touch multiple partitions (CASSANDRA-20857) * AutoRepair: Safeguard Full repair against disk protection(CASSANDRA-20045) * BEGIN TRANSACTION crashes if a mutation touches multiple rows (CASSANDRA-20844) diff --git a/doc/modules/cassandra/pages/managing/operating/metrics.adoc b/doc/modules/cassandra/pages/managing/operating/metrics.adoc index 6156682550..2dc6d4fd99 100644 --- a/doc/modules/cassandra/pages/managing/operating/metrics.adoc +++ b/doc/modules/cassandra/pages/managing/operating/metrics.adoc @@ -671,6 +671,8 @@ executed. |PreparedStatementsRatio |Gauge |Percentage of statements that are prepared vs unprepared. + +|PreparedStatementsCacheSize | Gauge | The size of the prepared statements cache in bytes. |=== [[dropped-metrics]] diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java index 7954a7bfc1..8f22bda5c8 100644 --- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java +++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java @@ -42,6 +42,7 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.RemovalCause; import org.antlr.runtime.RecognitionException; + import org.apache.cassandra.concurrent.ImmediateExecutor; import org.apache.cassandra.concurrent.ScheduledExecutors; import org.apache.cassandra.config.DatabaseDescriptor; @@ -178,6 +179,16 @@ public class QueryProcessor implements QueryHandler return preparedStatements.asMap().size(); } + public static long preparedStatementsCacheMemoryUsedBytes() + { + long preparedStatementsCacheMemoryUsedBytes = 0; + for (Map.Entry entry : preparedStatements.asMap().entrySet()) + { + preparedStatementsCacheMemoryUsedBytes += getSizeOfPreparedStatementForCache(entry.getKey(), entry.getValue()); + } + return preparedStatementsCacheMemoryUsedBytes; + } + // Work around initialization dependency private enum InternalStateInstance { diff --git a/src/java/org/apache/cassandra/metrics/CQLMetrics.java b/src/java/org/apache/cassandra/metrics/CQLMetrics.java index edf13dfb4e..ad6c571b78 100644 --- a/src/java/org/apache/cassandra/metrics/CQLMetrics.java +++ b/src/java/org/apache/cassandra/metrics/CQLMetrics.java @@ -37,6 +37,7 @@ public class CQLMetrics public final Gauge preparedStatementsCount; public final Gauge preparedStatementsRatio; + public final Gauge preparedStatementsCacheSize; public CQLMetrics() { @@ -64,5 +65,6 @@ public class CQLMetrics return regularStatementsExecuted.getCount() + preparedStatementsExecuted.getCount(); } }); + preparedStatementsCacheSize = Metrics.register(factory.createMetricName("PreparedStatementsCacheSize"), QueryProcessor::preparedStatementsCacheMemoryUsedBytes); } } diff --git a/test/unit/org/apache/cassandra/metrics/CQLMetricsTest.java b/test/unit/org/apache/cassandra/metrics/CQLMetricsTest.java index 4c447a7012..a1681f169e 100644 --- a/test/unit/org/apache/cassandra/metrics/CQLMetricsTest.java +++ b/test/unit/org/apache/cassandra/metrics/CQLMetricsTest.java @@ -31,9 +31,12 @@ import com.datastax.driver.core.Session; import com.datastax.driver.core.exceptions.InvalidQueryException; import org.apache.cassandra.ServerTestUtils; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.QueryHandler; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.EmbeddedCassandraService; +import org.apache.cassandra.utils.MD5Digest; import static org.apache.cassandra.cql3.CQLTester.assertRowsContains; import static org.apache.cassandra.cql3.CQLTester.row; @@ -95,18 +98,50 @@ public class CQLMetricsTest } @Test - public void testPreparedStatementsCount() + public void testPreparedStatementsCountWithUse() + { + int n = QueryProcessor.metrics.preparedStatementsCount.getValue(); + // Use local test session to avoid interference with other tests due to CASSANDRA-17248 + try (Session localSession = cluster.connect()) + { + long useCountBefore = QueryProcessor.metrics.useStatementsExecuted.getCount(); + localSession.execute("use junit"); + Assert.assertEquals(useCountBefore + 1, QueryProcessor.metrics.useStatementsExecuted.getCount()); + localSession.prepare("SELECT * FROM junit.metricstest WHERE id = ?"); + assertEquals(n + 2, (int) QueryProcessor.metrics.preparedStatementsCount.getValue()); + + assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.cql_group"), + row("org.apache.cassandra.metrics.CQL.PreparedStatementsCount", METRIC_SCOPE_UNDEFINED, "gauge", + String.valueOf(QueryProcessor.metrics.preparedStatementsCount.getValue()))); + } + } + + @Test + public void testPreparedStatementsCountWithoutUse() { int n = QueryProcessor.metrics.preparedStatementsCount.getValue(); long useCountBefore = QueryProcessor.metrics.useStatementsExecuted.getCount(); - session.execute("use junit"); - Assert.assertEquals(useCountBefore + 1, QueryProcessor.metrics.useStatementsExecuted.getCount()); - session.prepare("SELECT * FROM junit.metricstest WHERE id = ?"); - assertEquals(n+2, (int) QueryProcessor.metrics.preparedStatementsCount.getValue()); + session.prepare("SELECT * FROM junit.metricstest WHERE id = 101"); + Assert.assertEquals(useCountBefore, QueryProcessor.metrics.useStatementsExecuted.getCount()); + assertEquals(n+1, (int) QueryProcessor.metrics.preparedStatementsCount.getValue()); + } - assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.cql_group"), - row("org.apache.cassandra.metrics.CQL.PreparedStatementsCount", METRIC_SCOPE_UNDEFINED, "gauge", - String.valueOf(QueryProcessor.metrics.preparedStatementsCount.getValue()))); + @Test + public void testPreparedStatementCacheMemoryUsed() + { + long n = QueryProcessor.metrics.preparedStatementsCacheSize.getValue(); + String rawQueryString = "SELECT id, val FROM junit.metricstest WHERE id = 100"; + PreparedStatement s = session.prepare(rawQueryString); + + // Compute the expected size of a prepared statement. + QueryHandler.Prepared expected = QueryProcessor.parseAndPrepare(rawQueryString, ClientState.forInternalCalls(), false); + MD5Digest hashValue = MD5Digest.compute(rawQueryString); + long expectedHashSize = hashValue.size(); + long expectedStatementSize = expected.pstmntSize; + long expectedSize = expectedHashSize + expectedStatementSize; + + long actualSize = QueryProcessor.metrics.preparedStatementsCacheSize.getValue() - n; + assertEquals(expectedSize, actualSize); } @Test