This is an automated email from the ASF dual-hosted git repository.

smiklosovic pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 6cdf0fc38b Add prepared statement cache stats to nodetool info
6cdf0fc38b is described below

commit 6cdf0fc38baca39aa2e24231a09ae763fc600b60
Author: Arvind Kandpal <[email protected]>
AuthorDate: Wed Aug 5 20:04:40 2026 +0530

    Add prepared statement cache stats to nodetool info
    
    patch by Arvind Kandpal; reviewed by Maxwell Guo, Stefan Miklosovic for 
CASSANDRA-14366
---
 CHANGES.txt                                        |  1 +
 .../org/apache/cassandra/metrics/CQLMetrics.java   |  2 +
 src/java/org/apache/cassandra/tools/NodeProbe.java | 32 +++++++++++
 .../org/apache/cassandra/tools/nodetool/Info.java  | 17 ++++++
 .../mock/nodetool/InternalNodeProbe.java           |  6 +++
 .../apache/cassandra/tools/nodetool/InfoTest.java  | 62 ++++++++++++++++++++++
 6 files changed, 120 insertions(+)

diff --git a/CHANGES.txt b/CHANGES.txt
index 497e278978..ef37224d5f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 7.0
+ * Add prepared statement cache stats to nodetool info (CASSANDRA-14366)
  * Don't increment client metrics on messaging service connection unpause 
(CASSANDRA-21491)
  * Add nodetool getreplicas (CASSANDRA-17665)
  * Implementation of CEP-49: Hardware-accelerated compression (CASSANDRA-20975)
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<Integer> preparedStatementsCount;
     public final Gauge<Double> preparedStatementsRatio;
     public final Gauge<Long> preparedStatementsCacheSize;
+    public final Gauge<Long> 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..c6e774f5aa 100644
--- a/src/java/org/apache/cassandra/tools/NodeProbe.java
+++ b/src/java/org/apache/cassandra/tools/NodeProbe.java
@@ -44,11 +44,16 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
 import javax.annotation.Nullable;
+import javax.management.AttributeNotFoundException;
 import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
 import javax.management.JMX;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanException;
 import javax.management.MBeanServerConnection;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
+import javax.management.ReflectionException;
 import javax.management.openmbean.CompositeData;
 import javax.management.openmbean.OpenDataException;
 import javax.management.openmbean.TabularData;
@@ -112,7 +117,9 @@ import 
org.apache.cassandra.locator.DynamicEndpointSnitchMBean;
 import org.apache.cassandra.locator.EndpointSnitchInfoMBean;
 import org.apache.cassandra.locator.LocationInfoMBean;
 import org.apache.cassandra.metrics.CIDRAuthorizerMetrics;
+import org.apache.cassandra.metrics.CQLMetrics;
 import org.apache.cassandra.metrics.CassandraMetricsRegistry;
+import org.apache.cassandra.metrics.DefaultNameFactory;
 import org.apache.cassandra.metrics.StorageMetrics;
 import org.apache.cassandra.metrics.TableMetrics;
 import org.apache.cassandra.metrics.ThreadPoolMetrics;
@@ -1939,6 +1946,31 @@ public class NodeProbe implements AutoCloseable
         }
     }
 
+    /**
+     * Retrieve a CQL metric value by name. Works generically for any metric 
registered under
+     * {@code org.apache.cassandra.metrics:type=CQL,name=<metricName>} by 
inspecting the MBean
+     * attributes at runtime, without requiring knowledge of the underlying 
metric type.
+     */
+    public Object getCQLMetric(String metricName)
+    {
+        try
+        {
+            ObjectName objectName = new 
ObjectName(DefaultNameFactory.GROUP_NAME + ":type=" + CQLMetrics.TYPE_NAME + 
",name=" + metricName);
+            for (MBeanAttributeInfo attr : 
mbeanServerConn.getMBeanInfo(objectName).getAttributes())
+            {
+                String name = attr.getName();
+                if ("Value".equals(name) || "Count".equals(name))
+                    return mbeanServerConn.getAttribute(objectName, name);
+            }
+            throw new RuntimeException("No readable value attribute for CQL 
metric: " + metricName);
+        }
+        catch (MalformedObjectNameException | InstanceNotFoundException | 
IntrospectionException |
+               ReflectionException | AttributeNotFoundException | 
MBeanException | IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
     private static Multimap<String, String> 
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..f216493be9 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/Info.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/Info.java
@@ -119,6 +119,23 @@ public class Info extends AbstractCommand
                 probe.getCacheMetric("CounterCache", "HitRate"),
                 cacheService.getCounterCacheSavePeriodInSeconds());
 
+        // Prepared Stmt Cache: entries, size, capacity, executions, evictions
+        try
+        {
+            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"));
+        }
+        catch (RuntimeException e)
+        {
+            if (!(e.getCause() instanceof InstanceNotFoundException))
+                throw e;
+        }
+
         // 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..73a8a2828b
--- /dev/null
+++ b/test/unit/org/apache/cassandra/tools/nodetool/InfoTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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 java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.datastax.driver.core.PreparedStatement;
+
+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
+{
+    private static final Pattern PREPARED_STATEMENT_CACHE_PATTERN =
+    Pattern.compile("Prepared Stmt Cache\\s+: entries (\\d+), size ([^,]+), 
capacity ([^,]+), (\\d+) executions, (\\d+) evictions");
+
+    @BeforeClass
+    public static void setup() throws Exception
+    {
+        requireNetwork();
+        startJMXServer();
+    }
+
+    @Test
+    public void testInfoContainsPreparedStatementCache()
+    {
+        createTable("CREATE TABLE %s (id int PRIMARY KEY, val text)");
+        PreparedStatement preparedStatement = sessionNet().prepare("INSERT 
INTO " + KEYSPACE + '.' + currentTable() + " (id, val) VALUES (?, ?)");
+        sessionNet().execute(preparedStatement.bind(1, "value1"));
+
+        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("info");
+        tool.assertOnCleanExit();
+        String stdout = tool.getStdout();
+        assertThat(stdout).contains("Prepared Stmt Cache");
+        Matcher matcher = PREPARED_STATEMENT_CACHE_PATTERN.matcher(stdout);
+        assertThat(matcher.find()).isTrue();
+        assertThat(Integer.parseInt(matcher.group(1))).isGreaterThan(0);
+        assertThat(matcher.group(2)).isNotEqualTo("0 bytes");
+        assertThat(Integer.parseInt(matcher.group(4))).isGreaterThan(0);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to