From 945a4fc23ac1f60b8380be3b60aef89caf3daba2 Mon Sep 17 00:00:00 2001 From: Shailaja Koppu Date: Tue, 1 Feb 2022 09:53:49 -0800 Subject: [PATCH] Add a virtual table for exposing prepared statements metrics patch by Shailaja Koppu; reviewed by Ekaterina Dimitrova, Francisco Guerrero, Yifan Cai for CASSANDRA-17224 --- CHANGES.txt | 1 + .../cassandra/pages/new/virtualtables.adoc | 18 +++ .../cassandra/db/virtual/CQLMetricsTable.java | 81 +++++++++++ .../db/virtual/SystemViewsKeyspace.java | 1 + .../db/virtual/CQLMetricsTableTest.java | 126 ++++++++++++++++++ 5 files changed, 227 insertions(+) create mode 100644 src/java/org/apache/cassandra/db/virtual/CQLMetricsTable.java create mode 100644 test/unit/org/apache/cassandra/db/virtual/CQLMetricsTableTest.java diff --git a/CHANGES.txt b/CHANGES.txt index 03a97faf8e..fe3fb22453 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.1 + * Add a virtual table for exposing prepared statements metrics (CASSANDRA-17224) * Remove python 2.x support from cqlsh (CASSANDRA-17242) * Prewarm role and credential caches to avoid timeouts at startup (CASSANDRA-16958) * Make capacity/validity/updateinterval/activeupdate for Auth Caches configurable via nodetool (CASSANDRA-17063) diff --git a/doc/modules/cassandra/pages/new/virtualtables.adoc b/doc/modules/cassandra/pages/new/virtualtables.adoc index b18ba31316..914bc2788d 100644 --- a/doc/modules/cassandra/pages/new/virtualtables.adoc +++ b/doc/modules/cassandra/pages/new/virtualtables.adoc @@ -74,6 +74,8 @@ recent_hit_rate_per_second, recent_request_rate_per_second, request_count, and s |coordinator_write_latency |Records counts, keyspace_name, table_name, max, median, and per_second for coordinator writes. +|cql_metrcs |Metrics specific to CQL prepared statement caching. + |disk_usage |Records disk usage including disk_space, keyspace_name, and table_name, sorted by system keyspaces. |internode_inbound |Lists information about the inbound internode messaging. @@ -99,6 +101,7 @@ recent_hit_rate_per_second, recent_request_rate_per_second, request_count, and s |thread_pools |Lists metrics for each thread pool. |tombstones_per_read |Records counts, keyspace_name, tablek_name, max, and median for tombstones. + |=== We shall discuss some of the virtual tables in more detail next. @@ -170,6 +173,21 @@ counters | 26214400 | 0 | 0 | NaN | (4 rows) .... +=== CQL metrics Virtual Table +The `cql_metrics` virtual table lists metrics specific to CQL prepared statement caching. A query on `cql_metrics` virtual table lists below metrics. + +.... +cqlsh> select * from system_views.cql_metrics ; + + name | value +------------------------------+------- + prepared_statements_count | 0 + prepared_statements_evicted | 0 + prepared_statements_executed | 0 + prepared_statements_ratio | 0 + regular_statements_executed | 17 +.... + === Settings Virtual Table The `settings` table is rather useful and lists all the current diff --git a/src/java/org/apache/cassandra/db/virtual/CQLMetricsTable.java b/src/java/org/apache/cassandra/db/virtual/CQLMetricsTable.java new file mode 100644 index 0000000000..acd8947e2f --- /dev/null +++ b/src/java/org/apache/cassandra/db/virtual/CQLMetricsTable.java @@ -0,0 +1,81 @@ +/* + * 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.db.virtual; + +import com.google.common.annotations.VisibleForTesting; + +import org.apache.cassandra.db.marshal.DoubleType; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.dht.LocalPartitioner; +import org.apache.cassandra.metrics.CQLMetrics; +import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.cql3.QueryProcessor; + + +final class CQLMetricsTable extends AbstractVirtualTable +{ + public static final String TABLE_NAME = "cql_metrics"; + public static final String PREPARED_STATEMENTS_COUNT = "prepared_statements_count"; + public static final String PREPARED_STATEMENTS_EVICTED = "prepared_statements_evicted"; + public static final String PREPARED_STATEMENTS_EXECUTED = "prepared_statements_executed"; + public static final String PREPARED_STATEMENTS_RATIO = "prepared_statements_ratio"; + public static final String REGULAR_STATEMENTS_EXECUTED = "regular_statements_executed"; + public static final String NAME_COL = "name"; + public static final String VALUE_COL = "value"; + + private final CQLMetrics cqlMetrics; + + CQLMetricsTable(String keyspace) + { + this(keyspace, QueryProcessor.metrics); + } + + // For dependency injection + @VisibleForTesting + CQLMetricsTable(String keyspace, CQLMetrics cqlMetrics) + { + super(TableMetadata.builder(keyspace, TABLE_NAME) + .comment("Metrics specific to CQL prepared statement caching") + .kind(TableMetadata.Kind.VIRTUAL) + .partitioner(new LocalPartitioner(UTF8Type.instance)) + .addPartitionKeyColumn(NAME_COL, UTF8Type.instance) + .addRegularColumn(VALUE_COL, DoubleType.instance) + .build()); + this.cqlMetrics = cqlMetrics; + } + + @Override + public DataSet data() + { + SimpleDataSet result = new SimpleDataSet(metadata()); + addRow(result, PREPARED_STATEMENTS_COUNT, cqlMetrics.preparedStatementsCount.getValue()); + addRow(result, PREPARED_STATEMENTS_EVICTED, cqlMetrics.preparedStatementsEvicted.getCount()); + addRow(result, PREPARED_STATEMENTS_EXECUTED, cqlMetrics.preparedStatementsExecuted.getCount()); + addRow(result, PREPARED_STATEMENTS_RATIO, cqlMetrics.preparedStatementsRatio.getValue()); + addRow(result, REGULAR_STATEMENTS_EXECUTED, cqlMetrics.regularStatementsExecuted.getCount()); + + return result; + } + + private void addRow(SimpleDataSet dataSet, String name, double value) + { + dataSet.row(name) + .column(VALUE_COL, value); + } +} diff --git a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java index 6dfd8a95ff..6d5582e531 100644 --- a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java +++ b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java @@ -43,6 +43,7 @@ public final class SystemViewsKeyspace extends VirtualKeyspace .add(new NetworkPermissionsCacheKeysTable(VIRTUAL_VIEWS)) .add(new PermissionsCacheKeysTable(VIRTUAL_VIEWS)) .add(new RolesCacheKeysTable(VIRTUAL_VIEWS)) + .add(new CQLMetricsTable(VIRTUAL_VIEWS)) .build()); } } diff --git a/test/unit/org/apache/cassandra/db/virtual/CQLMetricsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/CQLMetricsTableTest.java new file mode 100644 index 0000000000..658b84123d --- /dev/null +++ b/test/unit/org/apache/cassandra/db/virtual/CQLMetricsTableTest.java @@ -0,0 +1,126 @@ +/* + * 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.db.virtual; + +import java.util.concurrent.atomic.AtomicInteger; + +import com.google.common.collect.ImmutableList; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Session; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.BeforeClass; + +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.metrics.CQLMetrics; + +public class CQLMetricsTableTest extends CQLTester +{ + private static final String KS_NAME = "vts"; + + @BeforeClass + public static void setUpClass() + { + CQLTester.setUpClass(); + } + + private void queryAndValidateMetrics(CQLMetrics expectedMetrics) throws Throwable + { + String getMetricsQuery = "SELECT * FROM " + KS_NAME + "." + CQLMetricsTable.TABLE_NAME; + ResultSet vtsRows = executeNet(getMetricsQuery); + + assertEquals(2, vtsRows.getColumnDefinitions().size()); + + AtomicInteger rowCount = new AtomicInteger(0); + vtsRows.forEach(r -> { + final double metricValue = r.getDouble(CQLMetricsTable.VALUE_COL); + switch (r.getString(CQLMetricsTable.NAME_COL)) + { + case CQLMetricsTable.PREPARED_STATEMENTS_COUNT: + assertEquals(expectedMetrics.preparedStatementsCount.getValue(), metricValue, 0); + break; + case CQLMetricsTable.PREPARED_STATEMENTS_EVICTED: + assertEquals(expectedMetrics.preparedStatementsEvicted.getCount(), metricValue, 0); + break; + case CQLMetricsTable.PREPARED_STATEMENTS_EXECUTED: + assertEquals(expectedMetrics.preparedStatementsExecuted.getCount(), metricValue, 0); + break; + case CQLMetricsTable.PREPARED_STATEMENTS_RATIO: + assertEquals(expectedMetrics.preparedStatementsRatio.getValue(), metricValue, 0.01); + break; + case CQLMetricsTable.REGULAR_STATEMENTS_EXECUTED: + assertEquals(expectedMetrics.regularStatementsExecuted.getCount(), metricValue, 0); + break; + } + rowCount.getAndIncrement(); + }); + + assertEquals(5, rowCount.get()); + } + + @Test + public void testUsingPrepareStmts() throws Throwable + { + CQLMetricsTable table = new CQLMetricsTable(KS_NAME); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table))); + + String ks = createKeyspace("CREATE KEYSPACE %s WITH replication={ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }"); + String tbl = createTable(ks, "CREATE TABLE %s (id int PRIMARY KEY, cid int, val text)"); + Session session = sessionNet(); + + String insertCQL = "INSERT INTO " + ks + "." + tbl + " (id, cid, val) VALUES (?, ?, ?)"; + PreparedStatement preparedInsert = session.prepare(insertCQL); + + String selectCQL = "Select * from " + ks + "." + tbl + " where id = ?"; + PreparedStatement preparedSelect = session.prepare(selectCQL); + + for (int i = 0; i < 10; i++) + { + session.execute(preparedInsert.bind(i, i, "value" + i)); + session.execute(preparedSelect.bind(i)); + } + + queryAndValidateMetrics(QueryProcessor.metrics); + } + + @Test + public void testUsingInjectedValues() throws Throwable + { + CQLMetrics cqlMetrics = new CQLMetrics(); + CQLMetricsTable table = new CQLMetricsTable(KS_NAME, cqlMetrics); + VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table))); + + // With initial injected values + cqlMetrics.preparedStatementsExecuted.inc(50); + cqlMetrics.regularStatementsExecuted.inc(100); + cqlMetrics.preparedStatementsEvicted.inc(25); + queryAndValidateMetrics(cqlMetrics); + + // Test again with updated values + cqlMetrics.preparedStatementsExecuted.inc(150); + cqlMetrics.regularStatementsExecuted.inc(200); + cqlMetrics.preparedStatementsEvicted.inc(50); + queryAndValidateMetrics(cqlMetrics); + } +}