Skip to content
2 changes: 2 additions & 0 deletions src/java/org/apache/cassandra/metrics/CQLMetrics.java
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -67,5 +68,6 @@ public double getDenominator()
}
});
preparedStatementsCacheSize = Metrics.register(factory.createMetricName("PreparedStatementsCacheSize"), QueryProcessor::preparedStatementsCacheMemoryUsedBytes);
preparedStatementsCacheCapacity = Metrics.register(factory.createMetricName("PreparedStatementsCacheCapacity"), () -> QueryProcessor.PREPARED_STATEMENT_CACHE_SIZE_BYTES);
}
}
32 changes: 32 additions & 0 deletions src/java/org/apache/cassandra/tools/NodeProbe.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,16 @@
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;
Expand Down Expand Up @@ -112,7 +117,9 @@
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;
Expand Down Expand Up @@ -1939,6 +1946,31 @@ public Object getBufferPoolMetric(String poolType, String metricName)
}
}

/**
* 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)
Comment thread
smiklosovic marked this conversation as resolved.
{
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
Expand Down
17 changes: 17 additions & 0 deletions src/java/org/apache/cassandra/tools/nodetool/Info.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,23 @@ public void execute(NodeProbe probe)
probe.getCacheMetric("CounterCache", "HitRate"),
cacheService.getCounterCacheSavePeriodInSeconds());

// Prepared Statement Cache: entries, size, capacity, executions, evictions
try
{
out.printf("%-23s: entries %d, size %s, capacity %s, %d executions, %d evictions%n",
"Prepared Statement 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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ public Object getCompactionMetric(String metricName)
throw new UnsupportedOperationException();
}

@Override
public Object getCQLMetric(String metricName)
{
throw new UnsupportedOperationException();
}

@Override
public Object getClientMetric(String metricName)
{
Expand Down
62 changes: 62 additions & 0 deletions test/unit/org/apache/cassandra/tools/nodetool/InfoTest.java
Original file line number Diff line number Diff line change
@@ -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 Statement 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 Statement 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);
}
}