From a5569da3c9cb7b373cbbb9a67121497663c36769 Mon Sep 17 00:00:00 2001 From: Arvind Kandpal Date: Fri, 24 Jul 2026 13:48:29 +0530 Subject: [PATCH] CASSANDRA-14366: Add prepared statement cache stats to nodetool info Assisted-by: Claude Sonnet 4.6 (1M context) --- .../apache/cassandra/metrics/CQLMetrics.java | 2 + .../org/apache/cassandra/tools/NodeProbe.java | 32 +++++++++++++ .../apache/cassandra/tools/nodetool/Info.java | 9 ++++ .../mock/nodetool/InternalNodeProbe.java | 6 +++ .../cassandra/tools/nodetool/InfoTest.java | 46 +++++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 test/unit/org/apache/cassandra/tools/nodetool/InfoTest.java diff --git a/src/java/org/apache/cassandra/metrics/CQLMetrics.java b/src/java/org/apache/cassandra/metrics/CQLMetrics.java index e7b6ec57d1..4a134c9a42 100644 --- a/src/java/org/apache/cassandra/metrics/CQLMetrics.java +++ b/src/java/org/apache/cassandra/metrics/CQLMetrics.java @@ -39,6 +39,7 @@ public class CQLMetrics public final Gauge preparedStatementsCount; public final Gauge preparedStatementsRatio; public final Gauge preparedStatementsCacheSize; + public final Gauge preparedStatementsCacheCapacity; public CQLMetrics() { @@ -67,5 +68,6 @@ public class CQLMetrics } }); preparedStatementsCacheSize = Metrics.register(factory.createMetricName("PreparedStatementsCacheSize"), QueryProcessor::preparedStatementsCacheMemoryUsedBytes); + preparedStatementsCacheCapacity = Metrics.register(factory.createMetricName("PreparedStatementsCacheCapacity"), () -> QueryProcessor.PREPARED_STATEMENT_CACHE_SIZE_BYTES); } } diff --git a/src/java/org/apache/cassandra/tools/NodeProbe.java b/src/java/org/apache/cassandra/tools/NodeProbe.java index c60e0427e5..793ccb1e70 100644 --- a/src/java/org/apache/cassandra/tools/NodeProbe.java +++ b/src/java/org/apache/cassandra/tools/NodeProbe.java @@ -1939,6 +1939,38 @@ public class NodeProbe implements AutoCloseable } } + /** + * Retrieve CQL metrics by name. + * @param metricName PreparedStatementsCount, PreparedStatementsCacheSize, PreparedStatementsCacheCapacity, + * PreparedStatementsExecuted, or PreparedStatementsEvicted. + */ + public Object getCQLMetric(String metricName) + { + try + { + switch (metricName) + { + case "PreparedStatementsCount": + case "PreparedStatementsCacheSize": + case "PreparedStatementsCacheCapacity": + return JMX.newMBeanProxy(mbeanServerConn, + new ObjectName("org.apache.cassandra.metrics:type=CQL,name=" + metricName), + CassandraMetricsRegistry.JmxGaugeMBean.class).getValue(); + case "PreparedStatementsExecuted": + case "PreparedStatementsEvicted": + return JMX.newMBeanProxy(mbeanServerConn, + new ObjectName("org.apache.cassandra.metrics:type=CQL,name=" + metricName), + CassandraMetricsRegistry.JmxCounterMBean.class).getCount(); + default: + throw new RuntimeException("Unknown CQL metric name " + metricName); + } + } + catch (MalformedObjectNameException e) + { + throw new RuntimeException(e); + } + } + private static Multimap getJmxThreadPools(MBeanServerConnection mbeanServerConn) { try diff --git a/src/java/org/apache/cassandra/tools/nodetool/Info.java b/src/java/org/apache/cassandra/tools/nodetool/Info.java index b0b2dff019..dc721c7ddd 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/Info.java +++ b/src/java/org/apache/cassandra/tools/nodetool/Info.java @@ -119,6 +119,15 @@ public class Info extends AbstractCommand probe.getCacheMetric("CounterCache", "HitRate"), cacheService.getCounterCacheSavePeriodInSeconds()); + // Prepared Statement Cache: entries, size, capacity, executions, evictions + out.printf("%-23s: entries %d, size %s, capacity %s, %d executions, %d evictions%n", + "Prepared Stmt Cache", + probe.getCQLMetric("PreparedStatementsCount"), + FileUtils.stringifyFileSize((long) probe.getCQLMetric("PreparedStatementsCacheSize")), + FileUtils.stringifyFileSize((long) probe.getCQLMetric("PreparedStatementsCacheCapacity")), + probe.getCQLMetric("PreparedStatementsExecuted"), + probe.getCQLMetric("PreparedStatementsEvicted")); + // Chunk Cache: Hits, Requests, RecentHitRate, SavePeriodInSeconds try { diff --git a/test/distributed/org/apache/cassandra/distributed/mock/nodetool/InternalNodeProbe.java b/test/distributed/org/apache/cassandra/distributed/mock/nodetool/InternalNodeProbe.java index 62e0dad3ea..78183f7d2b 100644 --- a/test/distributed/org/apache/cassandra/distributed/mock/nodetool/InternalNodeProbe.java +++ b/test/distributed/org/apache/cassandra/distributed/mock/nodetool/InternalNodeProbe.java @@ -174,6 +174,12 @@ public class InternalNodeProbe extends NodeProbe throw new UnsupportedOperationException(); } + @Override + public Object getCQLMetric(String metricName) + { + throw new UnsupportedOperationException(); + } + @Override public Object getClientMetric(String metricName) { diff --git a/test/unit/org/apache/cassandra/tools/nodetool/InfoTest.java b/test/unit/org/apache/cassandra/tools/nodetool/InfoTest.java new file mode 100644 index 0000000000..e7597de2b9 --- /dev/null +++ b/test/unit/org/apache/cassandra/tools/nodetool/InfoTest.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.cassandra.tools.nodetool; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.tools.ToolRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +public class InfoTest extends CQLTester +{ + @BeforeClass + public static void setup() throws Exception + { + requireNetwork(); + startJMXServer(); + } + + @Test + public void testInfoContainsPreparedStmtCache() + { + ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("info"); + tool.assertOnCleanExit(); + String stdout = tool.getStdout(); + assertThat(stdout).contains("Prepared Stmt Cache"); + assertThat(stdout).containsPattern("Prepared Stmt Cache\\s+: entries \\d+, size .+, capacity .+, \\d+ executions, \\d+ evictions"); + } +}