You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by mm...@apache.org on 2024/04/03 18:09:26 UTC
(cassandra) branch trunk updated: Expose all dropwizard metrics in virtual tables
This is an automated email from the ASF dual-hosted git repository.
mmuzaf 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 2e7def7626 Expose all dropwizard metrics in virtual tables
2e7def7626 is described below
commit 2e7def7626fe299318e0cf4c2bf8138a6c31e149
Author: Maxim Muzafarov <ma...@gmail.com>
AuthorDate: Wed Apr 3 17:07:14 2024 +0200
Expose all dropwizard metrics in virtual tables
patch by Maxim Muzafarov; reviewed by Chris Lohfink, Stefan Miklosovic for CASSANDRA-14572
---
CHANGES.txt | 1 +
.../pages/managing/operating/metrics.adoc | 10 +-
.../pages/managing/operating/virtualtables.adoc | 60 ++-
pylib/cqlshlib/cql3handling.py | 5 +-
pylib/cqlshlib/test/test_cqlsh_completion.py | 8 +-
.../cassandra/db/virtual/BatchMetricsTable.java | 14 +-
.../cassandra/db/virtual/CQLMetricsTable.java | 11 +-
.../db/virtual/CollectionVirtualTableAdapter.java | 582 +++++++++++++++++++++
.../cassandra/db/virtual/SystemViewsKeyspace.java | 11 +-
.../cassandra/db/virtual/ThreadPoolsTable.java | 94 ----
.../cassandra/db/virtual/VirtualKeyspace.java | 12 +
.../apache/cassandra/db/virtual/model/Column.java | 64 +++
.../virtual/model/CounterMetricRow.java} | 38 +-
.../virtual/model/GaugeMetricRow.java} | 42 +-
.../db/virtual/model/HistogramMetricRow.java | 100 ++++
.../cassandra/db/virtual/model/MeterMetricRow.java | 81 +++
.../virtual/model/MetricGroupRow.java} | 34 +-
.../cassandra/db/virtual/model/MetricRow.java | 80 +++
.../cassandra/db/virtual/model/ThreadPoolRow.java | 94 ++++
.../cassandra/db/virtual/model/TimerMetricRow.java | 81 +++
.../db/virtual/walker/CounterMetricRowWalker.java | 61 +++
.../db/virtual/walker/GaugeMetricRowWalker.java | 61 +++
.../virtual/walker/HistogramMetricRowWalker.java | 75 +++
.../db/virtual/walker/MeterMetricRowWalker.java | 69 +++
.../db/virtual/walker/MetricGroupRowWalker.java | 58 ++
.../db/virtual/walker/MetricRowWalker.java | 63 +++
.../cassandra/db/virtual/walker/RowWalker.java | 87 +++
.../db/virtual/walker/ThreadPoolRowWalker.java | 75 +++
.../db/virtual/walker/TimerMetricRowWalker.java | 69 +++
.../index/sai/metrics/AbstractMetrics.java | 4 +-
.../io/sstable/format/big/RowIndexEntry.java | 3 +-
.../org/apache/cassandra/metrics/BatchMetrics.java | 4 +-
.../cassandra/metrics/BufferPoolMetrics.java | 3 +-
.../metrics/CASClientWriteRequestMetrics.java | 4 +-
.../cassandra/metrics/CIDRAuthorizerMetrics.java | 3 +-
.../org/apache/cassandra/metrics/CQLMetrics.java | 3 +-
.../org/apache/cassandra/metrics/CacheMetrics.java | 3 +-
.../metrics/CassandraMetricsRegistry.java | 559 ++++++++++++++++----
.../cassandra/metrics/ChunkCacheMetrics.java | 3 +-
.../metrics/ClientMessageSizeMetrics.java | 2 +-
.../apache/cassandra/metrics/ClientMetrics.java | 7 +-
.../cassandra/metrics/ClientRequestMetrics.java | 3 +-
.../metrics/ClientRequestSizeMetrics.java | 2 +-
.../apache/cassandra/metrics/CommitLogMetrics.java | 3 +-
.../cassandra/metrics/CompactionMetrics.java | 3 +-
.../apache/cassandra/metrics/DenylistMetrics.java | 3 +-
.../cassandra/metrics/DroppedMessageMetrics.java | 2 +-
.../cassandra/metrics/HintedHandoffMetrics.java | 3 +-
.../cassandra/metrics/HintsServiceMetrics.java | 3 +-
.../cassandra/metrics/InternodeInboundMetrics.java | 3 +-
.../metrics/InternodeOutboundMetrics.java | 2 +-
.../apache/cassandra/metrics/KeyspaceMetrics.java | 5 +-
.../apache/cassandra/metrics/LatencyMetrics.java | 17 +-
.../apache/cassandra/metrics/MessagingMetrics.java | 3 +-
.../apache/cassandra/metrics/MutualTlsMetrics.java | 3 +-
.../org/apache/cassandra/metrics/PaxosMetrics.java | 3 +-
.../cassandra/metrics/ReadRepairMetrics.java | 3 +-
.../apache/cassandra/metrics/StorageMetrics.java | 1 +
.../apache/cassandra/metrics/StreamingMetrics.java | 2 +-
.../org/apache/cassandra/metrics/TCMMetrics.java | 3 +-
.../org/apache/cassandra/metrics/TableMetrics.java | 50 +-
.../cassandra/metrics/ThreadPoolMetrics.java | 7 +-
.../cassandra/metrics/TrieMemtableMetricsView.java | 11 +-
.../cassandra/metrics/UnweightedCacheMetrics.java | 3 +-
.../apache/cassandra/schema/SchemaConstants.java | 1 +
.../apache/cassandra/service/CassandraDaemon.java | 8 +-
.../apache/cassandra/utils/EstimatedHistogram.java | 6 +
.../org/apache/cassandra/utils/FBUtilities.java | 3 +
.../cassandra/utils/memory/MemtablePool.java | 4 +-
test/unit/org/apache/cassandra/cql3/CQLTester.java | 108 +++-
.../virtual/CollectionVirtualTableAdapterTest.java | 243 +++++++++
.../db/virtual/model/CollectionEntry.java | 126 +++++
.../db/virtual/model/CollectionEntryTestRow.java | 93 ++++
.../db/virtual/model/PartitionEntryTestRow.java | 40 +-
.../walker/CollectionEntryTestRowWalker.java | 75 +++
.../walker/PartitionEntryTestRowWalker.java | 61 +++
.../apache/cassandra/metrics/BatchMetricsTest.java | 11 +
.../apache/cassandra/metrics/CQLMetricsTest.java | 21 +
.../metrics/CassandraMetricsRegistryTest.java | 117 ++++-
.../metrics/ClientRequestMetricsTest.java | 33 +-
.../ClientRequestRowAndColumnMetricsTest.java | 8 +-
.../metrics/JmxVirtualTableMetricsTest.java | 336 ++++++++++++
.../cassandra/metrics/KeyspaceMetricsTest.java | 34 ++
.../cassandra/metrics/LatencyMetricsTest.java | 10 +-
.../apache/cassandra/metrics/TableMetricsTest.java | 21 +
.../cassandra/metrics/TrieMemtableMetricsTest.java | 25 +-
.../apache/cassandra/utils/FBUtilitiesTest.java | 3 +-
87 files changed, 3810 insertions(+), 353 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 1c5f1766fd..3703a0bf04 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
5.1
+ * Expose all dropwizard metrics in virtual tables (CASSANDRA-14572)
* Ensured that PropertyFileSnitchTest do not overwrite cassandra-toploogy.properties (CASSANDRA-19502)
* Add option for MutualTlsAuthenticator to restrict the certificate validity period (CASSANDRA-18951)
* Fix StorageService::constructRangeToEndpointMap for non-distributed keyspaces (CASSANDRA-19255)
diff --git a/doc/modules/cassandra/pages/managing/operating/metrics.adoc b/doc/modules/cassandra/pages/managing/operating/metrics.adoc
index c4392fea82..78dbd16586 100644
--- a/doc/modules/cassandra/pages/managing/operating/metrics.adoc
+++ b/doc/modules/cassandra/pages/managing/operating/metrics.adoc
@@ -792,22 +792,24 @@ Reported name format:
*Metric Name*::
`org.apache.cassandra.metrics.Storage.<MetricName>`
*JMX MBean*::
- `org.apache.cassandra.metrics:type=Storage name=<MetricName>`
+ `org.apache.cassandra.metrics:type=Storage,name=<MetricName>`
[cols=",,",options="header",]
|===
|Name |Type |Description
|Exceptions |Counter |Number of internal exceptions caught. Under normal
exceptions this should be zero.
-
|Load |Counter |Size, in bytes, of the on disk data size this node
manages.
-
|TotalHints |Counter |Number of hint messages written to this node since
[re]start. Includes one entry for each host to be hinted per hint.
-
|TotalHintsInProgress |Counter |Number of hints attemping to be sent
currently.
+|BootstrapFilesReceived | Gauage<Long> | Number of files received during bootstrap.
+|BootstrapFilesTotal | Gauge<Long> | Total number of files to be received during bootstrap.
+|BootstrapFilesThroughput | Meter | Throughput of files received during bootstrap.
+|BootstrapLastSeenError | Gauge<String> | Last error message seen during bootstrap.
+|BootstrapLastSeenStatus | Gauge<String> | Last status message seen during bootstrap.
|===
[[handoff-metrics]]
diff --git a/doc/modules/cassandra/pages/managing/operating/virtualtables.adoc b/doc/modules/cassandra/pages/managing/operating/virtualtables.adoc
index f4a5608507..d3b948e3d1 100644
--- a/doc/modules/cassandra/pages/managing/operating/virtualtables.adoc
+++ b/doc/modules/cassandra/pages/managing/operating/virtualtables.adoc
@@ -24,10 +24,11 @@ Since a virtual table is not replicated the partitioner sorts in order of partit
== Virtual Keyspaces
-Apache Cassandra 4.0 has added two new keyspaces for virtual tables:
+Apache Cassandra has the following virtual keyspaces:
* `system_virtual_schema`
-* `system_views`.
+* `system_views`
+* `system_metrics`
The `system_virtual_schema` keyspace has three tables: `keyspaces`,
`columns` and `tables` for the virtual keyspace, table, and column definitions, respectively.
@@ -36,6 +37,10 @@ It is used by Cassandra internally and a user should not access it directly.
The `system_views` keyspace contains the actual virtual tables.
+The `system_metrics` keyspace contains the virtual tables of all the metrics available in Apache Cassandra
+which can be used to monitor the health of the cluster. These metrics are split into several tables each
+containing a different set of metrics corresponding to the type of metric or the component of the system.
+
== Virtual Table Limitations
Before discussing virtual keyspaces and tables, note that virtual keyspaces and tables have some limitations.
@@ -55,6 +60,57 @@ In fact, mutations for virtual and regular tables cannot occur in the same batch
== Virtual Tables
+=== Virtual Tables _system_metrics_ Keyspace
+
+The `system_metrics` virtual keyspace contains the virtual tables of all the metrics available in Apache Cassandra.
+Currently, the `system_metrics` keyspace contains the following virtual tables (subject to change), which can be found
+in the `system_metrics.all_groups` virtual table:
+
+[source, console]
+----
+cqlsh> select * from system_metrics.all_groups ;
+
+ group_name | virtual_table
+-------------------+---------------------------
+ Batch | batch_group
+ BufferPool | buffer_pool_group
+ CIDRAuthorizer | cidr_authorizer_group
+ CQL | cql_group
+ Cache | cache_group
+ ChunkCache | chunk_cache_group
+ Client | client_group
+ ClientMessageSize | client_message_size_group
+ ClientRequest | client_request_group
+ ClientRequestSize | client_request_size_group
+ ColumnFamily | column_family_group
+ CommitLog | commit_log_group
+ Compaction | compaction_group
+ Connection | connection_group
+ DroppedMessage | dropped_message_group
+ HintsService | hints_service_group
+ Index | index_group
+ IndexColumnFamily | index_column_family_group
+ IndexTable | index_table_group
+ InternodeInbound | internode_inbound_group
+ Keyspace | keyspace_group
+ MemtablePool | memtable_pool_group
+ Messaging | messaging_group
+ Paxos | paxos_group
+ ReadRepair | read_repair_group
+ Repair | repair_group
+ Storage | storage_group
+ StorageProxy | storage_proxy_group
+ Streaming | streaming_group
+ TCM | tcm_group
+ Table | table_group
+ ThreadPools | thread_pools_group
+ TrieMemtable | trie_memtable_group
+ UnweightedCache | unweighted_cache_group
+
+----
+
+=== Virtual Tables _system_views_ Keyspace
+
Each of the virtual tables in the `system_views` virtual keyspace contain different information.
The following table describes the virtual tables:
diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py
index f795ee7dd0..c774da3c78 100644
--- a/pylib/cqlshlib/cql3handling.py
+++ b/pylib/cqlshlib/cql3handling.py
@@ -34,8 +34,9 @@ class UnexpectedTableStructure(UserWarning):
SYSTEM_KEYSPACES = ('system', 'system_schema', 'system_traces', 'system_auth', 'system_distributed', 'system_views',
- 'system_virtual_schema', 'system_cluster_metadata')
-NONALTERBALE_KEYSPACES = ('system', 'system_schema', 'system_views', 'system_virtual_schema', 'system_cluster_metadata')
+ 'system_metrics', 'system_virtual_schema', 'system_cluster_metadata')
+NONALTERBALE_KEYSPACES = ('system', 'system_schema', 'system_views', 'system_metrics', 'system_virtual_schema',
+ 'system_cluster_metadata')
class Cql3ParsingRuleSet(CqlParsingRuleSet):
diff --git a/pylib/cqlshlib/test/test_cqlsh_completion.py b/pylib/cqlshlib/test/test_cqlsh_completion.py
index 793dcd7a8b..0f01aa5286 100644
--- a/pylib/cqlshlib/test/test_cqlsh_completion.py
+++ b/pylib/cqlshlib/test/test_cqlsh_completion.py
@@ -588,7 +588,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
self.trycompletions('CREATE TYPE foo ', choices=['(', '.'])
def test_complete_in_drop_type(self):
- self.trycompletions('DROP TYPE ', choices=['IF', 'system_views.',
+ self.trycompletions('DROP TYPE ', choices=['IF', 'system_views.', 'system_metrics.',
'tags', 'system_traces.', 'system_distributed.', 'system_cluster_metadata.',
'phone_number', 'quote_udt', 'band_info_type', 'address', 'system.', 'system_schema.',
'system_auth.', 'system_virtual_schema.', self.cqlsh.keyspace + '.'
@@ -895,7 +895,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
def test_complete_in_use(self):
self.trycompletions('US', immediate='E ')
- self.trycompletions('USE ', choices=[self.cqlsh.keyspace, 'system', 'system_auth',
+ self.trycompletions('USE ', choices=[self.cqlsh.keyspace, 'system', 'system_auth', 'system_metrics',
'system_distributed', 'system_schema', 'system_traces', 'system_views',
'system_virtual_schema', 'system_cluster_metadata' ])
@@ -991,7 +991,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
'dynamic_columns',
'twenty_rows_composite_table',
'utf8_with_special_chars',
- 'system_traces.', 'songs', 'system_views.',
+ 'system_traces.', 'songs', 'system_views.', 'system_metrics.',
'system_virtual_schema.',
'system_schema.', 'system_distributed.',
'system_cluster_metadata.',
@@ -1020,7 +1020,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
def test_complete_in_alter_type(self):
self.trycompletions('ALTER TYPE I', immediate='F EXISTS ')
self.trycompletions('ALTER TYPE ', choices=['IF', 'system_views.',
- 'tags', 'system_traces.', 'system_distributed.',
+ 'tags', 'system_traces.', 'system_distributed.', 'system_metrics.',
'phone_number', 'quote_udt', 'band_info_type', 'address', 'system.', 'system_schema.',
'system_auth.', 'system_virtual_schema.', 'system_cluster_metadata.', self.cqlsh.keyspace + '.'
])
diff --git a/src/java/org/apache/cassandra/db/virtual/BatchMetricsTable.java b/src/java/org/apache/cassandra/db/virtual/BatchMetricsTable.java
index 948f2a1e9c..d712566388 100644
--- a/src/java/org/apache/cassandra/db/virtual/BatchMetricsTable.java
+++ b/src/java/org/apache/cassandra/db/virtual/BatchMetricsTable.java
@@ -26,10 +26,16 @@ import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.metrics.BatchMetrics;
import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.service.ClientWarn;
+/**
+ * @deprecated The "batch_metrics" virtual table is no longer used, and will be removed in a future release. Please,
+ * use either "system_metrics.batch_group", or "system_metrics.type_histogram" virtual tables instead.
+ */
+@Deprecated(since = "5.1")
public class BatchMetricsTable extends AbstractVirtualTable
{
-
+ protected static final String TABLE_NAME = "batch_metrics";
private static final String PARTITIONS_PER_LOGGED_BATCH = "partitions_per_logged_batch";
private static final String PARTITIONS_PER_UNLOGGED_BATCH = "partitions_per_unlogged_batch";
private static final String PARTITIONS_PER_COUNTER_BATCH = "partitions_per_counter_batch";
@@ -40,7 +46,7 @@ public class BatchMetricsTable extends AbstractVirtualTable
BatchMetricsTable(String keyspace)
{
- super(TableMetadata.builder(keyspace, "batch_metrics")
+ super(TableMetadata.builder(keyspace, TABLE_NAME)
.comment("Metrics specific to batch statements")
.kind(TableMetadata.Kind.VIRTUAL)
.partitioner(new LocalPartitioner(UTF8Type.instance))
@@ -61,6 +67,10 @@ public class BatchMetricsTable extends AbstractVirtualTable
addRow(result, PARTITIONS_PER_UNLOGGED_BATCH, metrics.partitionsPerUnloggedBatch.getSnapshot());
addRow(result, PARTITIONS_PER_COUNTER_BATCH, metrics.partitionsPerCounterBatch.getSnapshot());
+ ClientWarn.instance.warn(String.format("The \"%s\" virtual table is deprecated. " +
+ "Please, use either \"%s.batch_group\", or \"%s.type_histogram\" " +
+ "virtual tables instead.",
+ TABLE_NAME, metadata.keyspace, metadata.keyspace));
return result;
}
diff --git a/src/java/org/apache/cassandra/db/virtual/CQLMetricsTable.java b/src/java/org/apache/cassandra/db/virtual/CQLMetricsTable.java
index acd8947e2f..d86a393953 100644
--- a/src/java/org/apache/cassandra/db/virtual/CQLMetricsTable.java
+++ b/src/java/org/apache/cassandra/db/virtual/CQLMetricsTable.java
@@ -20,14 +20,19 @@ package org.apache.cassandra.db.virtual;
import com.google.common.annotations.VisibleForTesting;
+import org.apache.cassandra.cql3.QueryProcessor;
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;
-
+import org.apache.cassandra.service.ClientWarn;
+/**
+ * @deprecated The "cql_metrics" virtual table is no longer used, and will be removed in a future release. Please,
+ * use "system_metrics.cql_group" virtual table instead.
+ */
+@Deprecated(since = "5.1")
final class CQLMetricsTable extends AbstractVirtualTable
{
public static final String TABLE_NAME = "cql_metrics";
@@ -70,6 +75,8 @@ final class CQLMetricsTable extends AbstractVirtualTable
addRow(result, PREPARED_STATEMENTS_RATIO, cqlMetrics.preparedStatementsRatio.getValue());
addRow(result, REGULAR_STATEMENTS_EXECUTED, cqlMetrics.regularStatementsExecuted.getCount());
+ ClientWarn.instance.warn("The \"" + TABLE_NAME + "\" virtual table is deprecated. " +
+ "Please, use \"system_metrics.cql_group\" virtual table instead.");
return result;
}
diff --git a/src/java/org/apache/cassandra/db/virtual/CollectionVirtualTableAdapter.java b/src/java/org/apache/cassandra/db/virtual/CollectionVirtualTableAdapter.java
new file mode 100644
index 0000000000..56eacf5b3e
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/CollectionVirtualTableAdapter.java
@@ -0,0 +1,582 @@
+/*
+ * 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.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import com.google.common.collect.AbstractIterator;
+import com.google.common.collect.ImmutableMap;
+
+import org.apache.cassandra.db.Clustering;
+import org.apache.cassandra.db.DataRange;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.DeletionTime;
+import org.apache.cassandra.db.EmptyIterators;
+import org.apache.cassandra.db.filter.ClusteringIndexFilter;
+import org.apache.cassandra.db.filter.ColumnFilter;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.BooleanType;
+import org.apache.cassandra.db.marshal.ByteType;
+import org.apache.cassandra.db.marshal.CompositeType;
+import org.apache.cassandra.db.marshal.DoubleType;
+import org.apache.cassandra.db.marshal.FloatType;
+import org.apache.cassandra.db.marshal.Int32Type;
+import org.apache.cassandra.db.marshal.LongType;
+import org.apache.cassandra.db.marshal.ShortType;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.db.marshal.UUIDType;
+import org.apache.cassandra.db.partitions.AbstractUnfilteredPartitionIterator;
+import org.apache.cassandra.db.partitions.PartitionUpdate;
+import org.apache.cassandra.db.partitions.SingletonUnfilteredPartitionIterator;
+import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
+import org.apache.cassandra.db.rows.AbstractUnfilteredRowIterator;
+import org.apache.cassandra.db.rows.BTreeRow;
+import org.apache.cassandra.db.rows.BufferCell;
+import org.apache.cassandra.db.rows.EncodingStats;
+import org.apache.cassandra.db.rows.Row;
+import org.apache.cassandra.db.rows.Rows;
+import org.apache.cassandra.db.rows.Unfiltered;
+import org.apache.cassandra.db.rows.UnfilteredRowIterator;
+import org.apache.cassandra.db.virtual.model.Column;
+import org.apache.cassandra.db.virtual.walker.RowWalker;
+import org.apache.cassandra.dht.LocalPartitioner;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.schema.ColumnMetadata;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.cassandra.utils.Pair;
+
+import static org.apache.cassandra.db.rows.Cell.NO_DELETION_TIME;
+import static org.apache.cassandra.utils.FBUtilities.camelToSnake;
+
+/**
+ * This is a virtual table that iteratively builds rows using a data set provided by internal collection.
+ * Some metric views might be too large to fit in memory, for example, virtual tables that contain metrics
+ * for all the keyspaces registered in the cluster. Such a technique is also facilitates keeping the low
+ * memory footprint of the virtual tables in general.
+ * <p>
+ * It doesn't require the input data set to be sorted, but it does require that the partition keys are
+ * provided in the order of the partitioner of the table metadata.
+ */
+public class CollectionVirtualTableAdapter<R> implements VirtualTable
+{
+ private static final Pattern ONLY_ALPHABET_PATTERN = Pattern.compile("[^a-zA-Z1-9]");
+ private static final List<Pair<String, String>> knownAbbreviations = Arrays.asList(Pair.create("CAS", "Cas"),
+ Pair.create("CIDR", "Cidr"));
+
+ /** The map of the supported converters for the column types. */
+ private static final Map<Class<?>, ? extends AbstractType<?>> converters =
+ ImmutableMap.<Class<?>, AbstractType<?>>builder()
+ .put(String.class, UTF8Type.instance)
+ .put(Integer.class, Int32Type.instance)
+ .put(Integer.TYPE, Int32Type.instance)
+ .put(Long.class, LongType.instance)
+ .put(Long.TYPE, LongType.instance)
+ .put(Float.class, FloatType.instance)
+ .put(Float.TYPE, FloatType.instance)
+ .put(Double.class, DoubleType.instance)
+ .put(Double.TYPE, DoubleType.instance)
+ .put(Boolean.class, BooleanType.instance)
+ .put(Boolean.TYPE, BooleanType.instance)
+ .put(Byte.class, ByteType.instance)
+ .put(Byte.TYPE, ByteType.instance)
+ .put(Short.class, ShortType.instance)
+ .put(Short.TYPE, ShortType.instance)
+ .put(UUID.class, UUIDType.instance)
+ .build();
+
+ /**
+ * The map is used to avoid getting column metadata for each regular column for each row.
+ */
+ private final ConcurrentHashMap<String, ColumnMetadata> columnMetas = new ConcurrentHashMap<>();
+ private final RowWalker<R> walker;
+ private final Iterable<R> data;
+ private final Function<DecoratedKey, R> decorateKeyToRowExtractor;
+ private final TableMetadata metadata;
+
+ private CollectionVirtualTableAdapter(String keySpaceName,
+ String tableName,
+ String description,
+ RowWalker<R> walker,
+ Iterable<R> data)
+ {
+ this(keySpaceName, tableName, description, walker, data, null);
+ }
+
+ private CollectionVirtualTableAdapter(String keySpaceName,
+ String tableName,
+ String description,
+ RowWalker<R> walker,
+ Iterable<R> data,
+ Function<DecoratedKey, R> keyToRowExtractor)
+ {
+ this.walker = walker;
+ this.data = data;
+ this.metadata = buildMetadata(keySpaceName, tableName, description, walker);
+ this.decorateKeyToRowExtractor = keyToRowExtractor;
+ }
+
+ public static <C, R> CollectionVirtualTableAdapter<R> create(String keySpaceName,
+ String rawTableName,
+ String description,
+ RowWalker<R> walker,
+ Iterable<C> container,
+ Function<C, R> rowFunc)
+ {
+ return new CollectionVirtualTableAdapter<>(keySpaceName,
+ virtualTableNameStyle(rawTableName),
+ description,
+ walker,
+ () -> StreamSupport.stream(container.spliterator(), false)
+ .map(rowFunc).iterator());
+ }
+
+ public static <K, C, R> CollectionVirtualTableAdapter<R> createSinglePartitionedKeyFiltered(String keySpaceName,
+ String rawTableName,
+ String description,
+ RowWalker<R> walker,
+ Map<K, C> map,
+ Predicate<K> mapKeyFilter,
+ BiFunction<K, C, R> rowConverter)
+ {
+ return createSinglePartitioned(keySpaceName, rawTableName, description, walker, map, mapKeyFilter,
+ Objects::nonNull, rowConverter);
+ }
+
+ public static <K, C, R> CollectionVirtualTableAdapter<R> createSinglePartitionedValueFiltered(String keySpaceName,
+ String rawTableName,
+ String description,
+ RowWalker<R> walker,
+ Map<K, C> map,
+ Predicate<C> mapValueFilter,
+ BiFunction<K, C, R> rowConverter)
+ {
+ return createSinglePartitioned(keySpaceName, rawTableName, description, walker, map, key -> true,
+ mapValueFilter, rowConverter);
+ }
+
+ private static <K, C, R> CollectionVirtualTableAdapter<R> createSinglePartitioned(String keySpaceName,
+ String rawTableName,
+ String description,
+ RowWalker<R> walker,
+ Map<K, C> map,
+ Predicate<K> mapKeyFilter,
+ Predicate<C> mapValueFilter,
+ BiFunction<K, C, R> rowConverter)
+ {
+ assert walker.count(Column.Type.PARTITION_KEY) == 1 : "Partition key must be a single column";
+ assert walker.count(Column.Type.CLUSTERING) == 0 : "Clustering columns are not supported";
+
+ AtomicReference<Class<?>> partitionKeyClass = new AtomicReference<>();
+ walker.visitMeta(new RowWalker.MetadataVisitor()
+ {
+ @Override
+ public <T> void accept(Column.Type type, String columnName, Class<T> clazz)
+ {
+ if (type == Column.Type.PARTITION_KEY)
+ partitionKeyClass.set(clazz);
+ }
+ });
+
+ return new CollectionVirtualTableAdapter<>(keySpaceName,
+ virtualTableNameStyle(rawTableName),
+ description,
+ walker,
+ () -> map.entrySet()
+ .stream()
+ .filter(e -> mapKeyFilter.test(e.getKey()))
+ .filter(e -> mapValueFilter.test(e.getValue()))
+ .map(e -> rowConverter.apply(e.getKey(), e.getValue()))
+ .iterator(),
+ decoratedKey ->
+ {
+ K partitionKey = compose(converters.get(partitionKeyClass.get()),
+ decoratedKey.getKey());
+ boolean keyRequired = mapKeyFilter.test(partitionKey);
+ if (!keyRequired)
+ return null;
+
+ C value = map.get(partitionKey);
+ return mapValueFilter.test(value) ? rowConverter.apply(
+ partitionKey, value) : null;
+ });
+ }
+
+ public static String virtualTableNameStyle(String camel)
+ {
+ // Process sub names in the full metrics group name separately and then join them.
+ // For example: "ClientRequest.Write-EACH_QUORUM" will be converted to "client_request_write_each_quorum".
+ String[] subNames = ONLY_ALPHABET_PATTERN.matcher(camel).replaceAll(".").split("\\.");
+ return Arrays.stream(subNames)
+ .map(CollectionVirtualTableAdapter::camelToSnakeWithAbbreviations)
+ .reduce((a, b) -> a + '_' + b)
+ .orElseThrow(() -> new IllegalArgumentException("Invalid table name: " + camel));
+ }
+
+ private static String camelToSnakeWithAbbreviations(String camel)
+ {
+ Pattern pattern = Pattern.compile("^[A-Z1-9_]+$");
+ // Contains only uppercase letters, numbers and underscores, so it's already snake case.
+ if (pattern.matcher(camel).matches())
+ return camel.toLowerCase();
+
+ // Some special cases must be handled manually.
+ String modifiedCamel = camel;
+ for (Pair<String, String> replacement : knownAbbreviations)
+ modifiedCamel = modifiedCamel.replace(replacement.left, replacement.right);
+
+ return camelToSnake(modifiedCamel);
+ }
+
+ private TableMetadata buildMetadata(String keyspaceName, String tableName, String description, RowWalker<R> walker)
+ {
+ TableMetadata.Builder builder = TableMetadata.builder(keyspaceName, tableName)
+ .comment(description)
+ .kind(TableMetadata.Kind.VIRTUAL);
+
+ List<AbstractType<?>> partitionKeyTypes = new ArrayList<>(walker.count(Column.Type.PARTITION_KEY));
+ walker.visitMeta(new RowWalker.MetadataVisitor()
+ {
+ @Override
+ public <T> void accept(Column.Type type, String columnName, Class<T> clazz)
+ {
+ switch (type)
+ {
+ case PARTITION_KEY:
+ partitionKeyTypes.add(converters.get(clazz));
+ builder.addPartitionKeyColumn(columnName, converters.get(clazz));
+ break;
+ case CLUSTERING:
+ builder.addClusteringColumn(columnName, converters.get(clazz));
+ break;
+ case REGULAR:
+ builder.addRegularColumn(columnName, converters.get(clazz));
+ break;
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+ });
+
+ if (partitionKeyTypes.size() == 1)
+ builder.partitioner(new LocalPartitioner(partitionKeyTypes.get(0)));
+ else if (partitionKeyTypes.size() > 1)
+ builder.partitioner(new LocalPartitioner(CompositeType.getInstance(partitionKeyTypes)));
+
+ return builder.build();
+ }
+
+ @Override
+ public UnfilteredPartitionIterator select(DecoratedKey partitionKey,
+ ClusteringIndexFilter clusteringFilter,
+ ColumnFilter columnFilter)
+ {
+ if (!data.iterator().hasNext())
+ return EmptyIterators.unfilteredPartition(metadata);
+
+ NavigableMap<Clustering<?>, Row> rows = new TreeMap<>(metadata.comparator);
+ Stream<CollectionRow> stream;
+ if (decorateKeyToRowExtractor == null)
+ {
+ // The benchmark shows that if we continuously read the data from the virtual table e.g. by metric name,
+ // then the parallel stream is slightly faster to get the first result, but for continuous reads it gives us
+ // a higher GC pressure. The sequential stream is slightly slower to get the first result, but it has the
+ // same throughput as the parallel stream, and it gives us less GC pressure.
+ // See the details in the benchmark: https://gist.github.com/Mmuzaf/80c73b7f9441ff21f6d22efe5746541a
+ stream = StreamSupport.stream(data.spliterator(), false)
+ .map(row -> makeRow(row, columnFilter))
+ .filter(cr -> partitionKey.equals(cr.key.get()))
+ .filter(cr -> clusteringFilter.selects(cr.clustering));
+ }
+ else
+ {
+ R row = decorateKeyToRowExtractor.apply(partitionKey);
+ if (row == null)
+ return EmptyIterators.unfilteredPartition(metadata);
+ stream = Stream.of(makeRow(row, columnFilter));
+ }
+
+ // If there are no clustering columns, we've found a unique partition that matches the partition key,
+ // so we can stop the stream without looping through all the rows.
+ if (walker.count(Column.Type.CLUSTERING) == 0)
+ stream.findFirst().ifPresent(cr -> rows.put(cr.clustering, cr.rowSup.get()));
+ else
+ stream.forEach(cr -> rows.put(cr.clustering, cr.rowSup.get()));
+
+ return new SingletonUnfilteredPartitionIterator(new DataRowUnfilteredIterator(partitionKey,
+ clusteringFilter,
+ columnFilter,
+ rows));
+ }
+
+ @Override
+ public UnfilteredPartitionIterator select(DataRange dataRange, ColumnFilter columnFilter)
+ {
+ return createPartitionIterator(metadata, new AbstractIterator<>()
+ {
+ private final Iterator<? extends UnfilteredRowIterator> partitions = buildDataRangeIterator(dataRange,
+ columnFilter);
+
+ @Override
+ protected UnfilteredRowIterator computeNext()
+ {
+ return partitions.hasNext() ? partitions.next() : endOfData();
+ }
+
+ private Iterator<? extends UnfilteredRowIterator> buildDataRangeIterator(DataRange dataRange,
+ ColumnFilter columnFilter)
+ {
+ NavigableMap<DecoratedKey, NavigableMap<Clustering<?>, Row>> partitionMap = new ConcurrentSkipListMap<>(DecoratedKey.comparator);
+ StreamSupport.stream(data.spliterator(), true)
+ .map(row -> makeRow(row, columnFilter))
+ .filter(cr -> dataRange.keyRange().contains(cr.key.get()))
+ .forEach(cr -> partitionMap.computeIfAbsent(cr.key.get(),
+ key -> new TreeMap<>(metadata.comparator))
+ .put(cr.clustering, cr.rowSup.get()));
+
+ return partitionMap.entrySet().stream().map(
+ e -> new DataRowUnfilteredIterator(e.getKey(), dataRange.clusteringIndexFilter(e.getKey()), columnFilter,
+ e.getValue())).iterator();
+ }
+ });
+ }
+
+ private class DataRowUnfilteredIterator extends AbstractUnfilteredRowIterator
+ {
+ private final Iterator<Row> rows;
+
+ public DataRowUnfilteredIterator(DecoratedKey partitionKey,
+ ClusteringIndexFilter indexFilter,
+ ColumnFilter columnFilter,
+ NavigableMap<Clustering<?>, Row> data)
+ {
+ super(CollectionVirtualTableAdapter.this.metadata,
+ partitionKey,
+ DeletionTime.LIVE,
+ columnFilter.queriedColumns(),
+ Rows.EMPTY_STATIC_ROW,
+ indexFilter.isReversed(),
+ EncodingStats.NO_STATS);
+ this.rows = indexFilter.isReversed() ? data.descendingMap().values().iterator() : data.values().iterator();
+ }
+
+ @Override
+ protected Unfiltered computeNext()
+ {
+ return rows.hasNext() ? rows.next() : endOfData();
+ }
+ }
+
+ private CollectionRow makeRow(R row, ColumnFilter columnFilter)
+ {
+ assert metadata.partitionKeyColumns().size() == walker.count(Column.Type.PARTITION_KEY) :
+ "Invalid number of partition key columns";
+ assert metadata.clusteringColumns().size() == walker.count(Column.Type.CLUSTERING) :
+ "Invalid number of clustering columns";
+
+ Map<Column.Type, Object[]> fiterable = new EnumMap<>(Column.Type.class);
+ fiterable.put(Column.Type.PARTITION_KEY, new Object[metadata.partitionKeyColumns().size()]);
+ if (walker.count(Column.Type.CLUSTERING) > 0)
+ fiterable.put(Column.Type.CLUSTERING, new Object[metadata.clusteringColumns().size()]);
+
+ Map<ColumnMetadata, Supplier<?>> cells = new HashMap<>();
+
+ walker.visitRow(row, new RowWalker.RowMetadataVisitor()
+ {
+ private int pIdx, cIdx = 0;
+
+ @Override
+ public <T> void accept(Column.Type type, String columnName, Class<T> clazz, Supplier<T> value)
+ {
+ switch (type)
+ {
+ case PARTITION_KEY:
+ fiterable.get(type)[pIdx++] = value.get();
+ break;
+ case CLUSTERING:
+ fiterable.get(type)[cIdx++] = value.get();
+ break;
+ case REGULAR:
+ {
+ if (columnFilter.equals(ColumnFilter.NONE))
+ break;
+
+ // Push down the column filter to the walker, so we don't have to process the value if it's not queried
+ ColumnMetadata cm = columnMetas.computeIfAbsent(columnName, name -> metadata.getColumn(ByteBufferUtil.bytes(name)));
+ if (columnFilter.queriedColumns().contains(cm))
+ cells.put(cm, value);
+
+ break;
+ }
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+ });
+
+ return new CollectionRow(() -> makeRowKey(metadata, fiterable.get(Column.Type.PARTITION_KEY)),
+ makeRowClustering(metadata, fiterable.get(Column.Type.CLUSTERING)),
+ clustering ->
+ {
+ Row.Builder rowBuilder = BTreeRow.unsortedBuilder();
+ rowBuilder.newRow(clustering);
+ cells.forEach((column, value) -> {
+ Object valueObj = value.get();
+ if (valueObj == null)
+ return;
+ rowBuilder.addCell(BufferCell.live(column, NO_DELETION_TIME,
+ decompose(column.type, valueObj)));
+ });
+ return rowBuilder.build();
+ });
+ }
+
+ private static class CollectionRow
+ {
+ private final Supplier<DecoratedKey> key;
+ private final Clustering<?> clustering;
+ private final Supplier<Row> rowSup;
+
+ public CollectionRow(Supplier<DecoratedKey> key, Clustering<?> clustering, Function<Clustering<?>, Row> rowSup)
+ {
+ this.key = new ValueHolder<>(key);
+ this.clustering = clustering;
+ this.rowSup = new ValueHolder<>(() -> rowSup.apply(clustering));
+ }
+
+ private static class ValueHolder<T> implements Supplier<T>
+ {
+ private final Supplier<T> delegate;
+ private volatile T value;
+
+ public ValueHolder(Supplier<T> delegate)
+ {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public T get()
+ {
+ if (value == null)
+ value = delegate.get();
+
+ return value;
+ }
+ }
+ }
+
+ private static Clustering<?> makeRowClustering(TableMetadata metadata, Object... clusteringValues)
+ {
+ if (clusteringValues == null || clusteringValues.length == 0)
+ return Clustering.EMPTY;
+
+ ByteBuffer[] clusteringByteBuffers = new ByteBuffer[clusteringValues.length];
+ for (int i = 0; i < clusteringValues.length; i++)
+ clusteringByteBuffers[i] = decompose(metadata.clusteringColumns().get(i).type, clusteringValues[i]);
+ return Clustering.make(clusteringByteBuffers);
+ }
+
+ /**
+ * @param table the table metadata
+ * @param partitionKeyValues the partition key values
+ * @return the decorated key
+ */
+ private static DecoratedKey makeRowKey(TableMetadata table, Object... partitionKeyValues)
+ {
+ ByteBuffer key;
+ if (partitionKeyValues.length > 1)
+ key = ((CompositeType) table.partitionKeyType).decompose(partitionKeyValues);
+ else
+ key = decompose(table.partitionKeyType, partitionKeyValues[0]);
+ return table.partitioner.decorateKey(key);
+ }
+
+ private static UnfilteredPartitionIterator createPartitionIterator(TableMetadata metadata,
+ Iterator<UnfilteredRowIterator> partitions)
+ {
+ return new AbstractUnfilteredPartitionIterator()
+ {
+ public UnfilteredRowIterator next()
+ {
+ return partitions.next();
+ }
+
+ public boolean hasNext()
+ {
+ return partitions.hasNext();
+ }
+
+ public TableMetadata metadata()
+ {
+ return metadata;
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> ByteBuffer decompose(AbstractType<?> type, T value)
+ {
+ return ((AbstractType<T>) type).decompose(value);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> T compose(AbstractType<?> type, ByteBuffer value)
+ {
+ return (T) type.compose(value);
+ }
+
+ @Override
+ public TableMetadata metadata()
+ {
+ return metadata;
+ }
+
+ @Override
+ public void apply(PartitionUpdate update)
+ {
+ throw new InvalidRequestException("Modification is not supported by table " + metadata);
+ }
+
+ @Override
+ public void truncate()
+ {
+ throw new InvalidRequestException("Truncate is not supported by table " + metadata);
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
index 7b7e28e30a..973be20304 100644
--- a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
+++ b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
@@ -19,8 +19,11 @@ package org.apache.cassandra.db.virtual;
import com.google.common.collect.ImmutableList;
+import org.apache.cassandra.db.virtual.model.ThreadPoolRow;
+import org.apache.cassandra.db.virtual.walker.ThreadPoolRowWalker;
import org.apache.cassandra.index.sai.virtual.StorageAttachedIndexTables;
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
import static org.apache.cassandra.schema.SchemaConstants.VIRTUAL_VIEWS;
public final class SystemViewsKeyspace extends VirtualKeyspace
@@ -35,7 +38,13 @@ public final class SystemViewsKeyspace extends VirtualKeyspace
.add(new SettingsTable(VIRTUAL_VIEWS))
.add(new SystemPropertiesTable(VIRTUAL_VIEWS))
.add(new SSTableTasksTable(VIRTUAL_VIEWS))
- .add(new ThreadPoolsTable(VIRTUAL_VIEWS))
+ // Fully backward/forward compatible with the legace ThreadPoolsTable under the same "system_views.thread_pools" name.
+ .add(CollectionVirtualTableAdapter.create(VIRTUAL_VIEWS,
+ "thread_pools",
+ "Thread pool metrics for all thread pools",
+ new ThreadPoolRowWalker(),
+ Metrics.allThreadPoolMetrics(),
+ ThreadPoolRow::new))
.add(new InternodeOutboundTable(VIRTUAL_VIEWS))
.add(new InternodeInboundTable(VIRTUAL_VIEWS))
.add(new PendingHintsTable(VIRTUAL_VIEWS))
diff --git a/src/java/org/apache/cassandra/db/virtual/ThreadPoolsTable.java b/src/java/org/apache/cassandra/db/virtual/ThreadPoolsTable.java
deleted file mode 100644
index 4bd4624d58..0000000000
--- a/src/java/org/apache/cassandra/db/virtual/ThreadPoolsTable.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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 org.apache.cassandra.db.DecoratedKey;
-import org.apache.cassandra.db.marshal.Int32Type;
-import org.apache.cassandra.db.marshal.LongType;
-import org.apache.cassandra.db.marshal.UTF8Type;
-import org.apache.cassandra.dht.LocalPartitioner;
-import org.apache.cassandra.metrics.ThreadPoolMetrics;
-import org.apache.cassandra.schema.TableMetadata;
-
-import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
-
-final class ThreadPoolsTable extends AbstractVirtualTable
-{
- private static final String NAME = "name";
- private static final String ACTIVE_TASKS = "active_tasks";
- private static final String ACTIVE_TASKS_LIMIT = "active_tasks_limit";
- private static final String PENDING_TASKS = "pending_tasks";
- private static final String COMPLETED_TASKS = "completed_tasks";
- private static final String BLOCKED_TASKS = "blocked_tasks";
- private static final String BLOCKED_TASKS_ALL_TIME = "blocked_tasks_all_time";
- private static final String CORE_POOL_SIZE = "core_pool_size";
- private static final String MAX_POOL_SIZE = "max_pool_size";
- private static final String MAX_TASKS_QUEUED = "max_tasks_queued";
-
- ThreadPoolsTable(String keyspace)
- {
- super(TableMetadata.builder(keyspace, "thread_pools")
- .kind(TableMetadata.Kind.VIRTUAL)
- .partitioner(new LocalPartitioner(UTF8Type.instance))
- .addPartitionKeyColumn(NAME, UTF8Type.instance)
- .addRegularColumn(ACTIVE_TASKS, Int32Type.instance)
- .addRegularColumn(ACTIVE_TASKS_LIMIT, Int32Type.instance)
- .addRegularColumn(PENDING_TASKS, Int32Type.instance)
- .addRegularColumn(COMPLETED_TASKS, LongType.instance)
- .addRegularColumn(BLOCKED_TASKS, LongType.instance)
- .addRegularColumn(BLOCKED_TASKS_ALL_TIME, LongType.instance)
- .addRegularColumn(CORE_POOL_SIZE, Int32Type.instance)
- .addRegularColumn(MAX_POOL_SIZE, Int32Type.instance)
- .addRegularColumn(MAX_TASKS_QUEUED, Int32Type.instance)
- .build());
- }
-
- @Override
- public DataSet data(DecoratedKey partitionKey)
- {
- String poolName = UTF8Type.instance.compose(partitionKey.getKey());
-
- SimpleDataSet result = new SimpleDataSet(metadata());
- Metrics.getThreadPoolMetrics(poolName)
- .ifPresent(metrics -> addRow(result, metrics));
- return result;
- }
-
- @Override
- public DataSet data()
- {
- SimpleDataSet result = new SimpleDataSet(metadata());
- Metrics.allThreadPoolMetrics()
- .forEach(metrics -> addRow(result, metrics));
- return result;
- }
-
- private void addRow(SimpleDataSet dataSet, ThreadPoolMetrics metrics)
- {
- dataSet.row(metrics.poolName)
- .column(ACTIVE_TASKS, metrics.activeTasks.getValue())
- .column(ACTIVE_TASKS_LIMIT, metrics.maxPoolSize.getValue())
- .column(PENDING_TASKS, metrics.pendingTasks.getValue())
- .column(COMPLETED_TASKS, metrics.completedTasks.getValue())
- .column(BLOCKED_TASKS, metrics.currentBlocked.getCount())
- .column(BLOCKED_TASKS_ALL_TIME, metrics.totalBlocked.getCount())
- .column(CORE_POOL_SIZE, metrics.corePoolSize.getValue())
- .column(MAX_POOL_SIZE, metrics.maxPoolSize.getValue())
- .column(MAX_TASKS_QUEUED, metrics.maxTasksQueued.getValue());
- }
-}
diff --git a/src/java/org/apache/cassandra/db/virtual/VirtualKeyspace.java b/src/java/org/apache/cassandra/db/virtual/VirtualKeyspace.java
index 675021547c..044c11476b 100644
--- a/src/java/org/apache/cassandra/db/virtual/VirtualKeyspace.java
+++ b/src/java/org/apache/cassandra/db/virtual/VirtualKeyspace.java
@@ -18,6 +18,9 @@
package org.apache.cassandra.db.virtual;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
@@ -38,6 +41,15 @@ public class VirtualKeyspace
this.name = name;
this.tables = ImmutableList.copyOf(tables);
+ List<String> duplicates = tables.stream()
+ .map(VirtualTable::name)
+ .distinct()
+ .filter(entry -> Collections.frequency(tables, entry) > 1)
+ .collect(Collectors.toList());
+
+ if (!duplicates.isEmpty())
+ throw new IllegalArgumentException(String.format("Duplicate table names in virtual keyspace %s: %s", name, duplicates));
+
metadata = KeyspaceMetadata.virtual(name, Tables.of(Iterables.transform(tables, VirtualTable::metadata)));
}
diff --git a/src/java/org/apache/cassandra/db/virtual/model/Column.java b/src/java/org/apache/cassandra/db/virtual/model/Column.java
new file mode 100644
index 0000000000..6d8f329ad7
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/model/Column.java
@@ -0,0 +1,64 @@
+/*
+ * 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.model;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.apache.cassandra.db.virtual.walker.RowWalker;
+
+/**
+ * Annotation to mark a walk order for a {@link RowWalker}. Annotation is used on a method that returns a value and
+ * represents a column value for a row. The columns order is defined by the column type and the column name
+ * in lexicographical order (case-insensitive) and the same way as in CQL (partition key columns first,
+ * then clustering columns, then regular columns).
+ * <p>
+ * The column {@link Column.Type} type is the same as the existing CQL {@code ColumnMetadata.Kind} and is used to
+ * define the column types for the virtual tables.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.METHOD)
+public @interface Column
+{
+ /**
+ * The type of the column
+ */
+ Type type() default Type.REGULAR;
+
+ /**
+ * Be sure the order of the enum values is preserved as they are used in ordinal comparisons for the virtual tables.
+ */
+ enum Type
+ {
+ /**
+ * The column is a partition key column.
+ */
+ PARTITION_KEY,
+ /**
+ * The column is a clustering column.
+ */
+ CLUSTERING,
+ /**
+ * The column is a regular column.
+ */
+ REGULAR
+ }
+}
diff --git a/src/java/org/apache/cassandra/metrics/PaxosMetrics.java b/src/java/org/apache/cassandra/db/virtual/model/CounterMetricRow.java
similarity index 56%
copy from src/java/org/apache/cassandra/metrics/PaxosMetrics.java
copy to src/java/org/apache/cassandra/db/virtual/model/CounterMetricRow.java
index 1e1ba89ba2..81d32ef990 100644
--- a/src/java/org/apache/cassandra/metrics/PaxosMetrics.java
+++ b/src/java/org/apache/cassandra/db/virtual/model/CounterMetricRow.java
@@ -16,18 +16,42 @@
* limitations under the License.
*/
-package org.apache.cassandra.metrics;
+package org.apache.cassandra.db.virtual.model;
import com.codahale.metrics.Counter;
-import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metric;
import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
-public class PaxosMetrics
+/**
+ * Counter metric representation for a {@link org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter}.
+ */
+public class CounterMetricRow
{
- private static final MetricNameFactory factory = new DefaultNameFactory("Paxos");
- public static final Counter linearizabilityViolations = Metrics.counter(factory.createMetricName("LinearizabilityViolations"));
- public static final Meter repairPaxosTopologyRetries = Metrics.meter(factory.createMetricName("RepairPaxosTopologyRetries"));
+ private final String key;
+ private final Counter value;
+
+ public CounterMetricRow(String key, Metric value)
+ {
+ this.key = key;
+ this.value = (Counter) value;
+ }
+
+ @Column(type = Column.Type.PARTITION_KEY)
+ public String name()
+ {
+ return key;
+ }
+
+ @Column
+ public String scope()
+ {
+ return Metrics.getMetricScope(key);
+ }
- public static void initialize() {}
+ @Column
+ public long value()
+ {
+ return value.getCount();
+ }
}
diff --git a/src/java/org/apache/cassandra/metrics/BatchMetrics.java b/src/java/org/apache/cassandra/db/virtual/model/GaugeMetricRow.java
similarity index 52%
copy from src/java/org/apache/cassandra/metrics/BatchMetrics.java
copy to src/java/org/apache/cassandra/db/virtual/model/GaugeMetricRow.java
index 9bea162116..11c7677d2a 100644
--- a/src/java/org/apache/cassandra/metrics/BatchMetrics.java
+++ b/src/java/org/apache/cassandra/db/virtual/model/GaugeMetricRow.java
@@ -15,24 +15,44 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.cassandra.metrics;
-import com.codahale.metrics.Histogram;
+package org.apache.cassandra.db.virtual.model;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Metric;
import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.getGaugeValue;
-public class BatchMetrics
+/**
+ * Gauge metric representation for a {@link org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter}.
+ */
+public class GaugeMetricRow
{
- private static final MetricNameFactory factory = new DefaultNameFactory("Batch");
+ private final String key;
+ private final Gauge<?> value;
+
+ public GaugeMetricRow(String key, Metric value)
+ {
+ this.key = key;
+ this.value = (Gauge<?>) value;
+ }
- public final Histogram partitionsPerLoggedBatch;
- public final Histogram partitionsPerUnloggedBatch;
- public final Histogram partitionsPerCounterBatch;
+ @Column(type = Column.Type.PARTITION_KEY)
+ public String name()
+ {
+ return key;
+ }
+
+ @Column
+ public String scope()
+ {
+ return Metrics.getMetricScope(key);
+ }
- public BatchMetrics()
+ @Column
+ public String value()
{
- partitionsPerLoggedBatch = Metrics.histogram(factory.createMetricName("PartitionsPerLoggedBatch"), false);
- partitionsPerUnloggedBatch = Metrics.histogram(factory.createMetricName("PartitionsPerUnloggedBatch"), false);
- partitionsPerCounterBatch = Metrics.histogram(factory.createMetricName("PartitionsPerCounterBatch"), false);
+ return getGaugeValue(value);
}
}
diff --git a/src/java/org/apache/cassandra/db/virtual/model/HistogramMetricRow.java b/src/java/org/apache/cassandra/db/virtual/model/HistogramMetricRow.java
new file mode 100644
index 0000000000..057872b7dd
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/model/HistogramMetricRow.java
@@ -0,0 +1,100 @@
+/*
+ * 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.model;
+
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.Snapshot;
+
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
+
+/**
+ * Historgam metric representation for a {@link org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter}.
+ */
+public class HistogramMetricRow
+{
+ private final String key;
+ private final Snapshot value;
+
+ public HistogramMetricRow(String key, Metric value)
+ {
+ this.key = key;
+ this.value = ((Histogram) value).getSnapshot();
+ }
+
+ @Column
+ public String scope()
+ {
+ return Metrics.getMetricScope(key);
+ }
+
+ @Column(type = Column.Type.PARTITION_KEY)
+ public String name()
+ {
+ return key;
+ }
+
+ @Column
+ public double p75th()
+ {
+ return value.get75thPercentile();
+ }
+
+ @Column
+ public double p95th()
+ {
+ return value.get95thPercentile();
+ }
+
+ @Column
+ public double p98th()
+ {
+ return value.get98thPercentile();
+ }
+
+ @Column
+ public double p99th()
+ {
+ return value.get99thPercentile();
+ }
+
+ @Column
+ public double p999th()
+ {
+ return value.get999thPercentile();
+ }
+
+ @Column
+ public long max()
+ {
+ return value.getMax();
+ }
+
+ @Column
+ public double mean()
+ {
+ return value.getMean();
+ }
+
+ @Column
+ public long min()
+ {
+ return value.getMin();
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/model/MeterMetricRow.java b/src/java/org/apache/cassandra/db/virtual/model/MeterMetricRow.java
new file mode 100644
index 0000000000..e4857a5a39
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/model/MeterMetricRow.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.model;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metric;
+
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
+
+/**
+ * Meter metric representation for a {@link org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter}.
+ */
+public class MeterMetricRow
+{
+ private final String key;
+ private final Meter value;
+
+ public MeterMetricRow(String key, Metric value)
+ {
+ this.key = key;
+ this.value = (Meter) value;
+ }
+
+ @Column(type = Column.Type.PARTITION_KEY)
+ public String name()
+ {
+ return key;
+ }
+
+ @Column
+ public String scope()
+ {
+ return Metrics.getMetricScope(key);
+ }
+
+ @Column
+ public long count()
+ {
+ return value.getCount();
+ }
+
+ @Column
+ public double fifteenMinuteRate()
+ {
+ return value.getFifteenMinuteRate();
+ }
+
+ @Column
+ public double fiveMinuteRate()
+ {
+ return value.getFiveMinuteRate();
+ }
+
+ @Column
+ public double meanRate()
+ {
+ return value.getMeanRate();
+ }
+
+ @Column
+ public double oneMinuteRate()
+ {
+ return value.getOneMinuteRate();
+ }
+}
diff --git a/src/java/org/apache/cassandra/metrics/PaxosMetrics.java b/src/java/org/apache/cassandra/db/virtual/model/MetricGroupRow.java
similarity index 56%
copy from src/java/org/apache/cassandra/metrics/PaxosMetrics.java
copy to src/java/org/apache/cassandra/db/virtual/model/MetricGroupRow.java
index 1e1ba89ba2..b7326bad3e 100644
--- a/src/java/org/apache/cassandra/metrics/PaxosMetrics.java
+++ b/src/java/org/apache/cassandra/db/virtual/model/MetricGroupRow.java
@@ -16,18 +16,32 @@
* limitations under the License.
*/
-package org.apache.cassandra.metrics;
+package org.apache.cassandra.db.virtual.model;
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Meter;
+import static org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter.virtualTableNameStyle;
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.METRICS_GROUP_POSTFIX;
-import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
-
-public class PaxosMetrics
+/**
+ * Metric group row representation for a {@link org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter}.
+ */
+public class MetricGroupRow
{
- private static final MetricNameFactory factory = new DefaultNameFactory("Paxos");
- public static final Counter linearizabilityViolations = Metrics.counter(factory.createMetricName("LinearizabilityViolations"));
- public static final Meter repairPaxosTopologyRetries = Metrics.meter(factory.createMetricName("RepairPaxosTopologyRetries"));
+ private final String group;
+
+ public MetricGroupRow(String group)
+ {
+ this.group = group;
+ }
+
+ @Column(type = Column.Type.PARTITION_KEY)
+ public String groupName()
+ {
+ return group;
+ }
- public static void initialize() {}
+ @Column
+ public String virtualTable()
+ {
+ return METRICS_GROUP_POSTFIX.apply(virtualTableNameStyle(group));
+ }
}
diff --git a/src/java/org/apache/cassandra/db/virtual/model/MetricRow.java b/src/java/org/apache/cassandra/db/virtual/model/MetricRow.java
new file mode 100644
index 0000000000..88ed98146b
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/model/MetricRow.java
@@ -0,0 +1,80 @@
+/*
+ * 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.model;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.Timer;
+import org.apache.cassandra.metrics.CassandraMetricsRegistry;
+
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
+
+/**
+ * Metric row representation for a {@link org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter}.
+ */
+public class MetricRow
+{
+ private final String key;
+ private final Metric metricEntry;
+
+ public MetricRow(String key, Metric metricEntry)
+ {
+ this.key = key;
+ this.metricEntry = metricEntry;
+ }
+
+ @Column(type = Column.Type.PARTITION_KEY)
+ public String name()
+ {
+ return key;
+ }
+
+ @Column
+ public String scope()
+ {
+ return Metrics.getMetricScope(key);
+ }
+
+ @Column
+ public String type()
+ {
+ Class<?> clazz = metricEntry.getClass();
+ if (Counter.class.isAssignableFrom(clazz))
+ return "counter";
+ else if (Gauge.class.isAssignableFrom(clazz))
+ return "gauge";
+ else if (Histogram.class.isAssignableFrom(clazz))
+ return "histogram";
+ else if (Meter.class.isAssignableFrom(clazz))
+ return "meter";
+ else if (Timer.class.isAssignableFrom(clazz))
+ return "timer";
+ else
+ throw new IllegalStateException("Unknown metric type: " + metricEntry.getClass());
+ }
+
+ @Column
+ public String value()
+ {
+ return CassandraMetricsRegistry.getValueAsString(metricEntry);
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/model/ThreadPoolRow.java b/src/java/org/apache/cassandra/db/virtual/model/ThreadPoolRow.java
new file mode 100644
index 0000000000..a14cf19e21
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/model/ThreadPoolRow.java
@@ -0,0 +1,94 @@
+/*
+ * 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.model;
+
+import org.apache.cassandra.metrics.ThreadPoolMetrics;
+
+/**
+ * Thread pool metrics representation for a {@link org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter}.
+ */
+public class ThreadPoolRow
+{
+ private final ThreadPoolMetrics entry;
+
+ public ThreadPoolRow(ThreadPoolMetrics entry)
+ {
+ this.entry = entry;
+ }
+
+ @Column(type = Column.Type.PARTITION_KEY)
+ public String name()
+ {
+ return entry.poolName;
+ }
+
+ @Column
+ public Integer activeTasks()
+ {
+ return entry.activeTasks.getValue();
+ }
+
+ @Column
+ public Integer activeTasksLimit()
+ {
+ return entry.maxPoolSize.getValue();
+ }
+
+ @Column
+ public Integer pendingTasks()
+ {
+ return entry.pendingTasks.getValue();
+ }
+
+ @Column
+ public Long completedTasks()
+ {
+ return entry.completedTasks.getValue();
+ }
+
+ @Column
+ public long blockedTasks()
+ {
+ return entry.currentBlocked.getCount();
+ }
+
+ @Column
+ public long blockedTasksAllTime()
+ {
+ return entry.totalBlocked.getCount();
+ }
+
+ @Column
+ public Integer corePoolSize()
+ {
+ return entry.corePoolSize.getValue();
+ }
+
+ @Column
+ public Integer maxPoolSize()
+ {
+ return entry.maxPoolSize.getValue();
+ }
+
+ @Column
+ public Integer maxTasksQueued()
+ {
+ return entry.maxTasksQueued.getValue();
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/model/TimerMetricRow.java b/src/java/org/apache/cassandra/db/virtual/model/TimerMetricRow.java
new file mode 100644
index 0000000000..4fd4b81758
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/model/TimerMetricRow.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.model;
+
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.Timer;
+
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
+
+/**
+ * Timer metric representation for a {@link org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter}.
+ */
+public class TimerMetricRow
+{
+ private final String key;
+ private final Timer value;
+
+ public TimerMetricRow(String key, Metric value)
+ {
+ this.key = key;
+ this.value = (Timer) value;
+ }
+
+ @Column
+ public String scope()
+ {
+ return Metrics.getMetricScope(key);
+ }
+
+ @Column(type = Column.Type.PARTITION_KEY)
+ public String name()
+ {
+ return key;
+ }
+
+ @Column
+ public long count()
+ {
+ return value.getCount();
+ }
+
+ @Column
+ public double fifteenMinuteRate()
+ {
+ return value.getFifteenMinuteRate();
+ }
+
+ @Column
+ public double fiveMinuteRate()
+ {
+ return value.getFiveMinuteRate();
+ }
+
+ @Column
+ public double meanRate()
+ {
+ return value.getMeanRate();
+ }
+
+ @Column
+ public double oneMinuteRate()
+ {
+ return value.getOneMinuteRate();
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/walker/CounterMetricRowWalker.java b/src/java/org/apache/cassandra/db/virtual/walker/CounterMetricRowWalker.java
new file mode 100644
index 0000000000..8f6876afa3
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/walker/CounterMetricRowWalker.java
@@ -0,0 +1,61 @@
+/*
+ * 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
+ *
+ * https://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.walker;
+
+import org.apache.cassandra.db.virtual.model.Column;
+import org.apache.cassandra.db.virtual.model.CounterMetricRow;
+
+/**
+ * The {@link org.apache.cassandra.db.virtual.model.CounterMetricRow} row metadata and data walker.
+ *
+ * @see org.apache.cassandra.db.virtual.model.CounterMetricRow
+ */
+public class CounterMetricRowWalker implements RowWalker<CounterMetricRow>
+{
+ @Override
+ public void visitMeta(MetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class);
+ visitor.accept(Column.Type.REGULAR, "value", Long.TYPE);
+ }
+
+ @Override
+ public void visitRow(CounterMetricRow row, RowMetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class, row::name);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class, row::scope);
+ visitor.accept(Column.Type.REGULAR, "value", Long.TYPE, row::value);
+ }
+
+ @Override
+ public int count(Column.Type type)
+ {
+ switch (type)
+ {
+ case PARTITION_KEY:
+ return 1;
+ case CLUSTERING:
+ return 0;
+ case REGULAR:
+ return 2;
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/walker/GaugeMetricRowWalker.java b/src/java/org/apache/cassandra/db/virtual/walker/GaugeMetricRowWalker.java
new file mode 100644
index 0000000000..e510f33b5d
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/walker/GaugeMetricRowWalker.java
@@ -0,0 +1,61 @@
+/*
+ * 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
+ *
+ * https://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.walker;
+
+import org.apache.cassandra.db.virtual.model.Column;
+import org.apache.cassandra.db.virtual.model.GaugeMetricRow;
+
+/**
+ * The {@link org.apache.cassandra.db.virtual.model.GaugeMetricRow} row metadata and data walker.
+ *
+ * @see org.apache.cassandra.db.virtual.model.GaugeMetricRow
+ */
+public class GaugeMetricRowWalker implements RowWalker<GaugeMetricRow>
+{
+ @Override
+ public void visitMeta(MetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class);
+ visitor.accept(Column.Type.REGULAR, "value", String.class);
+ }
+
+ @Override
+ public void visitRow(GaugeMetricRow row, RowMetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class, row::name);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class, row::scope);
+ visitor.accept(Column.Type.REGULAR, "value", String.class, row::value);
+ }
+
+ @Override
+ public int count(Column.Type type)
+ {
+ switch (type)
+ {
+ case PARTITION_KEY:
+ return 1;
+ case CLUSTERING:
+ return 0;
+ case REGULAR:
+ return 2;
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/walker/HistogramMetricRowWalker.java b/src/java/org/apache/cassandra/db/virtual/walker/HistogramMetricRowWalker.java
new file mode 100644
index 0000000000..9347a0a715
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/walker/HistogramMetricRowWalker.java
@@ -0,0 +1,75 @@
+/*
+ * 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
+ *
+ * https://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.walker;
+
+import org.apache.cassandra.db.virtual.model.Column;
+import org.apache.cassandra.db.virtual.model.HistogramMetricRow;
+
+/**
+ * The {@link org.apache.cassandra.db.virtual.model.HistogramMetricRow} row metadata and data walker.
+ *
+ * @see org.apache.cassandra.db.virtual.model.HistogramMetricRow
+ */
+public class HistogramMetricRowWalker implements RowWalker<HistogramMetricRow>
+{
+ @Override
+ public void visitMeta(MetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class);
+ visitor.accept(Column.Type.REGULAR, "max", Long.TYPE);
+ visitor.accept(Column.Type.REGULAR, "mean", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "min", Long.TYPE);
+ visitor.accept(Column.Type.REGULAR, "p75th", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "p95th", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "p98th", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "p999th", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "p99th", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class);
+ }
+
+ @Override
+ public void visitRow(HistogramMetricRow row, RowMetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class, row::name);
+ visitor.accept(Column.Type.REGULAR, "max", Long.TYPE, row::max);
+ visitor.accept(Column.Type.REGULAR, "mean", Double.TYPE, row::mean);
+ visitor.accept(Column.Type.REGULAR, "min", Long.TYPE, row::min);
+ visitor.accept(Column.Type.REGULAR, "p75th", Double.TYPE, row::p75th);
+ visitor.accept(Column.Type.REGULAR, "p95th", Double.TYPE, row::p95th);
+ visitor.accept(Column.Type.REGULAR, "p98th", Double.TYPE, row::p98th);
+ visitor.accept(Column.Type.REGULAR, "p999th", Double.TYPE, row::p999th);
+ visitor.accept(Column.Type.REGULAR, "p99th", Double.TYPE, row::p99th);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class, row::scope);
+ }
+
+ @Override
+ public int count(Column.Type type)
+ {
+ switch (type)
+ {
+ case PARTITION_KEY:
+ return 1;
+ case CLUSTERING:
+ return 0;
+ case REGULAR:
+ return 9;
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/walker/MeterMetricRowWalker.java b/src/java/org/apache/cassandra/db/virtual/walker/MeterMetricRowWalker.java
new file mode 100644
index 0000000000..3039bb924b
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/walker/MeterMetricRowWalker.java
@@ -0,0 +1,69 @@
+/*
+ * 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
+ *
+ * https://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.walker;
+
+import org.apache.cassandra.db.virtual.model.Column;
+import org.apache.cassandra.db.virtual.model.MeterMetricRow;
+
+/**
+ * The {@link org.apache.cassandra.db.virtual.model.MeterMetricRow} row metadata and data walker.
+ *
+ * @see org.apache.cassandra.db.virtual.model.MeterMetricRow
+ */
+public class MeterMetricRowWalker implements RowWalker<MeterMetricRow>
+{
+ @Override
+ public void visitMeta(MetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class);
+ visitor.accept(Column.Type.REGULAR, "count", Long.TYPE);
+ visitor.accept(Column.Type.REGULAR, "fifteen_minute_rate", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "five_minute_rate", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "mean_rate", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "one_minute_rate", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class);
+ }
+
+ @Override
+ public void visitRow(MeterMetricRow row, RowMetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class, row::name);
+ visitor.accept(Column.Type.REGULAR, "count", Long.TYPE, row::count);
+ visitor.accept(Column.Type.REGULAR, "fifteen_minute_rate", Double.TYPE, row::fifteenMinuteRate);
+ visitor.accept(Column.Type.REGULAR, "five_minute_rate", Double.TYPE, row::fiveMinuteRate);
+ visitor.accept(Column.Type.REGULAR, "mean_rate", Double.TYPE, row::meanRate);
+ visitor.accept(Column.Type.REGULAR, "one_minute_rate", Double.TYPE, row::oneMinuteRate);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class, row::scope);
+ }
+
+ @Override
+ public int count(Column.Type type)
+ {
+ switch (type)
+ {
+ case PARTITION_KEY:
+ return 1;
+ case CLUSTERING:
+ return 0;
+ case REGULAR:
+ return 6;
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/walker/MetricGroupRowWalker.java b/src/java/org/apache/cassandra/db/virtual/walker/MetricGroupRowWalker.java
new file mode 100644
index 0000000000..0cb7c6f345
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/walker/MetricGroupRowWalker.java
@@ -0,0 +1,58 @@
+/*
+ * 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
+ *
+ * https://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.walker;
+
+import org.apache.cassandra.db.virtual.model.Column;
+import org.apache.cassandra.db.virtual.model.MetricGroupRow;
+
+/**
+ * The {@link org.apache.cassandra.db.virtual.model.MetricGroupRow} row metadata and data walker.
+ *
+ * @see org.apache.cassandra.db.virtual.model.MetricGroupRow
+ */
+public class MetricGroupRowWalker implements RowWalker<MetricGroupRow>
+{
+ @Override
+ public void visitMeta(MetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "group_name", String.class);
+ visitor.accept(Column.Type.REGULAR, "virtual_table", String.class);
+ }
+
+ @Override
+ public void visitRow(MetricGroupRow row, RowMetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "group_name", String.class, row::groupName);
+ visitor.accept(Column.Type.REGULAR, "virtual_table", String.class, row::virtualTable);
+ }
+
+ @Override
+ public int count(Column.Type type)
+ {
+ switch (type)
+ {
+ case PARTITION_KEY:
+ case REGULAR:
+ return 1;
+ case CLUSTERING:
+ return 0;
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/walker/MetricRowWalker.java b/src/java/org/apache/cassandra/db/virtual/walker/MetricRowWalker.java
new file mode 100644
index 0000000000..f5e46c021b
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/walker/MetricRowWalker.java
@@ -0,0 +1,63 @@
+/*
+ * 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
+ *
+ * https://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.walker;
+
+import org.apache.cassandra.db.virtual.model.Column;
+import org.apache.cassandra.db.virtual.model.MetricRow;
+
+/**
+ * The {@link org.apache.cassandra.db.virtual.model.MetricRow} row metadata and data walker.
+ *
+ * @see org.apache.cassandra.db.virtual.model.MetricRow
+ */
+public class MetricRowWalker implements RowWalker<MetricRow>
+{
+ @Override
+ public void visitMeta(MetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class);
+ visitor.accept(Column.Type.REGULAR, "type", String.class);
+ visitor.accept(Column.Type.REGULAR, "value", String.class);
+ }
+
+ @Override
+ public void visitRow(MetricRow row, RowMetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class, row::name);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class, row::scope);
+ visitor.accept(Column.Type.REGULAR, "type", String.class, row::type);
+ visitor.accept(Column.Type.REGULAR, "value", String.class, row::value);
+ }
+
+ @Override
+ public int count(Column.Type type)
+ {
+ switch (type)
+ {
+ case PARTITION_KEY:
+ return 1;
+ case CLUSTERING:
+ return 0;
+ case REGULAR:
+ return 3;
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/walker/RowWalker.java b/src/java/org/apache/cassandra/db/virtual/walker/RowWalker.java
new file mode 100644
index 0000000000..e45b183486
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/walker/RowWalker.java
@@ -0,0 +1,87 @@
+/*
+ * 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.walker;
+
+import java.util.function.Supplier;
+
+import org.apache.cassandra.db.virtual.model.Column;
+
+/**
+ * Utility class for quick iteration over row attributes and row values.
+ * Walk order is defined by {@link Column} annotations and is the same as
+ * in CQL (partition key columns first, then clustering columns, then regular columns).
+ */
+public interface RowWalker<R>
+{
+ /**
+ * Returns the number of columns of the given type in the row.
+ *
+ * @param type the type of column
+ * @return the number of columns of the given type in the row.
+ */
+ int count(Column.Type type);
+
+ /**
+ * Visit the metadata of the row.
+ *
+ * @param visitor the visitor to accept the metadata
+ */
+ void visitMeta(MetadataVisitor visitor);
+
+ /**
+ * Visit the row.
+ *
+ * @param row the row to visit
+ * @param visitor the visitor to accept the row
+ */
+ void visitRow(R row, RowMetadataVisitor visitor);
+
+ /**
+ * Visitor for metadata of the row.
+ */
+ interface MetadataVisitor
+ {
+ /**
+ * Process the metadata.
+ *
+ * @param type the type of column
+ * @param name the name of the column
+ * @param clazz the class of the column
+ * @param <T> the type of the column
+ */
+ <T> void accept(Column.Type type, String name, Class<T> clazz);
+ }
+
+ /**
+ * Visitor for a row value and its metadata.
+ */
+ interface RowMetadataVisitor
+ {
+ /**
+ * Process the row metadata.
+ *
+ * @param type the type of column
+ * @param name the name of the column
+ * @param clazz the class of the column
+ * @param value the value of the column
+ * @param <T> the type of the column
+ */
+ <T> void accept(Column.Type type, String name, Class<T> clazz, Supplier<T> value);
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/walker/ThreadPoolRowWalker.java b/src/java/org/apache/cassandra/db/virtual/walker/ThreadPoolRowWalker.java
new file mode 100644
index 0000000000..68cbc8c693
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/walker/ThreadPoolRowWalker.java
@@ -0,0 +1,75 @@
+/*
+ * 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
+ *
+ * https://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.walker;
+
+import org.apache.cassandra.db.virtual.model.Column;
+import org.apache.cassandra.db.virtual.model.ThreadPoolRow;
+
+/**
+ * The {@link org.apache.cassandra.db.virtual.model.ThreadPoolRow} row metadata and data walker.
+ *
+ * @see org.apache.cassandra.db.virtual.model.ThreadPoolRow
+ */
+public class ThreadPoolRowWalker implements RowWalker<ThreadPoolRow>
+{
+ @Override
+ public void visitMeta(MetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class);
+ visitor.accept(Column.Type.REGULAR, "active_tasks", Integer.class);
+ visitor.accept(Column.Type.REGULAR, "active_tasks_limit", Integer.class);
+ visitor.accept(Column.Type.REGULAR, "blocked_tasks", Long.TYPE);
+ visitor.accept(Column.Type.REGULAR, "blocked_tasks_all_time", Long.TYPE);
+ visitor.accept(Column.Type.REGULAR, "completed_tasks", Long.class);
+ visitor.accept(Column.Type.REGULAR, "core_pool_size", Integer.class);
+ visitor.accept(Column.Type.REGULAR, "max_pool_size", Integer.class);
+ visitor.accept(Column.Type.REGULAR, "max_tasks_queued", Integer.class);
+ visitor.accept(Column.Type.REGULAR, "pending_tasks", Integer.class);
+ }
+
+ @Override
+ public void visitRow(ThreadPoolRow row, RowMetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class, row::name);
+ visitor.accept(Column.Type.REGULAR, "active_tasks", Integer.class, row::activeTasks);
+ visitor.accept(Column.Type.REGULAR, "active_tasks_limit", Integer.class, row::activeTasksLimit);
+ visitor.accept(Column.Type.REGULAR, "blocked_tasks", Long.TYPE, row::blockedTasks);
+ visitor.accept(Column.Type.REGULAR, "blocked_tasks_all_time", Long.TYPE, row::blockedTasksAllTime);
+ visitor.accept(Column.Type.REGULAR, "completed_tasks", Long.class, row::completedTasks);
+ visitor.accept(Column.Type.REGULAR, "core_pool_size", Integer.class, row::corePoolSize);
+ visitor.accept(Column.Type.REGULAR, "max_pool_size", Integer.class, row::maxPoolSize);
+ visitor.accept(Column.Type.REGULAR, "max_tasks_queued", Integer.class, row::maxTasksQueued);
+ visitor.accept(Column.Type.REGULAR, "pending_tasks", Integer.class, row::pendingTasks);
+ }
+
+ @Override
+ public int count(Column.Type type)
+ {
+ switch (type)
+ {
+ case PARTITION_KEY:
+ return 1;
+ case CLUSTERING:
+ return 0;
+ case REGULAR:
+ return 9;
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/walker/TimerMetricRowWalker.java b/src/java/org/apache/cassandra/db/virtual/walker/TimerMetricRowWalker.java
new file mode 100644
index 0000000000..418495e24b
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/walker/TimerMetricRowWalker.java
@@ -0,0 +1,69 @@
+/*
+ * 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
+ *
+ * https://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.walker;
+
+import org.apache.cassandra.db.virtual.model.Column;
+import org.apache.cassandra.db.virtual.model.TimerMetricRow;
+
+/**
+ * The {@link org.apache.cassandra.db.virtual.model.TimerMetricRow} row metadata and data walker.
+ *
+ * @see org.apache.cassandra.db.virtual.model.TimerMetricRow
+ */
+public class TimerMetricRowWalker implements RowWalker<TimerMetricRow>
+{
+ @Override
+ public void visitMeta(MetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class);
+ visitor.accept(Column.Type.REGULAR, "count", Long.TYPE);
+ visitor.accept(Column.Type.REGULAR, "fifteen_minute_rate", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "five_minute_rate", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "mean_rate", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "one_minute_rate", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class);
+ }
+
+ @Override
+ public void visitRow(TimerMetricRow row, RowMetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "name", String.class, row::name);
+ visitor.accept(Column.Type.REGULAR, "count", Long.TYPE, row::count);
+ visitor.accept(Column.Type.REGULAR, "fifteen_minute_rate", Double.TYPE, row::fifteenMinuteRate);
+ visitor.accept(Column.Type.REGULAR, "five_minute_rate", Double.TYPE, row::fiveMinuteRate);
+ visitor.accept(Column.Type.REGULAR, "mean_rate", Double.TYPE, row::meanRate);
+ visitor.accept(Column.Type.REGULAR, "one_minute_rate", Double.TYPE, row::oneMinuteRate);
+ visitor.accept(Column.Type.REGULAR, "scope", String.class, row::scope);
+ }
+
+ @Override
+ public int count(Column.Type type)
+ {
+ switch (type)
+ {
+ case PARTITION_KEY:
+ return 1;
+ case CLUSTERING:
+ return 0;
+ case REGULAR:
+ return 6;
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/index/sai/metrics/AbstractMetrics.java b/src/java/org/apache/cassandra/index/sai/metrics/AbstractMetrics.java
index 90956e4b32..b305720ebb 100644
--- a/src/java/org/apache/cassandra/index/sai/metrics/AbstractMetrics.java
+++ b/src/java/org/apache/cassandra/index/sai/metrics/AbstractMetrics.java
@@ -34,7 +34,7 @@ public abstract class AbstractMetrics
protected final String table;
private final String index;
private final String scope;
- protected final List<CassandraMetricsRegistry.MetricName> tracked = new ArrayList<>();
+ protected final List<String> tracked = new ArrayList<>();
AbstractMetrics(IndexIdentifier indexIdentifier, String scope)
{
@@ -77,7 +77,7 @@ public abstract class AbstractMetrics
CassandraMetricsRegistry.MetricName metricName = new CassandraMetricsRegistry.MetricName(DefaultNameFactory.GROUP_NAME,
TYPE, name, metricScope, createMBeanName(name, scope));
- tracked.add(metricName);
+ tracked.add(metricName.getMetricName());
return metricName;
}
diff --git a/src/java/org/apache/cassandra/io/sstable/format/big/RowIndexEntry.java b/src/java/org/apache/cassandra/io/sstable/format/big/RowIndexEntry.java
index 9d2ab6e93a..7828599e14 100644
--- a/src/java/org/apache/cassandra/io/sstable/format/big/RowIndexEntry.java
+++ b/src/java/org/apache/cassandra/io/sstable/format/big/RowIndexEntry.java
@@ -137,6 +137,7 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class RowIndexEntry extends AbstractRowIndexEntry
{
+ public static final String TYPE_NAME = "Index";
private static final BigFormat FORMAT = BigFormat.getInstance();
private static final long EMPTY_SIZE = ObjectSizes.measure(new RowIndexEntry(0));
@@ -151,7 +152,7 @@ public class RowIndexEntry extends AbstractRowIndexEntry
static final Histogram indexInfoReadsHistogram;
static
{
- MetricNameFactory factory = new DefaultNameFactory("Index", "RowIndexEntry");
+ MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME, "RowIndexEntry");
indexEntrySizeHistogram = Metrics.histogram(factory.createMetricName("IndexedEntrySize"), false);
indexInfoCountHistogram = Metrics.histogram(factory.createMetricName("IndexInfoCount"), false);
indexInfoGetsHistogram = Metrics.histogram(factory.createMetricName("IndexInfoGets"), false);
diff --git a/src/java/org/apache/cassandra/metrics/BatchMetrics.java b/src/java/org/apache/cassandra/metrics/BatchMetrics.java
index 9bea162116..28bf4cfbb2 100644
--- a/src/java/org/apache/cassandra/metrics/BatchMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/BatchMetrics.java
@@ -23,8 +23,8 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
public class BatchMetrics
{
- private static final MetricNameFactory factory = new DefaultNameFactory("Batch");
-
+ public static final String TYPE_NAME = "Batch";
+ private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
public final Histogram partitionsPerLoggedBatch;
public final Histogram partitionsPerUnloggedBatch;
public final Histogram partitionsPerCounterBatch;
diff --git a/src/java/org/apache/cassandra/metrics/BufferPoolMetrics.java b/src/java/org/apache/cassandra/metrics/BufferPoolMetrics.java
index 71373b35e8..afcf62a09b 100644
--- a/src/java/org/apache/cassandra/metrics/BufferPoolMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/BufferPoolMetrics.java
@@ -25,6 +25,7 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
public class BufferPoolMetrics
{
+ public static final String TYPE_NAME = "BufferPool";
/** Total number of hits */
public final Meter hits;
@@ -49,7 +50,7 @@ public class BufferPoolMetrics
public BufferPoolMetrics(String scope, BufferPool bufferPool)
{
- MetricNameFactory factory = new DefaultNameFactory("BufferPool", scope);
+ MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME, scope);
hits = Metrics.meter(factory.createMetricName("Hits"));
diff --git a/src/java/org/apache/cassandra/metrics/CASClientWriteRequestMetrics.java b/src/java/org/apache/cassandra/metrics/CASClientWriteRequestMetrics.java
index 87c0d53541..f42f49592a 100644
--- a/src/java/org/apache/cassandra/metrics/CASClientWriteRequestMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/CASClientWriteRequestMetrics.java
@@ -48,8 +48,8 @@ public class CASClientWriteRequestMetrics extends CASClientRequestMetrics
public void release()
{
super.release();
- Metrics.remove(factory.createMetricName("ConditionNotMet"),
- DefaultNameFactory.createMetricName("ClientRequest", "ConditionNotMet", "CASRead"));
+ // Aliases are already known to the parent metrics, so we don't need to remove them here.
+ Metrics.remove(factory.createMetricName("ConditionNotMet"));
Metrics.remove(factory.createMetricName("MutationSizeHistogram"));
}
}
diff --git a/src/java/org/apache/cassandra/metrics/CIDRAuthorizerMetrics.java b/src/java/org/apache/cassandra/metrics/CIDRAuthorizerMetrics.java
index 0de51dfb09..39a6e357c3 100644
--- a/src/java/org/apache/cassandra/metrics/CIDRAuthorizerMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/CIDRAuthorizerMetrics.java
@@ -30,7 +30,8 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class CIDRAuthorizerMetrics
{
- private static final MetricNameFactory factory = new DefaultNameFactory("CIDRAuthorizer");
+ public static final String TYPE_NAME = "CIDRAuthorizer";
+ private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
// Number of user accesses rejected by CIDR authorization
public static final String CIDR_ACCESSES_REJECTED_COUNT_PREFIX = "CIDRAccessesRejectedCount - ";
diff --git a/src/java/org/apache/cassandra/metrics/CQLMetrics.java b/src/java/org/apache/cassandra/metrics/CQLMetrics.java
index ce91333942..edf13dfb4e 100644
--- a/src/java/org/apache/cassandra/metrics/CQLMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/CQLMetrics.java
@@ -26,7 +26,8 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
public class CQLMetrics
{
- private static final MetricNameFactory factory = new DefaultNameFactory("CQL");
+ public static final String TYPE_NAME = "CQL";
+ private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
public final Counter regularStatementsExecuted;
public final Counter preparedStatementsExecuted;
diff --git a/src/java/org/apache/cassandra/metrics/CacheMetrics.java b/src/java/org/apache/cassandra/metrics/CacheMetrics.java
index a6017a39f8..574b0f065c 100644
--- a/src/java/org/apache/cassandra/metrics/CacheMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/CacheMetrics.java
@@ -27,6 +27,7 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class CacheMetrics extends AbstractCacheMetrics
{
+ public static final String TYPE_NAME = "Cache";
/** Cache capacity in bytes */
public final Gauge<Long> capacity;
@@ -44,7 +45,7 @@ public class CacheMetrics extends AbstractCacheMetrics
*/
public CacheMetrics(String type, CacheSize cache)
{
- super(new DefaultNameFactory("Cache", type), type);
+ super(new DefaultNameFactory(TYPE_NAME, type), type);
capacity = Metrics.register(factory.createMetricName("Capacity"), cache::capacity);
size = Metrics.register(factory.createMetricName("Size"), cache::weightedSize);
entries = Metrics.register(factory.createMetricName("Entries"), cache::size);
diff --git a/src/java/org/apache/cassandra/metrics/CassandraMetricsRegistry.java b/src/java/org/apache/cassandra/metrics/CassandraMetricsRegistry.java
index cd98cebce9..a687ff997e 100644
--- a/src/java/org/apache/cassandra/metrics/CassandraMetricsRegistry.java
+++ b/src/java/org/apache/cassandra/metrics/CassandraMetricsRegistry.java
@@ -18,85 +18,348 @@
package org.apache.cassandra.metrics;
import java.lang.reflect.Method;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import com.google.common.annotations.VisibleForTesting;
-
-import com.codahale.metrics.*;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.apache.commons.lang3.ArrayUtils;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metered;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.MetricRegistryListener;
+import com.codahale.metrics.MetricSet;
+import com.codahale.metrics.Timer;
+import org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter;
+import org.apache.cassandra.db.virtual.VirtualTable;
+import org.apache.cassandra.db.virtual.model.CounterMetricRow;
+import org.apache.cassandra.db.virtual.model.GaugeMetricRow;
+import org.apache.cassandra.db.virtual.model.HistogramMetricRow;
+import org.apache.cassandra.db.virtual.model.MeterMetricRow;
+import org.apache.cassandra.db.virtual.model.MetricGroupRow;
+import org.apache.cassandra.db.virtual.model.MetricRow;
+import org.apache.cassandra.db.virtual.model.TimerMetricRow;
+import org.apache.cassandra.db.virtual.walker.CounterMetricRowWalker;
+import org.apache.cassandra.db.virtual.walker.GaugeMetricRowWalker;
+import org.apache.cassandra.db.virtual.walker.HistogramMetricRowWalker;
+import org.apache.cassandra.db.virtual.walker.MeterMetricRowWalker;
+import org.apache.cassandra.db.virtual.walker.MetricGroupRowWalker;
+import org.apache.cassandra.db.virtual.walker.MetricRowWalker;
+import org.apache.cassandra.db.virtual.walker.TimerMetricRowWalker;
+import org.apache.cassandra.index.sai.metrics.AbstractMetrics;
+import org.apache.cassandra.io.sstable.format.big.RowIndexEntry;
import org.apache.cassandra.utils.MBeanWrapper;
+import org.apache.cassandra.utils.memory.MemtablePool;
+
+import static java.util.Optional.ofNullable;
+import static org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter.createSinglePartitionedKeyFiltered;
+import static org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter.createSinglePartitionedValueFiltered;
+import static org.apache.cassandra.schema.SchemaConstants.VIRTUAL_METRICS;
/**
- * Makes integrating 3.0 metrics API with 2.0.
- * <p>
- * The 3.0 API comes with poor JMX integration
- * </p>
+ * Dropwizard metrics registry extension for Cassandra, as of for now uses the latest version of Dropwizard metrics
+ * library {@code 4.2.x} that has a pretty good integration with JMX. The registry is used by Cassandra to
+ * store all metrics and expose them to JMX and {@link org.apache.cassandra.db.virtual.VirtualTable}.
+ * In addition to that, the registry provides a way to store aliases for metrics and group metrics by
+ * the Cassandra-specific metric groups, which are used to expose metrics in that way.
+ *
+ * @see org.apache.cassandra.db.virtual.VirtualTable
+ * @see org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter
*/
public class CassandraMetricsRegistry extends MetricRegistry
{
- public static final CassandraMetricsRegistry Metrics = new CassandraMetricsRegistry();
+ public static final UnaryOperator<String> METRICS_GROUP_POSTFIX = name -> name + "_group";
+
+ /**
+ * A map of metric name constructed by {@link com.codahale.metrics.MetricRegistry#name(String, String...)} and
+ * its full name in the way how it is represented in JMX. The map is used by {@link CassandraJmxMetricsExporter}
+ * to export metrics to JMX.
+ */
+ private static final ConcurrentMap<String, Deque<MetricName>> ALIASES = new ConcurrentHashMap<>();
+
+ /** A set of all known metric groups, used to validate metric groups that are statically defined in Cassandra. */
+ static final Set<String> metricGroups;
+
+ /**
+ * Root metrics registry that is used by Cassandra to store all metrics.
+ * All modifications to the registry are delegated to the corresponding listeners as well.
+ * Metrics from the root registry are exported to JMX by {@link CassandraJmxMetricsExporter} and to virtual tables
+ * via {@link #createMetricsKeyspaceTables()}.
+ */
+ public static final CassandraMetricsRegistry Metrics = init();
+ private final MetricRegistryListener jmxExporter = new CassandraJmxMetricsExporter(ALIASES);
+
+ /** We have to make sure that this metrics listener is called the last, so that it can clean up aliases. */
+ private final MetricRegistryListener housekeepingListener = new BaseMetricRegistryListener()
+ {
+ @Override
+ protected void onMetricRemove(String name)
+ {
+ ALIASES.remove(name);
+ }
+ };
+
private final Map<String, ThreadPoolMetrics> threadPoolMetrics = new ConcurrentHashMap<>();
+ public static final String METRIC_SCOPE_UNDEFINED = "undefined";
+ public static final TimeUnit DEFAULT_TIMER_UNIT = TimeUnit.MICROSECONDS;
- private final MBeanWrapper mBeanServer = MBeanWrapper.instance;
- public final static TimeUnit DEFAULT_TIMER_UNIT = TimeUnit.MICROSECONDS;
+ static
+ {
+ // We have to initialize metric group names like this, because we can't register them dynamically
+ // as it is done for the dropwizard metrics. So we have to be sure, that all these metric groups are
+ // initialized at the time #start() method is called. The virtual kespaces are immutable, drivers also
+ // rely on the fact that virtual keyspaces are immutable, so they won't receive any updates if we change them.
+ //
+ // For example, if a new metric group is added, the appropriate tests will fail, because of the missing
+ // group in this collection, which in turn guarantees that all metric groups are registered and exposed
+ // for virtual tables.
+ metricGroups = ImmutableSet.<String>builder()
+ .add(AbstractMetrics.TYPE)
+ .add(BatchMetrics.TYPE_NAME)
+ .add(BufferPoolMetrics.TYPE_NAME)
+ .add(CIDRAuthorizerMetrics.TYPE_NAME)
+ .add(CQLMetrics.TYPE_NAME)
+ .add(CacheMetrics.TYPE_NAME)
+ .add(ChunkCacheMetrics.TYPE_NAME)
+ .add(ClientMessageSizeMetrics.TYPE)
+ .add(ClientMetrics.TYPE_NAME)
+ .add(ClientRequestMetrics.TYPE_NAME)
+ .add(ClientRequestSizeMetrics.TYPE)
+ .add(CommitLogMetrics.TYPE_NAME)
+ .add(CompactionMetrics.TYPE_NAME)
+ .add(DenylistMetrics.TYPE_NAME)
+ .add(DroppedMessageMetrics.TYPE)
+ .add(HintedHandoffMetrics.TYPE_NAME)
+ .add(HintsServiceMetrics.TYPE_NAME)
+ .add(InternodeInboundMetrics.TYPE_NAME)
+ .add(InternodeOutboundMetrics.TYPE_NAME)
+ .add(KeyspaceMetrics.TYPE_NAME)
+ .add(MemtablePool.TYPE_NAME)
+ .add(MessagingMetrics.TYPE_NAME)
+ .add(MutualTlsMetrics.TYPE_NAME)
+ .add(PaxosMetrics.TYPE_NAME)
+ .add(ReadRepairMetrics.TYPE_NAME)
+ .add(RepairMetrics.TYPE_NAME)
+ .add(RowIndexEntry.TYPE_NAME)
+ .add(StorageMetrics.TYPE_NAME)
+ .add(StreamingMetrics.TYPE_NAME)
+ .add(TCMMetrics.TYPE_NAME)
+ .add(TableMetrics.ALIAS_TYPE_NAME)
+ .add(TableMetrics.TYPE_NAME)
+ .add(TableMetrics.INDEX_TYPE_NAME)
+ .add(TableMetrics.INDEX_ALIAS_TYPE_NAME)
+ .add(ThreadPoolMetrics.TYPE_NAME)
+ .add(TrieMemtableMetricsView.TYPE_NAME)
+ .add(UnweightedCacheMetrics.TYPE_NAME)
+ .build();
+ }
private CassandraMetricsRegistry()
{
- super();
}
- public Counter counter(MetricName name)
+ private static CassandraMetricsRegistry init()
{
- Counter counter = counter(name.getMetricName());
- registerMBean(counter, name.getMBeanName());
+ CassandraMetricsRegistry registry = new CassandraMetricsRegistry();
+ // Adding listeners to the root registry, so that they can be notified about all metrics changes.
+ registry.addListener(registry.jmxExporter);
+ registry.addListener(registry.housekeepingListener);
+ return registry;
+ }
- return counter;
+ @SuppressWarnings("rawtypes")
+ public static String getValueAsString(Metric metric)
+ {
+ if (metric instanceof Counter)
+ return Long.toString(((Counter) metric).getCount());
+ else if (metric instanceof Gauge)
+ return getGaugeValue((Gauge) metric);
+ else if (metric instanceof Histogram)
+ return Double.toString(((Histogram) metric).getSnapshot().getMedian());
+ else if (metric instanceof Meter)
+ return Long.toString(((Meter) metric).getCount());
+ else if (metric instanceof Timer)
+ return Long.toString(((Timer) metric).getCount());
+ else
+ throw new IllegalStateException("Unknown metric type: " + metric.getClass().getName());
}
- public Counter counter(MetricName name, MetricName alias)
+ public static String getGaugeValue(Gauge<?> gauge)
{
- Counter counter = counter(name);
- registerAlias(name, alias);
- return counter;
+ Object value = gauge.getValue();
+ if (value == null)
+ return "null";
+ else if (value instanceof long[])
+ return Arrays.toString((long[]) value);
+ else if (value instanceof double[])
+ return Arrays.toString((double[]) value);
+ else if (value instanceof short[])
+ return Arrays.toString((short[]) value);
+ else if (value instanceof int[])
+ return Arrays.toString((int[]) value);
+ else if (value instanceof float[])
+ return Arrays.toString((float[]) value);
+ else if (value instanceof byte[])
+ return Arrays.toString((byte[]) value);
+ else if (value instanceof Object[])
+ return Arrays.toString((Object[]) value);
+ else
+ return value.toString();
}
- public Meter meter(MetricName name)
+ public static List<VirtualTable> createMetricsKeyspaceTables()
{
- Meter meter = meter(name.getMetricName());
- registerMBean(meter, name.getMBeanName());
+ ImmutableList.Builder<VirtualTable> builder = ImmutableList.builder();
+ metricGroups.forEach(groupName -> {
+ // This is a very efficient way to filter metrics by group name, so make sure that metrics group name
+ // and metric type following the same order as it constructed in MetricName class.
+ final String groupPrefix = DefaultNameFactory.GROUP_NAME + '.' + groupName + '.';
+ builder.add(createSinglePartitionedKeyFiltered(VIRTUAL_METRICS,
+ METRICS_GROUP_POSTFIX.apply(groupName),
+ "All metrics for \"" + groupName + "\" metric group",
+ new MetricRowWalker(),
+ Metrics.getMetrics(),
+ key -> key.startsWith(groupPrefix),
+ MetricRow::new));
+ });
+ // Register virtual table of all known metric groups.
+ builder.add(CollectionVirtualTableAdapter.create(VIRTUAL_METRICS,
+ "all_groups",
+ "All metric group names",
+ new MetricGroupRowWalker(),
+ metricGroups,
+ MetricGroupRow::new))
+ // Register virtual tables of all metrics types similar to the JMX MBean structure,
+ // e.g.: HistogramJmxMBean, MeterJmxMBean, etc.
+ .add(createSinglePartitionedValueFiltered(VIRTUAL_METRICS,
+ "type_counter",
+ "All metrics with type \"Counter\"",
+ new CounterMetricRowWalker(),
+ Metrics.getMetrics(),
+ Counter.class::isInstance,
+ CounterMetricRow::new))
+ .add(createSinglePartitionedValueFiltered(VIRTUAL_METRICS,
+ "type_gauge",
+ "All metrics with type \"Gauge\"",
+ new GaugeMetricRowWalker(),
+ Metrics.getMetrics(),
+ Gauge.class::isInstance,
+ GaugeMetricRow::new))
+ .add(createSinglePartitionedValueFiltered(VIRTUAL_METRICS,
+ "type_histogram",
+ "All metrics with type \"Histogram\"",
+ new HistogramMetricRowWalker(),
+ Metrics.getMetrics(),
+ Histogram.class::isInstance,
+ HistogramMetricRow::new))
+ .add(createSinglePartitionedValueFiltered(VIRTUAL_METRICS,
+ "type_meter",
+ "All metrics with type \"Meter\"",
+ new MeterMetricRowWalker(),
+ Metrics.getMetrics(),
+ Meter.class::isInstance,
+ MeterMetricRow::new))
+ .add(createSinglePartitionedValueFiltered(VIRTUAL_METRICS,
+ "type_timer",
+ "All metrics with type \"Timer\"",
+ new TimerMetricRowWalker(),
+ Metrics.getMetrics(),
+ Timer.class::isInstance,
+ TimerMetricRow::new));
+ return builder.build();
+ }
- return meter;
+ private static void addUnknownMetric(MetricName newMetricName)
+ {
+ String type = newMetricName.getType();
+ if (type.indexOf('.') >= 0)
+ throw new IllegalStateException(
+ "Metric type must not contain '.' character as it results in the efficiency of the metric collection traversal: " + type);
+ if (!metricGroups.contains(newMetricName.getType()))
+ throw new IllegalStateException("Unknown metric group: " + newMetricName.getType());
+ if (!metricGroups.contains(newMetricName.getSystemViewName()))
+ throw new IllegalStateException("Metric view name must match statically registered groups: " +
+ newMetricName.getSystemViewName());
+
+ // We have to be sure that aliases are registered the same order as they are added to the registry.
+ ALIASES.computeIfAbsent(newMetricName.getMetricName(), k -> new LinkedList<>())
+ .add(newMetricName);
+ }
+
+ private static void setAliases(MetricName... names)
+ {
+ Arrays.asList(names).forEach(CassandraMetricsRegistry::addUnknownMetric);
+ ALIASES.get(names[0].getMetricName()).addAll(Arrays.asList(names));
+ }
+
+ public String getMetricScope(String metricName)
+ {
+ Deque<MetricName> deque = ALIASES.get(metricName);
+ return deque == null ? METRIC_SCOPE_UNDEFINED :
+ deque.stream().findFirst().map(MetricName::getScope).orElse(METRIC_SCOPE_UNDEFINED);
+ }
+
+ public Counter counter(MetricName... name)
+ {
+ setAliases(name);
+ Counter counter = super.counter(name[0].getMetricName());
+ Stream.of(name).skip(1).forEach(n -> register(n, counter));
+ return counter;
}
- public Meter meter(MetricName name, MetricName alias)
+ public Meter meter(MetricName... name)
{
- Meter meter = meter(name);
- registerAlias(name, alias);
+ setAliases(name);
+ Meter meter = super.meter(name[0].getMetricName());
+ Stream.of(name).skip(1).forEach(n -> register(n, meter));
return meter;
}
public Histogram histogram(MetricName name, boolean considerZeroes)
{
- Histogram histogram = register(name, new ClearableHistogram(new DecayingEstimatedHistogramReservoir(considerZeroes)));
- registerMBean(histogram, name.getMBeanName());
-
- return histogram;
+ return register(name, new ClearableHistogram(new DecayingEstimatedHistogramReservoir(considerZeroes)));
}
public Histogram histogram(MetricName name, MetricName alias, boolean considerZeroes)
{
+ setAliases(name, alias);
Histogram histogram = histogram(name, considerZeroes);
- registerAlias(name, alias);
+ register(alias, histogram);
return histogram;
}
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public <T extends Gauge> T gauge(MetricName name, MetricName alias, MetricSupplier<T> gauge)
+ {
+ setAliases(name, alias);
+ Gauge gaugeLoc = super.gauge(name.getMetricName(), gauge);
+ register(alias, gaugeLoc);
+ return (T) gaugeLoc;
+ }
+
public Timer timer(MetricName name)
{
return timer(name, DEFAULT_TIMER_UNIT);
@@ -107,17 +370,16 @@ public class CassandraMetricsRegistry extends MetricRegistry
return timer(name, alias, DEFAULT_TIMER_UNIT);
}
- public SnapshottingTimer timer(MetricName name, TimeUnit durationUnit)
+ private SnapshottingTimer timer(MetricName name, TimeUnit durationUnit)
{
- SnapshottingTimer timer = register(name, new SnapshottingTimer(CassandraMetricsRegistry.createReservoir(durationUnit)));
- registerMBean(timer, name.getMBeanName());
- return timer;
+ return register(name, new SnapshottingTimer(CassandraMetricsRegistry.createReservoir(durationUnit)));
}
public SnapshottingTimer timer(MetricName name, MetricName alias, TimeUnit durationUnit)
{
+ setAliases(name, alias);
SnapshottingTimer timer = timer(name, durationUnit);
- registerAlias(name, alias);
+ register(alias, timer);
return timer;
}
@@ -144,11 +406,13 @@ public class CassandraMetricsRegistry extends MetricRegistry
public <T extends Metric> T register(MetricName name, T metric)
{
+ if (metric instanceof MetricSet)
+ throw new IllegalArgumentException("MetricSet registration using MetricName is not supported");
+
try
{
- register(name.getMetricName(), metric);
- registerMBean(metric, name.getMBeanName());
- return metric;
+ addUnknownMetric(name);
+ return super.register(name.getMetricName(), metric);
}
catch (IllegalArgumentException e)
{
@@ -164,7 +428,7 @@ public class CassandraMetricsRegistry extends MetricRegistry
public Optional<ThreadPoolMetrics> getThreadPoolMetrics(String poolName)
{
- return Optional.ofNullable(threadPoolMetrics.get(poolName));
+ return ofNullable(threadPoolMetrics.get(poolName));
}
ThreadPoolMetrics register(ThreadPoolMetrics metrics)
@@ -178,45 +442,35 @@ public class CassandraMetricsRegistry extends MetricRegistry
threadPoolMetrics.remove(metrics.poolName, metrics);
}
- public <T extends Metric> T register(MetricName name, MetricName aliasName, T metric)
- {
- T ret = register(name, metric);
- registerAlias(name, aliasName);
- return ret;
- }
-
public <T extends Metric> T register(MetricName name, T metric, MetricName... aliases)
{
- T ret = register(name, metric);
- for (MetricName aliasName : aliases)
- {
- registerAlias(name, aliasName);
- }
- return ret;
+ setAliases(ArrayUtils.addAll(new MetricName[]{name}, aliases));
+ T metricLoc = register(name, metric);
+ Stream.of(aliases).forEach(n -> register(n, metricLoc));
+ return metricLoc;
}
- public boolean remove(MetricName name)
+ public void remove(MetricName name)
{
- boolean removed = remove(name.getMetricName());
-
- mBeanServer.unregisterMBean(name.getMBeanName(), MBeanWrapper.OnException.IGNORE);
- return removed;
+ // Aliases are removed in onMetricRemoved by metrics listener.
+ remove(name.getMetricName());
}
- public boolean remove(MetricName name, MetricName... aliases)
+ public boolean remove(String name)
{
- if (remove(name))
- {
- for (MetricName alias : aliases)
- {
- removeAlias(alias);
- }
- return true;
- }
- return false;
+ LinkedList<String> delete = ofNullable(ALIASES.get(name))
+ .map(s -> s.stream().map(MetricName::getMetricName)
+ .collect(Collectors.toCollection(LinkedList::new)))
+ .orElse(new LinkedList<>(Collections.singletonList(name)));
+ // Aliases are removed in onMetricRemoved by metrics listener.
+ Iterator<String> iter = delete.descendingIterator();
+ boolean removed = true;
+ while (iter.hasNext())
+ removed &= super.remove(iter.next());
+ return removed;
}
- public void registerMBean(Metric metric, ObjectName name)
+ private void registerMBean(Metric metric, ObjectName name, MBeanWrapper mBeanServer)
{
AbstractBean mbean;
@@ -233,22 +487,13 @@ public class CassandraMetricsRegistry extends MetricRegistry
else
throw new IllegalArgumentException("Unknown metric type: " + metric.getClass());
- if (!mBeanServer.isRegistered(name))
+ if (mBeanServer != null && !mBeanServer.isRegistered(name))
mBeanServer.registerMBean(mbean, name, MBeanWrapper.OnException.LOG);
}
- private void registerAlias(MetricName existingName, MetricName aliasName)
+ private void unregisterMBean(ObjectName name, MBeanWrapper mBeanWrapper)
{
- Metric existing = Metrics.getMetrics().get(existingName.getMetricName());
- assert existing != null : existingName + " not registered";
-
- registerMBean(existing, aliasName.getMBeanName());
- }
-
- private void removeAlias(MetricName name)
- {
- if (mBeanServer.isRegistered(name.getMBeanName()))
- MBeanWrapper.instance.unregisterMBean(name.getMBeanName(), MBeanWrapper.OnException.IGNORE);
+ mBeanWrapper.unregisterMBean(name, MBeanWrapper.OnException.IGNORE);
}
/**
@@ -284,7 +529,10 @@ public class CassandraMetricsRegistry extends MetricRegistry
}
}
-
+ /**
+ * Exports a gauge as a JMX MBean, check corresponding {@link org.apache.cassandra.db.virtual.model.GaugeMetricRow}
+ * for the same functionality for virtual tables.
+ */
public interface JmxGaugeMBean extends MetricMBean
{
Object getValue();
@@ -307,6 +555,10 @@ public class CassandraMetricsRegistry extends MetricRegistry
}
}
+ /**
+ * Exports a histogram as a JMX MBean, check corresponding {@link org.apache.cassandra.db.virtual.model.HistogramMetricRow}
+ * for the same functionality for virtual tables.
+ */
public interface JmxHistogramMBean extends MetricMBean
{
long getCount();
@@ -437,6 +689,10 @@ public class CassandraMetricsRegistry extends MetricRegistry
}
}
+ /**
+ * Exports a counter as a JMX MBean, check corresponding {@link org.apache.cassandra.db.virtual.model.CounterMetricRow}
+ * for the same functionality for virtual tables.
+ */
public interface JmxCounterMBean extends MetricMBean
{
long getCount();
@@ -459,6 +715,10 @@ public class CassandraMetricsRegistry extends MetricRegistry
}
}
+ /**
+ * Exports a meter as a JMX MBean, check corresponding {@link org.apache.cassandra.db.virtual.model.MeterMetricRow}
+ * for the same functionality for virtual tables.
+ */
public interface JmxMeterMBean extends MetricMBean
{
long getCount();
@@ -474,6 +734,10 @@ public class CassandraMetricsRegistry extends MetricRegistry
String getRateUnit();
}
+ /**
+ * Exports a timer as a JMX MBean, check corresponding {@link org.apache.cassandra.db.virtual.model.TimerMetricRow}
+ * for the same functionality for virtual tables.
+ */
private static class JmxMeter extends AbstractBean implements JmxMeterMBean
{
private final Metered metric;
@@ -531,6 +795,10 @@ public class CassandraMetricsRegistry extends MetricRegistry
}
}
+ /**
+ * Exports a timer as a JMX MBean, check corresponding {@link org.apache.cassandra.db.virtual.model.TimerMetricRow}
+ * for the same functionality for virtual tables.
+ */
public interface JmxTimerMBean extends JmxMeterMBean
{
double getMin();
@@ -693,11 +961,13 @@ public class CassandraMetricsRegistry extends MetricRegistry
*/
public static class MetricName implements Comparable<MetricName>
{
+ public static final MetricName EMPTY = new MetricName(MetricName.class, "EMPTY");
private final String group;
private final String type;
private final String name;
private final String scope;
private final String mBeanName;
+ private final String systemViewName;
/**
* Creates a new {@link MetricName} without a scope.
@@ -750,17 +1020,22 @@ public class CassandraMetricsRegistry extends MetricRegistry
this(group, type, name, scope, createMBeanName(group, type, name, scope));
}
+ public MetricName(String group, String type, String name, String scope, String mBeanName)
+ {
+ this(group, type, name, scope, mBeanName, type);
+ }
+
/**
* Creates a new {@link MetricName} without a scope.
*
- * @param group the group to which the {@link Metric} belongs
- * @param type the type to which the {@link Metric} belongs
- * @param name the name of the {@link Metric}
- * @param scope the scope of the {@link Metric}
- * @param mBeanName the 'ObjectName', represented as a string, to use when registering the
- * MBean.
+ * @param group the group to which the {@link Metric} belongs
+ * @param type the type to which the {@link Metric} belongs
+ * @param name the name of the {@link Metric}
+ * @param scope the scope of the {@link Metric}
+ * @param mBeanName the 'ObjectName', represented as a string, to use when registering the MBean.
+ * @param systemViewName the name of the virtual table to which the {@link Metric} belongs.
*/
- public MetricName(String group, String type, String name, String scope, String mBeanName)
+ public MetricName(String group, String type, String name, String scope, String mBeanName, String systemViewName)
{
if (group == null || type == null)
{
@@ -775,6 +1050,7 @@ public class CassandraMetricsRegistry extends MetricRegistry
this.name = name;
this.scope = scope;
this.mBeanName = mBeanName;
+ this.systemViewName = systemViewName;
}
/**
@@ -863,6 +1139,11 @@ public class CassandraMetricsRegistry extends MetricRegistry
}
}
+ public String getSystemViewName()
+ {
+ return systemViewName;
+ }
+
@Override
public boolean equals(Object o)
{
@@ -963,6 +1244,106 @@ public class CassandraMetricsRegistry extends MetricRegistry
return name;
}
}
-}
+ private static class CassandraJmxMetricsExporter extends BaseMetricRegistryListener
+ {
+ private final MBeanWrapper mBeanWrapper = MBeanWrapper.instance;
+ private final Map<String, Deque<MetricName>> aliases;
+ public CassandraJmxMetricsExporter(Map<String, Deque<MetricName>> aliases)
+ {
+ this.aliases = aliases;
+ }
+
+ protected void onMetricAdded(String name, Metric metric)
+ {
+ Deque<MetricName> deque = aliases.get(name);
+ if (deque == null)
+ return;
+
+ assert deque.getFirst().getMetricName().equals(name);
+ Metrics.registerMBean(metric, deque.getFirst().getMBeanName(), mBeanWrapper);
+ }
+
+ protected void onMetricRemove(String name)
+ {
+ Deque<MetricName> deque = aliases.get(name);
+ if (deque == null)
+ return;
+
+ assert deque.getFirst().getMetricName().equals(name);
+ Metrics.unregisterMBean(deque.getFirst().getMBeanName(), mBeanWrapper);
+ }
+ }
+
+ private static abstract class BaseMetricRegistryListener implements MetricRegistryListener
+ {
+ protected void onMetricAdded(String name, Metric metric)
+ {
+ }
+
+ protected void onMetricRemove(String name)
+ {
+ }
+
+ @Override
+ public void onGaugeAdded(String metricName, Gauge<?> gauge)
+ {
+ onMetricAdded(metricName, gauge);
+ }
+
+ @Override
+ public void onGaugeRemoved(String metricName)
+ {
+ onMetricRemove(metricName);
+ }
+
+ @Override
+ public void onCounterAdded(String metricName, Counter counter)
+ {
+ onMetricAdded(metricName, counter);
+ }
+
+ @Override
+ public void onCounterRemoved(String metricName)
+ {
+ onMetricRemove(metricName);
+ }
+
+ @Override
+ public void onHistogramAdded(String metricName, Histogram histogram)
+ {
+ onMetricAdded(metricName, histogram);
+ }
+
+ @Override
+ public void onHistogramRemoved(String metricName)
+ {
+ onMetricRemove(metricName);
+ }
+
+ @Override
+ public void onMeterAdded(String metricName, Meter meter)
+ {
+ onMetricAdded(metricName, meter);
+ }
+
+ @Override
+ public void onMeterRemoved(String metricName)
+ {
+ onMetricRemove(metricName);
+ }
+
+ @Override
+ public void onTimerAdded(String metricName, Timer timer)
+ {
+ onMetricAdded(metricName, timer);
+ }
+
+ @Override
+ public void onTimerRemoved(String metricName)
+ {
+ onMetricRemove(metricName);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/metrics/ChunkCacheMetrics.java b/src/java/org/apache/cassandra/metrics/ChunkCacheMetrics.java
index 8195aafbf3..a208998c9a 100644
--- a/src/java/org/apache/cassandra/metrics/ChunkCacheMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/ChunkCacheMetrics.java
@@ -33,6 +33,7 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class ChunkCacheMetrics extends CacheMetrics implements StatsCounter
{
+ public static final String TYPE_NAME = "ChunkCache";
/** Latency of misses */
public final Timer missLatency;
@@ -43,7 +44,7 @@ public class ChunkCacheMetrics extends CacheMetrics implements StatsCounter
*/
public ChunkCacheMetrics(ChunkCache cache)
{
- super("ChunkCache", cache);
+ super(TYPE_NAME, cache);
missLatency = Metrics.timer(factory.createMetricName("MissLatency"));
}
diff --git a/src/java/org/apache/cassandra/metrics/ClientMessageSizeMetrics.java b/src/java/org/apache/cassandra/metrics/ClientMessageSizeMetrics.java
index acdbf2851b..416e6b44b4 100644
--- a/src/java/org/apache/cassandra/metrics/ClientMessageSizeMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/ClientMessageSizeMetrics.java
@@ -28,7 +28,7 @@ import com.codahale.metrics.Histogram;
*/
public class ClientMessageSizeMetrics
{
- private static final String TYPE = "ClientMessageSize";
+ public static final String TYPE = "ClientMessageSize";
public static final Counter bytesReceived = Metrics.counter(DefaultNameFactory.createMetricName(TYPE, "BytesReceived", null));
public static final Counter bytesSent = Metrics.counter(DefaultNameFactory.createMetricName(TYPE, "BytesSent", null));
public static final Histogram bytesReceivedPerRequest = Metrics.histogram(DefaultNameFactory.createMetricName(TYPE, "BytesReceivedPerRequest", null), true);
diff --git a/src/java/org/apache/cassandra/metrics/ClientMetrics.java b/src/java/org/apache/cassandra/metrics/ClientMetrics.java
index a8801bad5f..1a465506a6 100644
--- a/src/java/org/apache/cassandra/metrics/ClientMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/ClientMetrics.java
@@ -47,9 +47,10 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
public final class ClientMetrics
{
+ public static final String TYPE_NAME = "Client";
public static final ClientMetrics instance = new ClientMetrics();
- private static final MetricNameFactory factory = new DefaultNameFactory("Client");
+ private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
private volatile boolean initialized = false;
private Server server = null;
@@ -282,9 +283,7 @@ public final class ClientMetrics
private <T> Gauge<T> registerGauge(String name, String deprecated, Gauge<T> gauge)
{
- Gauge<T> registeredGauge = registerGauge(name, gauge);
- Metrics.registerMBean(registeredGauge, factory.createMetricName(deprecated).getMBeanName());
- return registeredGauge;
+ return Metrics.gauge(factory.createMetricName(name), factory.createMetricName(deprecated), () -> gauge);
}
private Meter registerMeter(String name)
diff --git a/src/java/org/apache/cassandra/metrics/ClientRequestMetrics.java b/src/java/org/apache/cassandra/metrics/ClientRequestMetrics.java
index 408087051e..61fcc34bf1 100644
--- a/src/java/org/apache/cassandra/metrics/ClientRequestMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/ClientRequestMetrics.java
@@ -31,6 +31,7 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
public class ClientRequestMetrics extends LatencyMetrics
{
+ public static final String TYPE_NAME = "ClientRequest";
public final Meter timeouts;
public final Meter unavailables;
public final Meter failures;
@@ -42,7 +43,7 @@ public class ClientRequestMetrics extends LatencyMetrics
public ClientRequestMetrics(String scope)
{
- super("ClientRequest", scope);
+ super(TYPE_NAME, scope);
timeouts = Metrics.meter(factory.createMetricName("Timeouts"));
unavailables = Metrics.meter(factory.createMetricName("Unavailables"));
diff --git a/src/java/org/apache/cassandra/metrics/ClientRequestSizeMetrics.java b/src/java/org/apache/cassandra/metrics/ClientRequestSizeMetrics.java
index 4def87ea16..f7719762a4 100644
--- a/src/java/org/apache/cassandra/metrics/ClientRequestSizeMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/ClientRequestSizeMetrics.java
@@ -34,7 +34,7 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
public class ClientRequestSizeMetrics
{
- private static final String TYPE = "ClientRequestSize";
+ public static final String TYPE = "ClientRequestSize";
public static final Counter totalColumnsRead = Metrics.counter(DefaultNameFactory.createMetricName(TYPE, "ColumnsRead", null));
public static final Counter totalRowsRead = Metrics.counter(DefaultNameFactory.createMetricName(TYPE, "RowsRead", null));
diff --git a/src/java/org/apache/cassandra/metrics/CommitLogMetrics.java b/src/java/org/apache/cassandra/metrics/CommitLogMetrics.java
index cb53575b2b..1c7390522a 100644
--- a/src/java/org/apache/cassandra/metrics/CommitLogMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/CommitLogMetrics.java
@@ -30,7 +30,8 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class CommitLogMetrics
{
- public static final MetricNameFactory factory = new DefaultNameFactory("CommitLog");
+ public static final String TYPE_NAME = "CommitLog";
+ public static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
/** Number of completed tasks */
public Gauge<Long> completedTasks;
diff --git a/src/java/org/apache/cassandra/metrics/CompactionMetrics.java b/src/java/org/apache/cassandra/metrics/CompactionMetrics.java
index 0fe1ec7418..8bd48e520e 100644
--- a/src/java/org/apache/cassandra/metrics/CompactionMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/CompactionMetrics.java
@@ -39,7 +39,8 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class CompactionMetrics
{
- public static final MetricNameFactory factory = new DefaultNameFactory("Compaction");
+ public static final String TYPE_NAME = "Compaction";
+ public static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
/** Estimated number of compactions remaining to perform */
public final Gauge<Integer> pendingTasks;
diff --git a/src/java/org/apache/cassandra/metrics/DenylistMetrics.java b/src/java/org/apache/cassandra/metrics/DenylistMetrics.java
index 0372787af9..e3c34ab357 100644
--- a/src/java/org/apache/cassandra/metrics/DenylistMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/DenylistMetrics.java
@@ -24,6 +24,7 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
public class DenylistMetrics
{
+ public static final String TYPE_NAME = "StorageProxy";
private final Meter writesRejected;
private final Meter readsRejected;
private final Meter rangeReadsRejected;
@@ -31,7 +32,7 @@ public class DenylistMetrics
public DenylistMetrics()
{
- final MetricNameFactory factory = new DefaultNameFactory("StorageProxy", "PartitionDenylist");
+ final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME, "PartitionDenylist");
writesRejected = Metrics.meter(factory.createMetricName("WriteRejected"));
readsRejected = Metrics.meter(factory.createMetricName("ReadRejected"));
rangeReadsRejected = Metrics.meter(factory.createMetricName("RangeReadRejected"));
diff --git a/src/java/org/apache/cassandra/metrics/DroppedMessageMetrics.java b/src/java/org/apache/cassandra/metrics/DroppedMessageMetrics.java
index 91a680e50d..e17c24de7a 100644
--- a/src/java/org/apache/cassandra/metrics/DroppedMessageMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/DroppedMessageMetrics.java
@@ -35,7 +35,7 @@ import static org.apache.cassandra.metrics.DefaultNameFactory.createMetricName;
*/
public class DroppedMessageMetrics
{
- private static final String TYPE = "DroppedMessage";
+ public static final String TYPE = "DroppedMessage";
// backward compatibility for request metrics which names have changed in 4.0 as part of CASSANDRA-15066
private static final ImmutableMap<Verb, String> REQUEST_VERB_ALIAS;
diff --git a/src/java/org/apache/cassandra/metrics/HintedHandoffMetrics.java b/src/java/org/apache/cassandra/metrics/HintedHandoffMetrics.java
index bc87218bbd..187f5afe43 100644
--- a/src/java/org/apache/cassandra/metrics/HintedHandoffMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/HintedHandoffMetrics.java
@@ -38,9 +38,10 @@ import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID;
*/
public class HintedHandoffMetrics
{
+ public static final String TYPE_NAME = "HintsService";
private static final Logger logger = LoggerFactory.getLogger(HintedHandoffMetrics.class);
- private static final MetricNameFactory factory = new DefaultNameFactory("HintsService");
+ private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
/** Total number of hints which are not stored, This is not a cache. */
private final LoadingCache<InetAddressAndPort, DifferencingCounter> notStored = Caffeine.newBuilder()
diff --git a/src/java/org/apache/cassandra/metrics/HintsServiceMetrics.java b/src/java/org/apache/cassandra/metrics/HintsServiceMetrics.java
index bcff389577..2a8ce92776 100644
--- a/src/java/org/apache/cassandra/metrics/HintsServiceMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/HintsServiceMetrics.java
@@ -34,9 +34,10 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public final class HintsServiceMetrics
{
+ public static final String TYPE_NAME = "HintsService";
private static final Logger logger = LoggerFactory.getLogger(HintsServiceMetrics.class);
- private static final MetricNameFactory factory = new DefaultNameFactory("HintsService");
+ private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
public static final Meter hintsSucceeded = Metrics.meter(factory.createMetricName("HintsSucceeded"));
public static final Meter hintsFailed = Metrics.meter(factory.createMetricName("HintsFailed"));
diff --git a/src/java/org/apache/cassandra/metrics/InternodeInboundMetrics.java b/src/java/org/apache/cassandra/metrics/InternodeInboundMetrics.java
index cc8dae9f4c..16870321be 100644
--- a/src/java/org/apache/cassandra/metrics/InternodeInboundMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/InternodeInboundMetrics.java
@@ -27,6 +27,7 @@ import org.apache.cassandra.metrics.CassandraMetricsRegistry.MetricName;
*/
public class InternodeInboundMetrics
{
+ public static final String TYPE_NAME = "InternodeInbound";
private final MetricName corruptFramesRecovered;
private final MetricName corruptFramesUnrecovered;
private final MetricName errorBytes;
@@ -50,7 +51,7 @@ public class InternodeInboundMetrics
public InternodeInboundMetrics(InetAddressAndPort peer, InboundMessageHandlers handlers)
{
// ipv6 addresses will contain colons, which are invalid in a JMX ObjectName
- MetricNameFactory factory = new DefaultNameFactory("InboundConnection", peer.getHostAddressAndPortForJMX());
+ MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME, peer.getHostAddressAndPortForJMX());
register(corruptFramesRecovered = factory.createMetricName("CorruptFramesRecovered"), handlers::corruptFramesRecovered);
register(corruptFramesUnrecovered = factory.createMetricName("CorruptFramesUnrecovered"), handlers::corruptFramesUnrecovered);
diff --git a/src/java/org/apache/cassandra/metrics/InternodeOutboundMetrics.java b/src/java/org/apache/cassandra/metrics/InternodeOutboundMetrics.java
index 2b0348eb55..f2a1a8bb6f 100644
--- a/src/java/org/apache/cassandra/metrics/InternodeOutboundMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/InternodeOutboundMetrics.java
@@ -119,7 +119,7 @@ public class InternodeOutboundMetrics
// ipv6 addresses will contain colons, which are invalid in a JMX ObjectName
address = ip.getHostAddressAndPortForJMX();
- factory = new DefaultNameFactory("Connection", address);
+ factory = new DefaultNameFactory(TYPE_NAME, address);
largeMessagePendingTasks = Metrics.register(factory.createMetricName("LargeMessagePendingTasks"), messagingPool.large::pendingCount);
largeMessagePendingBytes = Metrics.register(factory.createMetricName("LargeMessagePendingBytes"), messagingPool.large::pendingBytes);
diff --git a/src/java/org/apache/cassandra/metrics/KeyspaceMetrics.java b/src/java/org/apache/cassandra/metrics/KeyspaceMetrics.java
index ae15bbf95a..83a1651fa7 100644
--- a/src/java/org/apache/cassandra/metrics/KeyspaceMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/KeyspaceMetrics.java
@@ -43,6 +43,7 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class KeyspaceMetrics
{
+ public static final String TYPE_NAME = "keyspace";
/** Total amount of live data stored in the memtable, excluding any data structure overhead */
public final Gauge<Long> memtableLiveDataSize;
/** Total amount of data stored in the memtable that resides on-heap, including column related overhead and partitions overwritten. */
@@ -424,11 +425,11 @@ public class KeyspaceMetrics
StringBuilder mbeanName = new StringBuilder();
mbeanName.append(groupName).append(":");
- mbeanName.append("type=Keyspace");
+ mbeanName.append("type=").append("Keyspace");
mbeanName.append(",keyspace=").append(keyspaceName);
mbeanName.append(",name=").append(metricName);
- return new MetricName(groupName, "keyspace", metricName, keyspaceName, mbeanName.toString());
+ return new MetricName(groupName, TYPE_NAME, metricName, keyspaceName, mbeanName.toString());
}
}
}
diff --git a/src/java/org/apache/cassandra/metrics/LatencyMetrics.java b/src/java/org/apache/cassandra/metrics/LatencyMetrics.java
index af8ad71be1..e4abd40bbf 100644
--- a/src/java/org/apache/cassandra/metrics/LatencyMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/LatencyMetrics.java
@@ -99,8 +99,8 @@ public class LatencyMetrics
}
else
{
- latency = Metrics.register(factory.createMetricName(namePrefix + "Latency"), aliasFactory.createMetricName(namePrefix + "Latency"), timer);
- totalLatency = Metrics.register(factory.createMetricName(namePrefix + "TotalLatency"), aliasFactory.createMetricName(namePrefix + "TotalLatency"), counter);
+ latency = Metrics.register(factory.createMetricName(namePrefix + "Latency"), timer, aliasFactory.createMetricName(namePrefix + "Latency"));
+ totalLatency = Metrics.register(factory.createMetricName(namePrefix + "TotalLatency"), counter, aliasFactory.createMetricName(namePrefix + "TotalLatency"));
}
}
@@ -164,16 +164,9 @@ public class LatencyMetrics
{
parent.removeChildren(this);
}
- if (aliasFactory == null)
- {
- Metrics.remove(factory.createMetricName(namePrefix + "Latency"));
- Metrics.remove(factory.createMetricName(namePrefix + "TotalLatency"));
- }
- else
- {
- Metrics.remove(factory.createMetricName(namePrefix + "Latency"), aliasFactory.createMetricName(namePrefix + "Latency"));
- Metrics.remove(factory.createMetricName(namePrefix + "TotalLatency"), aliasFactory.createMetricName(namePrefix + "TotalLatency"));
- }
+ // Aliases are already known to the parent metrics, so we don't need to remove them here.
+ Metrics.remove(factory.createMetricName(namePrefix + "Latency"));
+ Metrics.remove(factory.createMetricName(namePrefix + "TotalLatency"));
}
public class LatencyMetricsTimer extends Timer
diff --git a/src/java/org/apache/cassandra/metrics/MessagingMetrics.java b/src/java/org/apache/cassandra/metrics/MessagingMetrics.java
index bef6d08737..6151f57ee4 100644
--- a/src/java/org/apache/cassandra/metrics/MessagingMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/MessagingMetrics.java
@@ -49,7 +49,8 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class MessagingMetrics implements InboundMessageHandlers.GlobalMetricCallbacks
{
- private static final MetricNameFactory factory = new DefaultNameFactory("Messaging");
+ public static final String TYPE_NAME = "Messaging";
+ private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
private static final Logger logger = LoggerFactory.getLogger(MessagingMetrics.class);
private static final int LOG_DROPPED_INTERVAL_IN_MS = 5000;
diff --git a/src/java/org/apache/cassandra/metrics/MutualTlsMetrics.java b/src/java/org/apache/cassandra/metrics/MutualTlsMetrics.java
index bb32f0416f..c16cbe4060 100644
--- a/src/java/org/apache/cassandra/metrics/MutualTlsMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/MutualTlsMetrics.java
@@ -28,7 +28,8 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class MutualTlsMetrics
{
- private static final MetricNameFactory factory = new DefaultNameFactory("MutualTls");
+ public static final String TYPE_NAME = "MutualTls";
+ private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
public final static MutualTlsMetrics instance = new MutualTlsMetrics();
diff --git a/src/java/org/apache/cassandra/metrics/PaxosMetrics.java b/src/java/org/apache/cassandra/metrics/PaxosMetrics.java
index 1e1ba89ba2..45088a1d17 100644
--- a/src/java/org/apache/cassandra/metrics/PaxosMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/PaxosMetrics.java
@@ -25,7 +25,8 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
public class PaxosMetrics
{
- private static final MetricNameFactory factory = new DefaultNameFactory("Paxos");
+ public static final String TYPE_NAME = "Paxos";
+ private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
public static final Counter linearizabilityViolations = Metrics.counter(factory.createMetricName("LinearizabilityViolations"));
public static final Meter repairPaxosTopologyRetries = Metrics.meter(factory.createMetricName("RepairPaxosTopologyRetries"));
diff --git a/src/java/org/apache/cassandra/metrics/ReadRepairMetrics.java b/src/java/org/apache/cassandra/metrics/ReadRepairMetrics.java
index 32d62efd20..1adb5dcd7f 100644
--- a/src/java/org/apache/cassandra/metrics/ReadRepairMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/ReadRepairMetrics.java
@@ -26,7 +26,8 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class ReadRepairMetrics
{
- private static final MetricNameFactory factory = new DefaultNameFactory("ReadRepair");
+ public static final String TYPE_NAME = "ReadRepair";
+ private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
public static final Meter repairedBlocking = Metrics.meter(factory.createMetricName("RepairedBlocking"));
public static final Meter reconcileRead = Metrics.meter(factory.createMetricName("ReconcileRead"));
diff --git a/src/java/org/apache/cassandra/metrics/StorageMetrics.java b/src/java/org/apache/cassandra/metrics/StorageMetrics.java
index 57b57e831f..1cd65c0896 100644
--- a/src/java/org/apache/cassandra/metrics/StorageMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/StorageMetrics.java
@@ -32,6 +32,7 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class StorageMetrics
{
+ public static final String TYPE_NAME = "Storage";
public static final MetricNameFactory factory = new DefaultNameFactory("Storage");
public static final Counter load = Metrics.counter(factory.createMetricName("Load"));
diff --git a/src/java/org/apache/cassandra/metrics/StreamingMetrics.java b/src/java/org/apache/cassandra/metrics/StreamingMetrics.java
index 5246908637..eb5d840063 100644
--- a/src/java/org/apache/cassandra/metrics/StreamingMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/StreamingMetrics.java
@@ -78,7 +78,7 @@ public class StreamingMetrics
public StreamingMetrics(final InetAddressAndPort peer)
{
- MetricNameFactory factory = new DefaultNameFactory("Streaming", peer.toString().replace(':', '.'));
+ MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME, peer.toString().replace(':', '.'));
incomingBytes = Metrics.counter(factory.createMetricName("IncomingBytes"));
outgoingBytes= Metrics.counter(factory.createMetricName("OutgoingBytes"));
incomingProcessTime = Metrics.timer(factory.createMetricName("IncomingProcessTime"));
diff --git a/src/java/org/apache/cassandra/metrics/TCMMetrics.java b/src/java/org/apache/cassandra/metrics/TCMMetrics.java
index 61a3b9d1e9..134a1a34e2 100644
--- a/src/java/org/apache/cassandra/metrics/TCMMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/TCMMetrics.java
@@ -33,7 +33,8 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
public class TCMMetrics
{
- private static final MetricNameFactory factory = new DefaultNameFactory("TCM");
+ public static final String TYPE_NAME = "TCM";
+ private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME);
public static final TCMMetrics instance = new TCMMetrics();
diff --git a/src/java/org/apache/cassandra/metrics/TableMetrics.java b/src/java/org/apache/cassandra/metrics/TableMetrics.java
index 410562cdaa..7ab1cbbfbf 100644
--- a/src/java/org/apache/cassandra/metrics/TableMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/TableMetrics.java
@@ -71,13 +71,17 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime;
*/
public class TableMetrics
{
+ public static final String TYPE_NAME = "Table";
+ public static final String INDEX_TYPE_NAME = "IndexTable";
+ public static final String ALIAS_TYPE_NAME = "ColumnFamily";
+ public static final String INDEX_ALIAS_TYPE_NAME = "IndexColumnFamily";
/**
* stores metrics that will be rolled into a single global metric
*/
private static final ConcurrentMap<String, Set<Metric>> ALL_TABLE_METRICS = Maps.newConcurrentMap();
public static final long[] EMPTY = new long[0];
- private static final MetricNameFactory GLOBAL_FACTORY = new AllTableMetricNameFactory("Table");
- private static final MetricNameFactory GLOBAL_ALIAS_FACTORY = new AllTableMetricNameFactory("ColumnFamily");
+ private static final MetricNameFactory GLOBAL_FACTORY = new AllTableMetricNameFactory(TYPE_NAME);
+ private static final MetricNameFactory GLOBAL_ALIAS_FACTORY = new AllTableMetricNameFactory(ALIAS_TYPE_NAME);
public final static LatencyMetrics GLOBAL_READ_LATENCY = new LatencyMetrics(GLOBAL_FACTORY, GLOBAL_ALIAS_FACTORY, "Read");
public final static LatencyMetrics GLOBAL_WRITE_LATENCY = new LatencyMetrics(GLOBAL_FACTORY, GLOBAL_ALIAS_FACTORY, "Write");
@@ -403,8 +407,8 @@ public class TableMetrics
*/
public TableMetrics(final ColumnFamilyStore cfs, ReleasableMetric memtableMetrics)
{
- factory = new TableMetricNameFactory(cfs, "Table");
- aliasFactory = new TableMetricNameFactory(cfs, "ColumnFamily");
+ factory = new TableMetricNameFactory(cfs, cfs.isIndex() ? INDEX_TYPE_NAME : TYPE_NAME);
+ aliasFactory = new TableMetricNameFactory(cfs, cfs.isIndex() ? INDEX_ALIAS_TYPE_NAME : ALIAS_TYPE_NAME);
if (memtableMetrics != null)
{
@@ -932,10 +936,10 @@ public class TableMetrics
protected <G,T> Gauge<T> createTableGauge(String name, String alias, Gauge<T> gauge, Gauge<G> globalGauge)
{
- Gauge<T> cfGauge = Metrics.register(factory.createMetricName(name), aliasFactory.createMetricName(alias), gauge);
+ Gauge<T> cfGauge = Metrics.register(factory.createMetricName(name), gauge, aliasFactory.createMetricName(alias));
if (register(name, alias, cfGauge) && globalGauge != null)
{
- Metrics.register(GLOBAL_FACTORY.createMetricName(name), GLOBAL_ALIAS_FACTORY.createMetricName(alias), globalGauge);
+ Metrics.register(GLOBAL_FACTORY.createMetricName(name), globalGauge, GLOBAL_ALIAS_FACTORY.createMetricName(alias));
}
return cfGauge;
}
@@ -984,19 +988,14 @@ public class TableMetrics
if (register(name, alias, cfCounter))
{
Metrics.register(GLOBAL_FACTORY.createMetricName(name),
- GLOBAL_ALIAS_FACTORY.createMetricName(alias),
- new Gauge<Long>()
- {
- public Long getValue()
- {
- long total = 0;
- for (Metric cfGauge : ALL_TABLE_METRICS.get(name))
+ (Gauge<Long>) () ->
{
- total += ((Counter) cfGauge).getCount();
- }
- return total;
- }
- });
+ long total = 0;
+ for (Metric cfGauge : ALL_TABLE_METRICS.get(name))
+ total += ((Counter) cfGauge).getCount();
+ return total;
+ },
+ GLOBAL_ALIAS_FACTORY.createMetricName(alias));
}
return cfCounter;
}
@@ -1151,16 +1150,8 @@ public class TableMetrics
{
// Metric will be null if we are releasing a view metric. Views have null for ViewLockAcquireTime and ViewLockReadTime
ALL_TABLE_METRICS.get(tableMetricName).remove(metric);
- CassandraMetricsRegistry.MetricName cfAlias = aliasFactory.createMetricName(cfMetricName);
-
- if (tableMetricAlias != null)
- {
- Metrics.remove(name, cfAlias, factory.createMetricName(tableMetricAlias), aliasFactory.createMetricName(tableMetricAlias));
- }
- else
- {
- Metrics.remove(name, cfAlias);
- }
+ // Aliases are already known to the parent metrics, so we don't need to remove them here.
+ Metrics.remove(name);
}
}
@@ -1258,21 +1249,18 @@ public class TableMetrics
{
private final String keyspaceName;
private final String tableName;
- private final boolean isIndex;
private final String type;
TableMetricNameFactory(ColumnFamilyStore cfs, String type)
{
this.keyspaceName = cfs.getKeyspaceName();
this.tableName = cfs.name;
- this.isIndex = cfs.isIndex();
this.type = type;
}
public CassandraMetricsRegistry.MetricName createMetricName(String metricName)
{
String groupName = TableMetrics.class.getPackage().getName();
- String type = isIndex ? "Index" + this.type : this.type;
StringBuilder mbeanName = new StringBuilder();
mbeanName.append(groupName).append(":");
diff --git a/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java b/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java
index 95ab601fe5..c8a4dedc63 100644
--- a/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java
@@ -33,6 +33,7 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class ThreadPoolMetrics
{
+ public static final String TYPE_NAME = "ThreadPools";
public static final String ACTIVE_TASKS = "ActiveTasks";
public static final String PENDING_TASKS = "PendingTasks";
public static final String COMPLETED_TASKS = "CompletedTasks";
@@ -123,10 +124,10 @@ public class ThreadPoolMetrics
private static MetricName makeMetricName(String path, String poolName, String metricName)
{
return new MetricName("org.apache.cassandra.metrics",
- "ThreadPools",
+ TYPE_NAME,
metricName,
path + '.' + poolName,
- format("org.apache.cassandra.metrics:type=ThreadPools,path=%s,scope=%s,name=%s",
- path, poolName, metricName));
+ format("org.apache.cassandra.metrics:type=%s,path=%s,scope=%s,name=%s",
+ TYPE_NAME, path, poolName, metricName));
}
}
diff --git a/src/java/org/apache/cassandra/metrics/TrieMemtableMetricsView.java b/src/java/org/apache/cassandra/metrics/TrieMemtableMetricsView.java
index 9343503999..a308fbbab8 100644
--- a/src/java/org/apache/cassandra/metrics/TrieMemtableMetricsView.java
+++ b/src/java/org/apache/cassandra/metrics/TrieMemtableMetricsView.java
@@ -21,9 +21,11 @@ package org.apache.cassandra.metrics;
import com.codahale.metrics.Counter;
import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
+import static org.apache.cassandra.metrics.DefaultNameFactory.GROUP_NAME;
public class TrieMemtableMetricsView
{
+ public static final String TYPE_NAME = "TrieMemtable";
private static final String UNCONTENDED_PUTS = "Uncontended memtable puts";
private static final String CONTENDED_PUTS = "Contended memtable puts";
private static final String CONTENTION_TIME = "Contention time";
@@ -74,17 +76,14 @@ public class TrieMemtableMetricsView
public CassandraMetricsRegistry.MetricName createMetricName(String metricName)
{
- String groupName = TableMetrics.class.getPackage().getName();
- String type = "TrieMemtable";
-
StringBuilder mbeanName = new StringBuilder();
- mbeanName.append(groupName).append(":");
- mbeanName.append("type=").append(type);
+ mbeanName.append(GROUP_NAME).append(":");
+ mbeanName.append("type=").append(TYPE_NAME);
mbeanName.append(",keyspace=").append(keyspace);
mbeanName.append(",scope=").append(table);
mbeanName.append(",name=").append(metricName);
- return new CassandraMetricsRegistry.MetricName(groupName, type, metricName, keyspace + "." + table, mbeanName.toString());
+ return new CassandraMetricsRegistry.MetricName(GROUP_NAME, TYPE_NAME, metricName, keyspace + '.' + table, mbeanName.toString());
}
}
}
diff --git a/src/java/org/apache/cassandra/metrics/UnweightedCacheMetrics.java b/src/java/org/apache/cassandra/metrics/UnweightedCacheMetrics.java
index ff40f3a038..b651a51995 100644
--- a/src/java/org/apache/cassandra/metrics/UnweightedCacheMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/UnweightedCacheMetrics.java
@@ -27,6 +27,7 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
*/
public class UnweightedCacheMetrics extends AbstractCacheMetrics
{
+ public static final String TYPE_NAME = "UnweightedCache";
/**
* Cache capacity (maximum number of entries)
*/
@@ -45,7 +46,7 @@ public class UnweightedCacheMetrics extends AbstractCacheMetrics
*/
public UnweightedCacheMetrics(String type, UnweightedCacheSize cache)
{
- super(new DefaultNameFactory("UnweightedCache", type), type);
+ super(new DefaultNameFactory(TYPE_NAME, type), type);
maxEntries = Metrics.register(factory.createMetricName("MaxEntries"), cache::maxEntries);
entries = Metrics.register(factory.createMetricName("Entries"), cache::entries);
diff --git a/src/java/org/apache/cassandra/schema/SchemaConstants.java b/src/java/org/apache/cassandra/schema/SchemaConstants.java
index a6561e2522..bc733d367e 100644
--- a/src/java/org/apache/cassandra/schema/SchemaConstants.java
+++ b/src/java/org/apache/cassandra/schema/SchemaConstants.java
@@ -50,6 +50,7 @@ public final class SchemaConstants
public static final String VIRTUAL_SCHEMA = "system_virtual_schema";
public static final String VIRTUAL_VIEWS = "system_views";
+ public static final String VIRTUAL_METRICS = "system_metrics";
public static final String DUMMY_KEYSPACE_OR_TABLE_NAME = "--dummy--";
diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java b/src/java/org/apache/cassandra/service/CassandraDaemon.java
index 8518a9ba99..4ae0413529 100644
--- a/src/java/org/apache/cassandra/service/CassandraDaemon.java
+++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java
@@ -32,7 +32,6 @@ import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
-import javax.management.ObjectName;
import javax.management.StandardMBean;
import javax.management.remote.JMXConnectorServer;
@@ -57,6 +56,7 @@ import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.SystemKeyspaceMigrator41;
import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.db.virtual.SystemViewsKeyspace;
+import org.apache.cassandra.db.virtual.VirtualKeyspace;
import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
import org.apache.cassandra.db.virtual.VirtualSchemaKeyspace;
import org.apache.cassandra.exceptions.ConfigurationException;
@@ -100,6 +100,8 @@ import static org.apache.cassandra.config.CassandraRelevantProperties.JAVA_VERSI
import static org.apache.cassandra.config.CassandraRelevantProperties.JAVA_VM_NAME;
import static org.apache.cassandra.config.CassandraRelevantProperties.SIZE_RECORDER_INTERVAL;
import static org.apache.cassandra.config.CassandraRelevantProperties.START_NATIVE_TRANSPORT;
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.createMetricsKeyspaceTables;
+import static org.apache.cassandra.schema.SchemaConstants.VIRTUAL_METRICS;
/**
* The <code>CassandraDaemon</code> is an abstraction for a Cassandra daemon
@@ -132,8 +134,7 @@ public class CassandraDaemon
int separator = metricName.lastIndexOf('.');
String appenderName = metricName.substring(0, separator);
String metric = metricName.substring(separator + 1); // remove "."
- ObjectName name = DefaultNameFactory.createMetricName(appenderName, metric, null).getMBeanName();
- CassandraMetricsRegistry.Metrics.registerMBean(meter, name);
+ CassandraMetricsRegistry.Metrics.register(DefaultNameFactory.createMetricName(appenderName, metric, null), meter);
}
});
logger = LoggerFactory.getLogger(CassandraDaemon.class);
@@ -546,6 +547,7 @@ public class CassandraDaemon
{
VirtualKeyspaceRegistry.instance.register(VirtualSchemaKeyspace.instance);
VirtualKeyspaceRegistry.instance.register(SystemViewsKeyspace.instance);
+ VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(VIRTUAL_METRICS, createMetricsKeyspaceTables()));
// flush log messages to system_views.system_logs virtual table as there were messages already logged
// before that virtual table was instantiated
diff --git a/src/java/org/apache/cassandra/utils/EstimatedHistogram.java b/src/java/org/apache/cassandra/utils/EstimatedHistogram.java
index 198f92286f..7cd8dc4a20 100644
--- a/src/java/org/apache/cassandra/utils/EstimatedHistogram.java
+++ b/src/java/org/apache/cassandra/utils/EstimatedHistogram.java
@@ -442,4 +442,10 @@ public class EstimatedHistogram implements DoubleToLongFunction
return size;
}
}
+
+ @Override
+ public String toString()
+ {
+ return String.valueOf(mean());
+ }
}
diff --git a/src/java/org/apache/cassandra/utils/FBUtilities.java b/src/java/org/apache/cassandra/utils/FBUtilities.java
index d3728f95d8..414811aff3 100644
--- a/src/java/org/apache/cassandra/utils/FBUtilities.java
+++ b/src/java/org/apache/cassandra/utils/FBUtilities.java
@@ -1402,6 +1402,9 @@ public class FBUtilities
public static String camelToSnake(String camel)
{
+ if (camel.chars().allMatch(Character::isUpperCase))
+ return camel.toLowerCase();
+
StringBuilder sb = new StringBuilder();
for (char c : camel.toCharArray())
{
diff --git a/src/java/org/apache/cassandra/utils/memory/MemtablePool.java b/src/java/org/apache/cassandra/utils/memory/MemtablePool.java
index 26c47912a2..f77ea2fab1 100644
--- a/src/java/org/apache/cassandra/utils/memory/MemtablePool.java
+++ b/src/java/org/apache/cassandra/utils/memory/MemtablePool.java
@@ -29,6 +29,7 @@ import com.codahale.metrics.Gauge;
import com.codahale.metrics.Timer;
import org.apache.cassandra.metrics.CassandraMetricsRegistry;
import org.apache.cassandra.metrics.DefaultNameFactory;
+import org.apache.cassandra.metrics.MetricNameFactory;
import org.apache.cassandra.utils.concurrent.WaitQueue;
import org.apache.cassandra.utils.ExecutorUtils;
@@ -41,6 +42,7 @@ import static org.apache.cassandra.utils.concurrent.WaitQueue.newWaitQueue;
*/
public abstract class MemtablePool
{
+ public static final String TYPE_NAME = "MemtablePool";
final MemtableCleanerThread<?> cleaner;
// the total memory used by this pool
@@ -59,7 +61,7 @@ public abstract class MemtablePool
this.onHeap = getSubPool(maxOnHeapMemory, cleanThreshold);
this.offHeap = getSubPool(maxOffHeapMemory, cleanThreshold);
this.cleaner = getCleaner(cleaner);
- DefaultNameFactory nameFactory = new DefaultNameFactory("MemtablePool");
+ MetricNameFactory nameFactory = new DefaultNameFactory(TYPE_NAME);
blockedOnAllocating = CassandraMetricsRegistry.Metrics.timer(nameFactory.createMetricName("BlockedOnAllocation"));
numPendingTasks = CassandraMetricsRegistry.Metrics.register(nameFactory.createMetricName("PendingFlushTasks"),
() -> (long) this.cleaner.numPendingTasks());
diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java
index cb96bfff24..4eb5039094 100644
--- a/test/unit/org/apache/cassandra/cql3/CQLTester.java
+++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java
@@ -51,6 +51,9 @@ import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import javax.annotation.Nullable;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
@@ -88,6 +91,7 @@ import com.datastax.driver.core.Session;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.SocketOptions;
import com.datastax.driver.core.Statement;
+import com.datastax.driver.core.TypeCodec;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.UserType;
import com.datastax.driver.core.exceptions.UnauthorizedException;
@@ -134,6 +138,7 @@ import org.apache.cassandra.db.marshal.TupleType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.UUIDType;
import org.apache.cassandra.db.marshal.VectorType;
+import org.apache.cassandra.db.virtual.VirtualKeyspace;
import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
import org.apache.cassandra.db.virtual.VirtualSchemaKeyspace;
import org.apache.cassandra.dht.Murmur3Partitioner;
@@ -160,9 +165,9 @@ import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.transport.Event;
import org.apache.cassandra.transport.Message;
-import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.transport.SimpleClient;
@@ -187,8 +192,10 @@ import static org.apache.cassandra.cql3.SchemaElement.SchemaElementType.FUNCTION
import static org.apache.cassandra.cql3.SchemaElement.SchemaElementType.MATERIALIZED_VIEW;
import static org.apache.cassandra.cql3.SchemaElement.SchemaElementType.TABLE;
import static org.apache.cassandra.cql3.SchemaElement.SchemaElementType.TYPE;
-import static org.junit.Assert.assertNotNull;
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.createMetricsKeyspaceTables;
+import static org.apache.cassandra.schema.SchemaConstants.VIRTUAL_METRICS;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -477,6 +484,11 @@ public abstract class CQLTester
user = null;
}
+ protected static void addMetricsKeyspace()
+ {
+ VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(VIRTUAL_METRICS, createMetricsKeyspaceTables()));
+ }
+
protected void resetSchema() throws Throwable
{
for (TableMetadata table : SchemaKeyspace.metadata().tables)
@@ -1578,7 +1590,7 @@ public abstract class CQLTester
return sessions.computeIfAbsent(new ClusterSettings(user, protocolVersion, useEncryption, useClientCert), settings -> cluster.connect());
}
- private Cluster getCluster(ProtocolVersion protocolVersion)
+ protected Cluster getCluster(ProtocolVersion protocolVersion)
{
return clusters.computeIfAbsent(new ClusterSettings(user, protocolVersion, useEncryption, useClientCert), settings -> initClientCluster(user, protocolVersion, useEncryption, useClientCert));
}
@@ -1764,6 +1776,96 @@ public abstract class CQLTester
rows.length>i ? "less" : "more", rows.length, i, protocolVersion), i == rows.length);
}
+ public void assertRowsContains(ResultSet result, Object[]... rows)
+ {
+ assertRowsContains(getCluster(getDefaultVersion()), result, rows);
+ }
+
+ public void assertRowsContains(ResultSet result, List<Object[]> rows)
+ {
+ assertRowsContains(getCluster(getDefaultVersion()), result, rows);
+ }
+
+ public static void assertRowsContains(Cluster cluster, ResultSet result, Object[]... rows)
+ {
+ assertRowsContains(cluster, result, rows == null ? Collections.emptyList() : Arrays.asList(rows));
+ }
+
+ public static void assertRowsContains(Cluster cluster, ResultSet result, List<Object[]> rows)
+ {
+ if (result == null && rows.isEmpty())
+ return;
+ assertNotNull(String.format("No rows returned by query but %d expected", rows.size()), result);
+ assertTrue(result.iterator().hasNext());
+
+ // It is necessary that all rows match the column definitions
+ for (Object[] row : rows)
+ {
+ if (row == null || row.length == 0)
+ Assert.fail("Rows must not be null or empty");
+
+ if (result.getColumnDefinitions().size() == row.length)
+ continue;
+
+ Assert.fail(String.format("Rows do not match column definitions. Expected %d columns but got %d",
+ result.getColumnDefinitions().size(), row.length));
+ }
+
+ ColumnDefinitions defs = result.getColumnDefinitions();
+ int size = defs.size();
+ List<ByteBuffer[]> resultSetValues = StreamSupport.stream(result.spliterator(), false)
+ .map(row -> IntStream.range(0, size)
+ .mapToObj(row::getBytesUnsafe)
+ .toArray(ByteBuffer[]::new))
+ .collect(Collectors.toList());
+
+ AtomicInteger columnCounter = new AtomicInteger();
+ com.datastax.driver.core.ProtocolVersion version = com.datastax.driver.core.ProtocolVersion.fromInt(getDefaultVersion().asInt());
+ List<ByteBuffer[]> expectedRowsValues = rows.stream()
+ .map(row -> Stream.of(row)
+ .map(cell -> {
+ int index = columnCounter.getAndIncrement() % size;
+ return cell instanceof ByteBuffer ? (ByteBuffer) cell :
+ cluster.getConfiguration()
+ .getCodecRegistry()
+ .codecFor(defs.getType(index))
+ .serialize(cell, version);
+ })
+ .toArray(ByteBuffer[]::new))
+ .collect(Collectors.toList());
+
+ int found = 0;
+ for (ByteBuffer[] expected : expectedRowsValues)
+ for (ByteBuffer[] actual : resultSetValues)
+ if (Arrays.equals(expected, actual))
+ found++;
+
+ if (found == expectedRowsValues.size())
+ return;
+
+ Assert.fail(String.format("Result set does not contain expected rows. Result set %s doesn't contain %s",
+ resultSetValues.stream().map(e -> deserializeCells(e, cluster.getConfiguration().getCodecRegistry(),
+ result.getColumnDefinitions(), version)).collect(Collectors.joining(", ")),
+ expectedRowsValues.stream().map(e -> deserializeCells(e, cluster.getConfiguration().getCodecRegistry(),
+ result.getColumnDefinitions(), version)).collect(Collectors.joining(", "))));
+ }
+
+ private static String deserializeCells(ByteBuffer[] row,
+ com.datastax.driver.core.CodecRegistry codecRegistry,
+ ColumnDefinitions columnDefinitions,
+ com.datastax.driver.core.ProtocolVersion version)
+ {
+ AtomicInteger index = new AtomicInteger();
+ int size = columnDefinitions.size();
+ return Stream.of(row)
+ .map(b -> {
+ int idx = index.getAndIncrement() % size;
+ TypeCodec<Object> codec = codecRegistry.codecFor(columnDefinitions.getType(idx));
+ return codec.format(codec.deserialize(b, version));
+ })
+ .collect(Collectors.joining(", ", "[", "]"));
+ }
+
private static String safeToString(Supplier<String> fn)
{
try
diff --git a/test/unit/org/apache/cassandra/db/virtual/CollectionVirtualTableAdapterTest.java b/test/unit/org/apache/cassandra/db/virtual/CollectionVirtualTableAdapterTest.java
new file mode 100644
index 0000000000..2a009b791c
--- /dev/null
+++ b/test/unit/org/apache/cassandra/db/virtual/CollectionVirtualTableAdapterTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Row;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.db.virtual.model.CollectionEntry;
+import org.apache.cassandra.db.virtual.model.CollectionEntryTestRow;
+import org.apache.cassandra.db.virtual.model.PartitionEntryTestRow;
+import org.apache.cassandra.db.virtual.walker.CollectionEntryTestRowWalker;
+import org.apache.cassandra.db.virtual.walker.PartitionEntryTestRowWalker;
+
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.METRIC_SCOPE_UNDEFINED;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test that verifies the functionality of the collection virtual table adapter, which is used to expose internal
+ * collection data as a virtual table. This test is a bit more complex than the other virtual table tests because it
+ * tests the functionality of the virtual table adapter itself, rather than just the virtual table.
+ */
+public class CollectionVirtualTableAdapterTest extends CQLTester
+{
+ private static final String KS_NAME = "vts";
+ private static final String VT_NAME = "collection_virtual_table";
+ private static final String VT_NAME_1 = "map_key_filter_virtual_table";
+ private static final String VT_NAME_2 = "map_value_filter_virtual_table";
+ private final List<VirtualTable> tables = new ArrayList<>();
+ private final List<CollectionEntry> internalTestCollection = new ArrayList<>();
+ private final Map<String, CollectionEntry> internalTestMap = new HashMap<>();
+
+ private static void addSinglePartitionData(Collection<CollectionEntry> list)
+ {
+ list.add(new CollectionEntry("1984", "key", 3, "value",
+ 1, 1, 1, (short) 1, (byte) 1, true));
+ list.add(new CollectionEntry("1984", "key", 2, "value",
+ 1, 1, 1, (short) 1, (byte) 1, true));
+ list.add(new CollectionEntry("1984", "key", 1, "value",
+ 1, 1, 1, (short) 1, (byte) 1, true));
+ }
+
+ private static void addMultiPartitionData(Collection<CollectionEntry> list)
+ {
+ addSinglePartitionData(list);
+ list.add(new CollectionEntry("1985", "key", 3, "value",
+ 1, 1, 1, (short) 1, (byte) 1, true));
+ list.add(new CollectionEntry("1985", "key", 2, "value",
+ 1, 1, 1, (short) 1, (byte) 1, true));
+ list.add(new CollectionEntry("1985", "key", 1, "value",
+ 1, 1, 1, (short) 1, (byte) 1, true));
+ }
+
+ @Before
+ public void config() throws Exception
+ {
+ tables.add(CollectionVirtualTableAdapter.create(
+ KS_NAME,
+ VT_NAME,
+ "The collection virtual table",
+ new CollectionEntryTestRowWalker(),
+ internalTestCollection,
+ CollectionEntryTestRow::new));
+ tables.add(CollectionVirtualTableAdapter.createSinglePartitionedKeyFiltered(
+ KS_NAME,
+ VT_NAME_1,
+ "The partition key filtered virtual table",
+ new PartitionEntryTestRowWalker(),
+ internalTestMap,
+ internalTestMap::containsKey,
+ PartitionEntryTestRow::new));
+ tables.add(CollectionVirtualTableAdapter.createSinglePartitionedValueFiltered(
+ KS_NAME,
+ VT_NAME_2,
+ "The partition value filtered virtual table",
+ new PartitionEntryTestRowWalker(),
+ internalTestMap,
+ value -> value instanceof CollectionEntryExt,
+ PartitionEntryTestRow::new));
+ VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, tables));
+ }
+
+ @After
+ public void postCleanup()
+ {
+ internalTestCollection.clear();
+ internalTestMap.clear();
+ }
+
+ @Test
+ public void testSelectAll()
+ {
+ addSinglePartitionData(internalTestCollection);
+ List<CollectionEntry> sortedClustering = new ArrayList<>(internalTestCollection);
+ sortedClustering.sort(Comparator.comparingLong(CollectionEntry::getOrderedKey));
+ ResultSet result = executeNet(String.format("SELECT * FROM %s.%s", KS_NAME, VT_NAME));
+
+ int index = 0;
+ for (Row row : result)
+ {
+ assertEquals(sortedClustering.get(index).getPrimaryKey(), row.getString("primary_key"));
+ assertEquals(sortedClustering.get(index).getSecondaryKey(), row.getString("secondary_key"));
+ assertEquals(sortedClustering.get(index).getOrderedKey(), row.getLong("ordered_key"));
+ assertEquals(sortedClustering.get(index).getIntValue(), row.getInt("int_value"));
+ assertEquals(sortedClustering.get(index).getLongValue(), row.getLong("long_value"));
+ assertEquals(sortedClustering.get(index).getValue(), row.getString("value"));
+ assertEquals(sortedClustering.get(index).getDoubleValue(), row.getDouble("double_value"), 0.0);
+ assertEquals(sortedClustering.get(index).getShortValue(), row.getShort("short_value"));
+ assertEquals(sortedClustering.get(index).getByteValue(), row.getByte("byte_value"));
+ assertEquals(sortedClustering.get(index).getBooleanValue(), row.getBool("boolean_value"));
+ index++;
+ }
+ assertEquals(sortedClustering.size(), index);
+ }
+
+ @Test
+ public void testSelectPartition()
+ {
+ addMultiPartitionData(internalTestCollection);
+ ResultSet result = executeNet(
+ String.format("SELECT * FROM %s.%s WHERE primary_key = ? AND secondary_key = ?", KS_NAME, VT_NAME),
+ "1984", "key");
+
+ AtomicInteger size = new AtomicInteger(3);
+ result.forEach(row -> {
+ assertEquals("1984", row.getString("primary_key"));
+ assertEquals("key", row.getString("secondary_key"));
+ size.decrementAndGet();
+ });
+ assertEquals(0, size.get());
+ }
+
+ @Test
+ public void testSelectPartitionMap()
+ {
+ internalTestMap.put("1984", new CollectionEntry("primary", "key", 3, "value",
+ 1, 1, 1, (short) 1, (byte) 1, true));
+ ResultSet result = executeNet(String.format("SELECT * FROM %s.%s WHERE key = ?", KS_NAME, VT_NAME_1),
+ "1984");
+
+ AtomicInteger size = new AtomicInteger(1);
+ result.forEach(row -> {
+ assertEquals("1984", row.getString("key"));
+ assertEquals("primary", row.getString("primary_key"));
+ assertEquals("key", row.getString("secondary_key"));
+ size.decrementAndGet();
+ });
+ assertEquals(0, size.get());
+ }
+
+ @Test
+ public void testSelectPartitionUnknownKey()
+ {
+ internalTestMap.put("1984", new CollectionEntry("primary", "key", 3, "value",
+ 1, 1, 1, (short) 1, (byte) 1, true));
+ ResultSet first = executeNet(String.format("SELECT * FROM %s.%s WHERE key = ?", KS_NAME, VT_NAME_1),
+ METRIC_SCOPE_UNDEFINED);
+ assertEquals(0, first.all().size());
+
+ addSinglePartitionData(internalTestCollection);
+ ResultSet second = executeNet(
+ String.format("SELECT * FROM %s.%s WHERE primary_key = ? AND secondary_key = ?", KS_NAME, VT_NAME),
+ METRIC_SCOPE_UNDEFINED, "key");
+ assertEquals(0, second.all().size());
+ }
+
+ @Test
+ public void testSelectPartitionValueFitered()
+ {
+ internalTestMap.put("1984", new CollectionEntry("primary", "key", 3, "value",
+ 1, 1, 1, (short) 1, (byte) 1, true));
+ internalTestMap.put("1985", new CollectionEntryExt("primary", "key", 3, "value",
+ 1, 1, 1, (short) 1, (byte) 1, true, "extra"));
+
+ ResultSet first = executeNet(String.format("SELECT * FROM %s.%s WHERE key = ?", KS_NAME, VT_NAME_2),
+ "1984");
+ assertEquals(0, first.all().size());
+
+ ResultSet second = executeNet(String.format("SELECT * FROM %s.%s WHERE key = ?", KS_NAME, VT_NAME_2),
+ "1985");
+ assertEquals(1, second.all().size());
+ }
+
+ @Test
+ public void testSelectEmptyPartition()
+ {
+ addSinglePartitionData(internalTestCollection);
+ assertRowsNet(executeNet(String.format("SELECT * FROM %s.%s WHERE primary_key = 'EMPTY'", KS_NAME, VT_NAME)));
+ }
+
+ @Test
+ public void testSelectEmptyCollection()
+ {
+ internalTestCollection.clear();
+ assertRowsNet(executeNet(String.format("SELECT * FROM %s.%s", KS_NAME, VT_NAME)));
+ }
+
+ private static class CollectionEntryExt extends CollectionEntry
+ {
+ public CollectionEntryExt(String primaryKey,
+ String secondaryKey,
+ long orderedKey,
+ String value,
+ int intValue,
+ long longValue,
+ double doubleValue,
+ short shortValue,
+ byte byteValue,
+ boolean booleanValue,
+ String extraColumn)
+ {
+ super(primaryKey, secondaryKey, orderedKey, value, intValue, longValue, doubleValue, shortValue, byteValue,
+ booleanValue);
+ }
+ }
+}
diff --git a/test/unit/org/apache/cassandra/db/virtual/model/CollectionEntry.java b/test/unit/org/apache/cassandra/db/virtual/model/CollectionEntry.java
new file mode 100644
index 0000000000..70c86f5a51
--- /dev/null
+++ b/test/unit/org/apache/cassandra/db/virtual/model/CollectionEntry.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.model;
+
+/**
+ * Test collection entry represents an internal collection entry of a Cassandra collection.
+ */
+public class CollectionEntry
+{
+ private final String primaryKey;
+ private final String secondaryKey;
+ private final long orderedKey;
+ private final String value;
+ private final int intValue;
+ private final long longValue;
+ private final double doubleValue;
+ private final short shortValue;
+ private final byte byteValue;
+ private final boolean booleanValue;
+
+ public CollectionEntry(String primaryKey,
+ String secondaryKey,
+ long orderedKey,
+ String value,
+ int intValue,
+ long longValue,
+ double doubleValue,
+ short shortValue,
+ byte byteValue,
+ boolean booleanValue)
+ {
+ this.primaryKey = primaryKey;
+ this.secondaryKey = secondaryKey;
+ this.orderedKey = orderedKey;
+ this.value = value;
+ this.intValue = intValue;
+ this.longValue = longValue;
+ this.doubleValue = doubleValue;
+ this.shortValue = shortValue;
+ this.byteValue = byteValue;
+ this.booleanValue = booleanValue;
+ }
+
+ public String getPrimaryKey()
+ {
+ return primaryKey;
+ }
+
+ public String getSecondaryKey()
+ {
+ return secondaryKey;
+ }
+
+ public long getOrderedKey()
+ {
+ return orderedKey;
+ }
+
+ public String getValue()
+ {
+ return value;
+ }
+
+ public int getIntValue()
+ {
+ return intValue;
+ }
+
+ public long getLongValue()
+ {
+ return longValue;
+ }
+
+ public double getDoubleValue()
+ {
+ return doubleValue;
+ }
+
+ public short getShortValue()
+ {
+ return shortValue;
+ }
+
+ public byte getByteValue()
+ {
+ return byteValue;
+ }
+
+ public boolean getBooleanValue()
+ {
+ return booleanValue;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "CollectionEntry{" +
+ "primaryKey='" + primaryKey + '\'' +
+ ", secondaryKey='" + secondaryKey + '\'' +
+ ", orderedKey=" + orderedKey +
+ ", value='" + value + '\'' +
+ ", intValue=" + intValue +
+ ", longValue=" + longValue +
+ ", doubleValue=" + doubleValue +
+ ", shortValue=" + shortValue +
+ ", byteValue=" + byteValue +
+ ", booleanValue=" + booleanValue +
+ '}';
+ }
+}
diff --git a/test/unit/org/apache/cassandra/db/virtual/model/CollectionEntryTestRow.java b/test/unit/org/apache/cassandra/db/virtual/model/CollectionEntryTestRow.java
new file mode 100644
index 0000000000..8d12f4e50d
--- /dev/null
+++ b/test/unit/org/apache/cassandra/db/virtual/model/CollectionEntryTestRow.java
@@ -0,0 +1,93 @@
+/*
+ * 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.model;
+
+
+/**
+ * Test meter metric test representation for a {@link org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter}.
+ */
+public class CollectionEntryTestRow
+{
+ private final CollectionEntry collectionEntry;
+
+ public CollectionEntryTestRow(CollectionEntry collectionEntry)
+ {
+ this.collectionEntry = collectionEntry;
+ }
+
+ @Column(type = Column.Type.PARTITION_KEY)
+ public String primaryKey()
+ {
+ return collectionEntry.getPrimaryKey();
+ }
+
+ @Column(type = Column.Type.PARTITION_KEY)
+ public String secondaryKey()
+ {
+ return collectionEntry.getSecondaryKey();
+ }
+
+ @Column(type = Column.Type.CLUSTERING)
+ public long orderedKey()
+ {
+ return collectionEntry.getOrderedKey();
+ }
+
+ @Column
+ public long longValue()
+ {
+ return collectionEntry.getLongValue();
+ }
+
+ @Column
+ public int intValue()
+ {
+ return collectionEntry.getIntValue();
+ }
+
+ @Column
+ public short shortValue()
+ {
+ return collectionEntry.getShortValue();
+ }
+
+ @Column
+ public byte byteValue()
+ {
+ return collectionEntry.getByteValue();
+ }
+
+ @Column
+ public double doubleValue()
+ {
+ return collectionEntry.getDoubleValue();
+ }
+
+ @Column
+ public String value()
+ {
+ return collectionEntry.getValue();
+ }
+
+ @Column
+ public boolean booleanValue()
+ {
+ return collectionEntry.getBooleanValue();
+ }
+}
diff --git a/src/java/org/apache/cassandra/metrics/PaxosMetrics.java b/test/unit/org/apache/cassandra/db/virtual/model/PartitionEntryTestRow.java
similarity index 51%
copy from src/java/org/apache/cassandra/metrics/PaxosMetrics.java
copy to test/unit/org/apache/cassandra/db/virtual/model/PartitionEntryTestRow.java
index 1e1ba89ba2..10ebdefd5b 100644
--- a/src/java/org/apache/cassandra/metrics/PaxosMetrics.java
+++ b/test/unit/org/apache/cassandra/db/virtual/model/PartitionEntryTestRow.java
@@ -16,18 +16,38 @@
* limitations under the License.
*/
-package org.apache.cassandra.metrics;
+package org.apache.cassandra.db.virtual.model;
-import com.codahale.metrics.Counter;
-import com.codahale.metrics.Meter;
-import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
-
-public class PaxosMetrics
+/**
+ * Test meter metric test representation for a {@link org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter}.
+ */
+public class PartitionEntryTestRow
{
- private static final MetricNameFactory factory = new DefaultNameFactory("Paxos");
- public static final Counter linearizabilityViolations = Metrics.counter(factory.createMetricName("LinearizabilityViolations"));
- public static final Meter repairPaxosTopologyRetries = Metrics.meter(factory.createMetricName("RepairPaxosTopologyRetries"));
+ private final String key;
+ private final CollectionEntry collectionEntry;
+
+ public PartitionEntryTestRow(String key, CollectionEntry collectionEntry)
+ {
+ this.key = key;
+ this.collectionEntry = collectionEntry;
+ }
+
+ @Column(type = Column.Type.PARTITION_KEY)
+ public String key()
+ {
+ return key;
+ }
+
+ @Column
+ public String primaryKey()
+ {
+ return collectionEntry.getPrimaryKey();
+ }
- public static void initialize() {}
+ @Column
+ public String secondaryKey()
+ {
+ return collectionEntry.getSecondaryKey();
+ }
}
diff --git a/test/unit/org/apache/cassandra/db/virtual/walker/CollectionEntryTestRowWalker.java b/test/unit/org/apache/cassandra/db/virtual/walker/CollectionEntryTestRowWalker.java
new file mode 100644
index 0000000000..f6ea8d6904
--- /dev/null
+++ b/test/unit/org/apache/cassandra/db/virtual/walker/CollectionEntryTestRowWalker.java
@@ -0,0 +1,75 @@
+/*
+ * 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
+ *
+ * https://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.walker;
+
+import org.apache.cassandra.db.virtual.model.CollectionEntryTestRow;
+import org.apache.cassandra.db.virtual.model.Column;
+
+/**
+ * aThe {@link org.apache.cassandra.db.virtual.model.CollectionEntryTestRow} row metadata and data walker.
+ *
+ * @see org.apache.cassandra.db.virtual.model.CollectionEntryTestRow
+ */
+public class CollectionEntryTestRowWalker implements RowWalker<CollectionEntryTestRow>
+{
+ @Override
+ public void visitMeta(MetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "primary_key", String.class);
+ visitor.accept(Column.Type.PARTITION_KEY, "secondary_key", String.class);
+ visitor.accept(Column.Type.CLUSTERING, "ordered_key", Long.TYPE);
+ visitor.accept(Column.Type.REGULAR, "boolean_value", Boolean.TYPE);
+ visitor.accept(Column.Type.REGULAR, "byte_value", Byte.TYPE);
+ visitor.accept(Column.Type.REGULAR, "double_value", Double.TYPE);
+ visitor.accept(Column.Type.REGULAR, "int_value", Integer.TYPE);
+ visitor.accept(Column.Type.REGULAR, "long_value", Long.TYPE);
+ visitor.accept(Column.Type.REGULAR, "short_value", Short.TYPE);
+ visitor.accept(Column.Type.REGULAR, "value", String.class);
+ }
+
+ @Override
+ public void visitRow(CollectionEntryTestRow row, RowMetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "primary_key", String.class, row::primaryKey);
+ visitor.accept(Column.Type.PARTITION_KEY, "secondary_key", String.class, row::secondaryKey);
+ visitor.accept(Column.Type.CLUSTERING, "ordered_key", Long.TYPE, row::orderedKey);
+ visitor.accept(Column.Type.REGULAR, "boolean_value", Boolean.TYPE, row::booleanValue);
+ visitor.accept(Column.Type.REGULAR, "byte_value", Byte.TYPE, row::byteValue);
+ visitor.accept(Column.Type.REGULAR, "double_value", Double.TYPE, row::doubleValue);
+ visitor.accept(Column.Type.REGULAR, "int_value", Integer.TYPE, row::intValue);
+ visitor.accept(Column.Type.REGULAR, "long_value", Long.TYPE, row::longValue);
+ visitor.accept(Column.Type.REGULAR, "short_value", Short.TYPE, row::shortValue);
+ visitor.accept(Column.Type.REGULAR, "value", String.class, row::value);
+ }
+
+ @Override
+ public int count(Column.Type type)
+ {
+ switch (type)
+ {
+ case CLUSTERING:
+ return 1;
+ case PARTITION_KEY:
+ return 2;
+ case REGULAR:
+ return 7;
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+}
diff --git a/test/unit/org/apache/cassandra/db/virtual/walker/PartitionEntryTestRowWalker.java b/test/unit/org/apache/cassandra/db/virtual/walker/PartitionEntryTestRowWalker.java
new file mode 100644
index 0000000000..241f3b68df
--- /dev/null
+++ b/test/unit/org/apache/cassandra/db/virtual/walker/PartitionEntryTestRowWalker.java
@@ -0,0 +1,61 @@
+/*
+ * 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
+ *
+ * https://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.walker;
+
+import org.apache.cassandra.db.virtual.model.Column;
+import org.apache.cassandra.db.virtual.model.PartitionEntryTestRow;
+
+/**
+ * The {@link org.apache.cassandra.db.virtual.model.PartitionEntryTestRow} row metadata and data walker.
+ *
+ * @see org.apache.cassandra.db.virtual.model.PartitionEntryTestRow
+ */
+public class PartitionEntryTestRowWalker implements RowWalker<PartitionEntryTestRow>
+{
+ @Override
+ public void visitMeta(MetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "key", String.class);
+ visitor.accept(Column.Type.REGULAR, "primary_key", String.class);
+ visitor.accept(Column.Type.REGULAR, "secondary_key", String.class);
+ }
+
+ @Override
+ public void visitRow(PartitionEntryTestRow row, RowMetadataVisitor visitor)
+ {
+ visitor.accept(Column.Type.PARTITION_KEY, "key", String.class, row::key);
+ visitor.accept(Column.Type.REGULAR, "primary_key", String.class, row::primaryKey);
+ visitor.accept(Column.Type.REGULAR, "secondary_key", String.class, row::secondaryKey);
+ }
+
+ @Override
+ public int count(Column.Type type)
+ {
+ switch (type)
+ {
+ case CLUSTERING:
+ return 0;
+ case PARTITION_KEY:
+ return 1;
+ case REGULAR:
+ return 2;
+ default:
+ throw new IllegalStateException("Unknown column type: " + type);
+ }
+ }
+}
diff --git a/test/unit/org/apache/cassandra/metrics/BatchMetricsTest.java b/test/unit/org/apache/cassandra/metrics/BatchMetricsTest.java
index b90f19a7fc..6cd0886a01 100644
--- a/test/unit/org/apache/cassandra/metrics/BatchMetricsTest.java
+++ b/test/unit/org/apache/cassandra/metrics/BatchMetricsTest.java
@@ -34,7 +34,10 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.service.EmbeddedCassandraService;
+import static org.apache.cassandra.cql3.CQLTester.assertRowsContains;
+import static org.apache.cassandra.cql3.CQLTester.row;
import static org.apache.cassandra.cql3.statements.BatchStatement.metrics;
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.METRIC_SCOPE_UNDEFINED;
import static org.apache.cassandra.metrics.DecayingEstimatedHistogramReservoir.EstimatedHistogramReservoirSnapshot;
import static org.apache.cassandra.metrics.DecayingEstimatedHistogramReservoir.Range;
import static org.junit.Assert.assertEquals;
@@ -164,6 +167,14 @@ public class BatchMetricsTest
assertEquals(expectedPartitionsPerLoggedBatchCount, metrics.partitionsPerLoggedBatch.getCount());
assertEquals(expectedPartitionsPerCounterBatchCount, metrics.partitionsPerCounterBatch.getCount());
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.batch_group"),
+ row("org.apache.cassandra.metrics.Batch.PartitionsPerUnloggedBatch",
+ METRIC_SCOPE_UNDEFINED, "histogram", String.valueOf(metrics.partitionsPerUnloggedBatch.getSnapshot().getMedian())),
+ row("org.apache.cassandra.metrics.Batch.PartitionsPerLoggedBatch",
+ METRIC_SCOPE_UNDEFINED, "histogram", String.valueOf(metrics.partitionsPerLoggedBatch.getSnapshot().getMedian())),
+ row("org.apache.cassandra.metrics.Batch.PartitionsPerCounterBatch",
+ METRIC_SCOPE_UNDEFINED, "histogram", String.valueOf(metrics.partitionsPerCounterBatch.getSnapshot().getMedian())));
+
EstimatedHistogramReservoirSnapshot partitionsPerLoggedBatchSnapshot = (EstimatedHistogramReservoirSnapshot) metrics.partitionsPerLoggedBatch.getSnapshot();
EstimatedHistogramReservoirSnapshot partitionsPerUnloggedBatchSnapshot = (EstimatedHistogramReservoirSnapshot) metrics.partitionsPerUnloggedBatch.getSnapshot();
EstimatedHistogramReservoirSnapshot partitionsPerCounterBatchSnapshot = (EstimatedHistogramReservoirSnapshot) metrics.partitionsPerCounterBatch.getSnapshot();
diff --git a/test/unit/org/apache/cassandra/metrics/CQLMetricsTest.java b/test/unit/org/apache/cassandra/metrics/CQLMetricsTest.java
index c41ded37db..4c447a7012 100644
--- a/test/unit/org/apache/cassandra/metrics/CQLMetricsTest.java
+++ b/test/unit/org/apache/cassandra/metrics/CQLMetricsTest.java
@@ -35,6 +35,9 @@ import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.service.EmbeddedCassandraService;
+import static org.apache.cassandra.cql3.CQLTester.assertRowsContains;
+import static org.apache.cassandra.cql3.CQLTester.row;
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.METRIC_SCOPE_UNDEFINED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -85,6 +88,10 @@ public class CQLMetricsTest
{
DatabaseDescriptor.setUseStatementsEnabled(true);
}
+
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.cql_group"),
+ row("org.apache.cassandra.metrics.CQL.UseStatementsExecuted", METRIC_SCOPE_UNDEFINED, "counter",
+ String.valueOf(useCountBefore)));
}
@Test
@@ -96,6 +103,10 @@ public class CQLMetricsTest
Assert.assertEquals(useCountBefore + 1, QueryProcessor.metrics.useStatementsExecuted.getCount());
session.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
@@ -128,6 +139,12 @@ public class CQLMetricsTest
assertEquals(10, QueryProcessor.metrics.preparedStatementsExecuted.getCount());
assertEquals(0, QueryProcessor.metrics.regularStatementsExecuted.getCount());
+
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.cql_group"),
+ row("org.apache.cassandra.metrics.CQL.RegularStatementsExecuted", METRIC_SCOPE_UNDEFINED, "counter",
+ String.valueOf(QueryProcessor.metrics.regularStatementsExecuted.getCount())),
+ row("org.apache.cassandra.metrics.CQL.PreparedStatementsExecuted", METRIC_SCOPE_UNDEFINED, "counter",
+ String.valueOf(QueryProcessor.metrics.preparedStatementsExecuted.getCount())));
}
@Test
@@ -145,6 +162,10 @@ public class CQLMetricsTest
for (int i = 0; i < 10; i++)
session.execute(String.format("INSERT INTO junit.metricstest (id, val) VALUES (%d, '%s')", i, "val" + i));
assertEquals(0.5, QueryProcessor.metrics.preparedStatementsRatio.getValue(), 0.0);
+
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.cql_group"),
+ row("org.apache.cassandra.metrics.CQL.PreparedStatementsRatio", METRIC_SCOPE_UNDEFINED, "gauge",
+ String.valueOf(QueryProcessor.metrics.preparedStatementsRatio.getValue())));
}
private void clearMetrics()
diff --git a/test/unit/org/apache/cassandra/metrics/CassandraMetricsRegistryTest.java b/test/unit/org/apache/cassandra/metrics/CassandraMetricsRegistryTest.java
index 02e4228c3f..897265ef4d 100644
--- a/test/unit/org/apache/cassandra/metrics/CassandraMetricsRegistryTest.java
+++ b/test/unit/org/apache/cassandra/metrics/CassandraMetricsRegistryTest.java
@@ -20,22 +20,37 @@
*/
package org.apache.cassandra.metrics;
-import static org.junit.Assert.*;
-
import java.lang.management.ManagementFactory;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
-import com.codahale.metrics.Timer;
-import org.apache.cassandra.metrics.CassandraMetricsRegistry.MetricName;
-
+import org.junit.Before;
import org.junit.Test;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistryListener;
+import com.codahale.metrics.Timer;
import com.codahale.metrics.jvm.BufferPoolMetricSet;
import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
+import org.apache.cassandra.metrics.CassandraMetricsRegistry.MetricName;
import org.apache.cassandra.utils.EstimatedHistogram;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
public class CassandraMetricsRegistryTest
{
@@ -44,6 +59,12 @@ public class CassandraMetricsRegistryTest
{
}
+ @Before
+ public void prepare()
+ {
+ CassandraMetricsRegistry.Metrics.getNames().forEach(CassandraMetricsRegistry.Metrics::remove);
+ }
+
@Test
public void testChooseType()
{
@@ -90,6 +111,14 @@ public class CassandraMetricsRegistryTest
}
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testMetricSetRegistration()
+ {
+ CassandraMetricsRegistry registry = CassandraMetricsRegistry.Metrics;
+ DefaultNameFactory factory = new DefaultNameFactory("ThreadPools", "TestMetricSetRegistration");
+ registry.register(factory.createMetricName("jvm.buffers"), new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer()));
+ }
+
@Test
public void testDeltaBaseCase()
{
@@ -156,6 +185,84 @@ public class CassandraMetricsRegistryTest
assertEquals(expectedBucketsWithValues, bucketsWithValues);
}
+ @Test
+ public void testMetricAliasesOrder()
+ {
+ String dummy = "defaultName";
+ LinkedList<MetricName> aliases = new LinkedList<>();
+ int size = ThreadLocalRandom.current().nextInt(10, 1000);
+ MetricName first = DefaultNameFactory.createMetricName("Table", "FirstTestMetricAliasesOrder", "FirstScope");
+ for (int i = 0; i < size; i++)
+ aliases.add(DefaultNameFactory.createMetricName("Table", "FirstTestMetricAliasesOrder" + UUID.randomUUID(),
+ UUID.randomUUID().toString()));
+
+ Meter metric = CassandraMetricsRegistry.Metrics.meter(dummy);
+ LinkedList<MetricName> verify = new LinkedList<>(aliases);
+ verify.addFirst(first);
+ MetricRegistryListener listener;
+ CassandraMetricsRegistry.Metrics.addListener(listener = new MetricRegistryListener.Base()
+ {
+ @Override
+ public void onMeterAdded(String name, Meter meter)
+ {
+ if (dummy.equals(name))
+ return;
+
+ assertEquals(verify.removeFirst().getMetricName(), name);
+ }
+ });
+ CassandraMetricsRegistry.Metrics.register(first, metric, aliases.toArray(new MetricName[size]));
+ CassandraMetricsRegistry.Metrics.removeListener(listener);
+
+ List<String> all = CassandraMetricsRegistry.Metrics.getMetrics().keySet().
+ stream()
+ .filter(m -> m.contains("FirstTestMetricAliasesOrder"))
+ .collect(Collectors.toList());
+
+ assertNotNull(all);
+ assertEquals(size + 1, all.size());
+
+ CassandraMetricsRegistry.Metrics.remove(first.getMetricName());
+ Map<String, Metric> metrics = CassandraMetricsRegistry.Metrics.getMetrics();
+ assertEquals(1, metrics.size());
+ assertEquals(dummy, metrics.keySet().iterator().next());
+
+ CassandraMetricsRegistry.Metrics.remove(dummy);
+ assertTrue(CassandraMetricsRegistry.Metrics.getMetrics().isEmpty());
+ }
+
+ @Test
+ public void testMetricAliasesRegistration()
+ {
+ CassandraMetricsRegistry registry = CassandraMetricsRegistry.Metrics;
+ MetricNameFactory factory = new DefaultNameFactory("Table", "MetricsTestScope");
+ MetricNameFactory aliasFactory = new DefaultNameFactory("Table", "MetricsTestAliasScope");
+
+ MetricName meter = factory.createMetricName("TestMeter");
+ MetricName meterAlias = aliasFactory.createMetricName("TestMeterAlias");
+ registry.meter(meter, meterAlias);
+
+ MetricName histogram = factory.createMetricName("TestHistogram");
+ MetricName histogramAlias = aliasFactory.createMetricName("TestHistogramAlias");
+ registry.histogram(histogram, histogramAlias, false);
+
+ MetricName counter = factory.createMetricName("TestCounter");
+ MetricName counterAlias = aliasFactory.createMetricName("TestCounterAlias");
+ registry.counter(counter, counterAlias);
+
+ MetricName timer = factory.createMetricName("TestTimer");
+ MetricName timerAlias = aliasFactory.createMetricName("TestTimerAlias");
+ registry.timer(timer, timerAlias);
+
+ Set<String> aliases = new HashSet<>(Arrays.asList(meter.getMetricName(), meterAlias.getMetricName(),
+ histogram.getMetricName(), histogramAlias.getMetricName(),
+ counter.getMetricName(), counterAlias.getMetricName(),
+ timer.getMetricName(), timerAlias.getMetricName()));
+
+ assertTrue(registry.getMetrics().keySet().containsAll(aliases));
+ assertTrue(registry.getNames().containsAll(aliases));
+ }
+
private boolean inRange(long anchor, long input, double range)
{
return input / ((double) anchor) < range;
diff --git a/test/unit/org/apache/cassandra/metrics/ClientRequestMetricsTest.java b/test/unit/org/apache/cassandra/metrics/ClientRequestMetricsTest.java
index b4e9b085d2..88060ffd27 100644
--- a/test/unit/org/apache/cassandra/metrics/ClientRequestMetricsTest.java
+++ b/test/unit/org/apache/cassandra/metrics/ClientRequestMetricsTest.java
@@ -36,11 +36,12 @@ import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.service.EmbeddedCassandraService;
import org.apache.cassandra.service.reads.range.RangeCommandIterator;
+import static com.datastax.driver.core.Cluster.builder;
+import static org.apache.cassandra.cql3.CQLTester.assertRowsContains;
+import static org.apache.cassandra.cql3.CQLTester.row;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
-import static com.datastax.driver.core.Cluster.builder;
-
public class ClientRequestMetricsTest
{
private static Cluster cluster;
@@ -99,6 +100,8 @@ public class ClientRequestMetricsTest
assertEquals(0, readMetricsContainer.compareLocalRequest());
assertEquals(0, readMetricsContainer.compareRemoteRequest());
+
+ assertMetricsVirtualTable(writeMetricsContainer, readMetricsContainer);
}
@Test
@@ -114,6 +117,8 @@ public class ClientRequestMetricsTest
assertEquals(1, writeMetricsContainer.compareLocalRequest());
assertEquals(0, writeMetricsContainer.compareRemoteRequest());
+
+ assertMetricsVirtualTable(writeMetricsContainer, readMetricsContainer);
}
@Test
@@ -129,6 +134,8 @@ public class ClientRequestMetricsTest
assertEquals(10, writeMetricsContainer.compareLocalRequest());
assertEquals(0, writeMetricsContainer.compareRemoteRequest());
+
+ assertMetricsVirtualTable(writeMetricsContainer, readMetricsContainer);
}
@Test
@@ -146,6 +153,8 @@ public class ClientRequestMetricsTest
assertEquals(0, writeMetricsContainer.compareLocalRequest());
assertEquals(0, writeMetricsContainer.compareRemoteRequest());
+
+ assertMetricsVirtualTable(writeMetricsContainer, readMetricsContainer);
}
@Test
@@ -163,6 +172,8 @@ public class ClientRequestMetricsTest
assertEquals(0, writeMetricsContainer.compareLocalRequest());
assertEquals(0, writeMetricsContainer.compareRemoteRequest());
+
+ assertMetricsVirtualTable(writeMetricsContainer, readMetricsContainer);
}
@Test
@@ -178,6 +189,19 @@ public class ClientRequestMetricsTest
assertThat(RangeCommandIterator.rangeMetrics.latency.getCount()).isEqualTo(latencyCount + 1);
}
+ private void assertMetricsVirtualTable(ClientRequestMetricsContainer writeMetricsContainer, ClientRequestMetricsContainer readMetricsContainer)
+ {
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.client_request_group"),
+ row("org.apache.cassandra.metrics.ClientRequest.LocalRequests.Read", "Read", "meter",
+ String.valueOf(readMetricsContainer.getMetrics().localRequests.getCount())),
+ row("org.apache.cassandra.metrics.ClientRequest.LocalRequests.Write", "Write", "meter",
+ String.valueOf(writeMetricsContainer.getMetrics().localRequests.getCount())),
+ row("org.apache.cassandra.metrics.ClientRequest.RemoteRequests.Read", "Read", "meter",
+ String.valueOf(readMetricsContainer.getMetrics().remoteRequests.getCount())),
+ row("org.apache.cassandra.metrics.ClientRequest.RemoteRequests.Write", "Write", "meter",
+ String.valueOf(writeMetricsContainer.getMetrics().remoteRequests.getCount())));
+ }
+
private void clearHistogram(Histogram histogram)
{
((ClearableHistogram) histogram).clear();
@@ -206,6 +230,11 @@ public class ClientRequestMetricsTest
{
return metrics.remoteRequests.getCount() - remoteRequests;
}
+
+ public ClientRequestMetrics getMetrics()
+ {
+ return metrics;
+ }
}
private void executeWrite(int id, int ord, String val)
diff --git a/test/unit/org/apache/cassandra/metrics/ClientRequestRowAndColumnMetricsTest.java b/test/unit/org/apache/cassandra/metrics/ClientRequestRowAndColumnMetricsTest.java
index 80a3d3e0ce..b94ed8db48 100644
--- a/test/unit/org/apache/cassandra/metrics/ClientRequestRowAndColumnMetricsTest.java
+++ b/test/unit/org/apache/cassandra/metrics/ClientRequestRowAndColumnMetricsTest.java
@@ -38,9 +38,9 @@ import org.apache.cassandra.transport.SimpleClient;
import org.apache.cassandra.transport.messages.BatchMessage;
import org.apache.cassandra.transport.messages.QueryMessage;
-import static org.junit.Assert.assertEquals;
-
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.METRIC_SCOPE_UNDEFINED;
import static org.apache.cassandra.transport.ProtocolVersion.CURRENT;
+import static org.junit.Assert.assertEquals;
public class ClientRequestRowAndColumnMetricsTest extends CQLTester
{
@@ -48,6 +48,7 @@ public class ClientRequestRowAndColumnMetricsTest extends CQLTester
public static void setup()
{
requireNetwork();
+ addMetricsKeyspace();
}
@Before
@@ -73,6 +74,9 @@ public class ClientRequestRowAndColumnMetricsTest extends CQLTester
assertEquals(2, ClientRequestSizeMetrics.totalRowsRead.getCount());
// The partition key is provided by the client in the request, so we don't consider those columns as read.
assertEquals(4, ClientRequestSizeMetrics.totalColumnsRead.getCount());
+ assertRowsContains(executeNet("SELECT * FROM system_metrics.client_request_size_group"),
+ row("org.apache.cassandra.metrics.ClientRequestSize.RowsRead", METRIC_SCOPE_UNDEFINED, "counter", "2"),
+ row("org.apache.cassandra.metrics.ClientRequestSize.ColumnsRead", METRIC_SCOPE_UNDEFINED, "counter", "4"));
}
@Test
diff --git a/test/unit/org/apache/cassandra/metrics/JmxVirtualTableMetricsTest.java b/test/unit/org/apache/cassandra/metrics/JmxVirtualTableMetricsTest.java
new file mode 100644
index 0000000000..6f8c8b9822
--- /dev/null
+++ b/test/unit/org/apache/cassandra/metrics/JmxVirtualTableMetricsTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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.metrics;
+
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import javax.management.JMX;
+import javax.management.ObjectName;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistry;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.db.virtual.CollectionVirtualTableAdapter;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.cassandra.metrics.CassandraMetricsRegistry.METRICS_GROUP_POSTFIX;
+import static org.apache.cassandra.schema.SchemaConstants.VIRTUAL_METRICS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Test compares JMX metrics to virtual table metrics values, basically all metric values must be equal and
+ * have the same representation in both places.
+ */
+public class JmxVirtualTableMetricsTest extends CQLTester
+{
+ private final Map<MetricType, Metric> metricToNameMap = new EnumMap<>(MetricType.class);
+ private final AtomicInteger gaugeValue = new AtomicInteger(123);
+
+ @BeforeClass
+ public static void setup() throws Exception
+ {
+ startJMXServer();
+ createMBeanServerConnection();
+ addMetricsKeyspace();
+ }
+
+ @Before
+ public void beforeTest()
+ {
+ metricToNameMap.clear();
+ MetricRegistry registry = new MetricRegistry();
+
+ metricToNameMap.put(MetricType.METER, registry.meter("meter"));
+ metricToNameMap.put(MetricType.COUNTER, registry.counter("counter"));
+ metricToNameMap.put(MetricType.HISTOGRAM, registry.histogram("histogram"));
+ metricToNameMap.put(MetricType.TIMER, registry.timer("timer"));
+ metricToNameMap.put(MetricType.GAUGE, registry.gauge("gauge", () -> gaugeValue::get));
+
+ CassandraMetricsRegistry.metricGroups.forEach(group -> {
+ MetricNameFactory factory = new DefaultNameFactory(group, "jmx.virtual");
+ CassandraMetricsRegistry.Metrics.register(factory.createMetricName(MetricType.METER.metricName),
+ metricToNameMap.get(MetricType.METER));
+ CassandraMetricsRegistry.Metrics.register(factory.createMetricName(MetricType.COUNTER.metricName),
+ metricToNameMap.get(MetricType.COUNTER));
+ CassandraMetricsRegistry.Metrics.register(factory.createMetricName(MetricType.HISTOGRAM.metricName),
+ metricToNameMap.get(MetricType.HISTOGRAM));
+ CassandraMetricsRegistry.Metrics.register(factory.createMetricName(MetricType.TIMER.metricName),
+ metricToNameMap.get(MetricType.TIMER));
+ CassandraMetricsRegistry.Metrics.register(factory.createMetricName(MetricType.GAUGE.metricName),
+ metricToNameMap.get(MetricType.GAUGE));
+ });
+ }
+
+ @Test
+ public void testJmxEqualVirtualTableByMetricGroup() throws Exception
+ {
+ Map<String, List<ObjectName>> mbeanByMetricGroup = jmxConnection.queryNames(null, null)
+ .stream()
+ .filter(this::isLocalMetric)
+ .collect(Collectors.groupingBy(
+ on -> requireNonNull(
+ on.getKeyPropertyList().get("type"))));
+
+ for (Map.Entry<String, List<ObjectName>> e : mbeanByMetricGroup.entrySet())
+ {
+ assertRowsContains(executeNet(String.format("SELECT * FROM %s.%s", VIRTUAL_METRICS,
+ METRICS_GROUP_POSTFIX.apply(
+ CollectionVirtualTableAdapter.virtualTableNameStyle(
+ e.getKey())))),
+ e.getValue().stream().map(this::makeMetricRow).collect(Collectors.toList()));
+ }
+ }
+
+ @Test
+ public void testJmxEqualVirtualTableByMetricType() throws Exception
+ {
+ Map<MetricType, List<ObjectName>> mbeanByMetricGroup = jmxConnection.queryNames(null, null)
+ .stream()
+ .filter(this::isLocalMetric)
+ .collect(Collectors.groupingBy(
+ on -> MetricType.find(
+ on.getKeyPropertyList().get("name"))
+ .orElseThrow()));
+
+ for (Map.Entry<MetricType, List<ObjectName>> e : mbeanByMetricGroup.entrySet())
+ {
+ switch (e.getKey())
+ {
+ case METER:
+ assertRowsContains(executeNet(String.format("SELECT * FROM %s.type_meter", VIRTUAL_METRICS)),
+ e.getValue().stream().map(this::makeMeterRow).collect(Collectors.toList()));
+ break;
+ case COUNTER:
+ assertRowsContains(executeNet(String.format("SELECT * FROM %s.type_counter", VIRTUAL_METRICS)),
+ e.getValue().stream().map(this::makeCounterRow).collect(Collectors.toList()));
+ break;
+ case HISTOGRAM:
+ assertRowsContains(executeNet(String.format("SELECT * FROM %s.type_histogram", VIRTUAL_METRICS)),
+ e.getValue().stream().map(this::makeHistogramRow).collect(Collectors.toList()));
+ break;
+ case TIMER:
+ assertRowsContains(executeNet(String.format("SELECT * FROM %s.type_timer", VIRTUAL_METRICS)),
+ e.getValue().stream().map(this::makeTimerRow).collect(Collectors.toList()));
+ break;
+ case GAUGE:
+ assertRowsContains(executeNet(String.format("SELECT * FROM %s.type_gauge", VIRTUAL_METRICS)),
+ e.getValue().stream().map(this::makeGaugeRow).collect(Collectors.toList()));
+ break;
+ }
+ }
+ }
+
+ @Test
+ public void testAliasesWithJmxVirtualTables() throws Exception
+ {
+ CassandraMetricsRegistry registry = CassandraMetricsRegistry.Metrics;
+ MetricNameFactory factory = new DefaultNameFactory("Table", "AliasTestScopeFirst");
+ MetricNameFactory aliasFactory = new DefaultNameFactory("Index", "AliasTestScopeSecond");
+
+ CassandraMetricsRegistry.MetricName meter = factory.createMetricName("TestMeter");
+ CassandraMetricsRegistry.MetricName meterAlias = aliasFactory.createMetricName("TestMeterAlias");
+ registry.meter(meter, meterAlias);
+
+ assertRowsContains(executeNet(String.format("SELECT * FROM %s.type_meter", VIRTUAL_METRICS)),
+ row(meter.getMetricName(), 0L, 0.0, 0.0, 0.0, 0.0, "AliasTestScopeFirst"),
+ row(meterAlias.getMetricName(), 0L, 0.0, 0.0, 0.0, 0.0, "AliasTestScopeSecond"));
+
+ CassandraMetricsRegistry.JmxMeterMBean bean = JMX.newMBeanProxy(jmxConnection,
+ meter.getMBeanName(),
+ CassandraMetricsRegistry.JmxMeterMBean.class);
+
+ CassandraMetricsRegistry.JmxMeterMBean beanAlias = JMX.newMBeanProxy(jmxConnection,
+ meterAlias.getMBeanName(),
+ CassandraMetricsRegistry.JmxMeterMBean.class);
+
+ assertNotNull(bean);
+ assertNotNull(beanAlias);
+ assertEquals(bean.getCount(), beanAlias.getCount());
+ assertEquals(bean.getFifteenMinuteRate(), beanAlias.getFifteenMinuteRate(), 0.0);
+ assertEquals(bean.getFiveMinuteRate(), beanAlias.getFiveMinuteRate(), 0.0);
+ assertEquals(bean.getMeanRate(), beanAlias.getMeanRate(), 0.0);
+ assertEquals(bean.getOneMinuteRate(), beanAlias.getOneMinuteRate(), 0.0);
+ }
+
+ private Object[] makeMetricRow(ObjectName objectName)
+ {
+ MetricType type = MetricType.find(objectName.getKeyPropertyList().get("name")).orElseThrow();
+ String jmxValue;
+ switch (type)
+ {
+ case METER:
+ jmxValue = String.valueOf(JMX.newMBeanProxy(jmxConnection,
+ objectName,
+ CassandraMetricsRegistry.JmxMeterMBean.class).getCount());
+ break;
+ case COUNTER:
+ jmxValue = String.valueOf(JMX.newMBeanProxy(jmxConnection,
+ objectName,
+ CassandraMetricsRegistry.JmxCounterMBean.class).getCount());
+ break;
+ case HISTOGRAM:
+ jmxValue = String.valueOf(JMX.newMBeanProxy(jmxConnection,
+ objectName,
+ CassandraMetricsRegistry.JmxHistogramMBean.class).get50thPercentile());
+ break;
+ case TIMER:
+ jmxValue = String.valueOf(JMX.newMBeanProxy(jmxConnection,
+ objectName,
+ CassandraMetricsRegistry.JmxTimerMBean.class).getCount());
+ break;
+ case GAUGE:
+ jmxValue = String.valueOf(JMX.newMBeanProxy(jmxConnection,
+ objectName,
+ CassandraMetricsRegistry.JmxGaugeMBean.class).getValue());
+ break;
+ default:
+ throw new RuntimeException("Unknown metric type: " + objectName.getKeyPropertyList().get("name"));
+ }
+ return CQLTester.row(getFullMetricName(objectName),
+ requireNonNull(objectName.getKeyPropertyList().get("scope")),
+ MetricType.find(objectName.getKeyPropertyList().get("name"))
+ .map(MetricType::name)
+ .map(String::toLowerCase)
+ .orElseThrow(),
+ jmxValue);
+ }
+
+ private Object[] makeMeterRow(ObjectName objectName)
+ {
+ assertEquals(MetricType.METER.metricName, objectName.getKeyPropertyList().get("name"));
+ CassandraMetricsRegistry.JmxMeterMBean bean = JMX.newMBeanProxy(jmxConnection,
+ objectName,
+ CassandraMetricsRegistry.JmxMeterMBean.class);
+
+ return CQLTester.row(getFullMetricName(objectName),
+ bean.getCount(),
+ bean.getFifteenMinuteRate(),
+ bean.getFiveMinuteRate(),
+ bean.getMeanRate(),
+ bean.getOneMinuteRate(),
+ requireNonNull(objectName.getKeyPropertyList().get("scope")));
+ }
+
+ private Object[] makeCounterRow(ObjectName objectName)
+ {
+ assertEquals(MetricType.COUNTER.metricName, objectName.getKeyPropertyList().get("name"));
+ CassandraMetricsRegistry.JmxCounterMBean bean = JMX.newMBeanProxy(jmxConnection,
+ objectName,
+ CassandraMetricsRegistry.JmxCounterMBean.class);
+
+ return CQLTester.row(getFullMetricName(objectName),
+ requireNonNull(objectName.getKeyPropertyList().get("scope")),
+ bean.getCount());
+ }
+
+ private Object[] makeHistogramRow(ObjectName objectName)
+ {
+ assertEquals(MetricType.HISTOGRAM.metricName, objectName.getKeyPropertyList().get("name"));
+ CassandraMetricsRegistry.JmxHistogramMBean bean = JMX.newMBeanProxy(jmxConnection,
+ objectName,
+ CassandraMetricsRegistry.JmxHistogramMBean.class);
+
+ return CQLTester.row(getFullMetricName(objectName),
+ bean.getMax(),
+ bean.getMean(),
+ bean.getMin(),
+ bean.get75thPercentile(),
+ bean.get95thPercentile(),
+ bean.get98thPercentile(),
+ bean.get999thPercentile(),
+ bean.get99thPercentile(),
+ requireNonNull(objectName.getKeyPropertyList().get("scope")));
+ }
+
+ private Object[] makeTimerRow(ObjectName objectName)
+ {
+ assertEquals(MetricType.TIMER.metricName, objectName.getKeyPropertyList().get("name"));
+ CassandraMetricsRegistry.JmxTimerMBean bean = JMX.newMBeanProxy(jmxConnection,
+ objectName,
+ CassandraMetricsRegistry.JmxTimerMBean.class);
+
+ return CQLTester.row(getFullMetricName(objectName),
+ bean.getCount(),
+ bean.getFifteenMinuteRate(),
+ bean.getFiveMinuteRate(),
+ bean.getMeanRate(),
+ bean.getOneMinuteRate(),
+ requireNonNull(objectName.getKeyPropertyList().get("scope")));
+ }
+
+ private Object[] makeGaugeRow(ObjectName objectName)
+ {
+ assertEquals(MetricType.GAUGE.metricName, objectName.getKeyPropertyList().get("name"));
+ String jmxValue = String.valueOf(JMX.newMBeanProxy(jmxConnection,
+ objectName,
+ CassandraMetricsRegistry.JmxGaugeMBean.class).getValue());
+
+ return CQLTester.row(getFullMetricName(objectName),
+ requireNonNull(objectName.getKeyPropertyList().get("scope")),
+ jmxValue);
+ }
+
+ private boolean isLocalMetric(ObjectName mBean)
+ {
+ MetricType type = MetricType.find(mBean.getKeyPropertyList().get("name")).orElse(null);
+ return mBean.toString().startsWith(DefaultNameFactory.GROUP_NAME) && metricToNameMap.containsKey(type);
+ }
+
+ private static String getFullMetricName(ObjectName objectName)
+ {
+ Map<String, String> props = objectName.getKeyPropertyList();
+ return new CassandraMetricsRegistry.MetricName(DefaultNameFactory.GROUP_NAME, requireNonNull(props.get("type")),
+ requireNonNull(props.get("name")),
+ requireNonNull(props.get("scope"))).getMetricName();
+ }
+
+ private enum MetricType
+ {
+ METER("MeterTestMetric"),
+ COUNTER("CounterTestMetric"),
+ HISTOGRAM("HistogramTestMetric"),
+ TIMER("TimerTestMetric"),
+ GAUGE("GaugeTestMetric");
+
+ private final String metricName;
+
+ MetricType(String metricName)
+ {
+ this.metricName = metricName;
+ }
+
+ public static Optional<MetricType> find(String metricName)
+ {
+ for (MetricType type : values())
+ if (type.metricName.equals(metricName))
+ return Optional.of(type);
+
+ return Optional.empty();
+ }
+ }
+}
diff --git a/test/unit/org/apache/cassandra/metrics/KeyspaceMetricsTest.java b/test/unit/org/apache/cassandra/metrics/KeyspaceMetricsTest.java
index 7c00da5819..f30b9307c1 100644
--- a/test/unit/org/apache/cassandra/metrics/KeyspaceMetricsTest.java
+++ b/test/unit/org/apache/cassandra/metrics/KeyspaceMetricsTest.java
@@ -27,7 +27,10 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
+import com.codahale.metrics.Metric;
import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import org.apache.cassandra.ServerTestUtils;
import org.apache.cassandra.config.DatabaseDescriptor;
@@ -35,6 +38,7 @@ import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.service.EmbeddedCassandraService;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class KeyspaceMetricsTest
@@ -71,6 +75,36 @@ public class KeyspaceMetricsTest
assertEquals(metrics.get().collect(Collectors.joining(",")), 0, metrics.get().count());
}
+ @Test
+ public void testKeyspaceVirtualTable()
+ {
+ String keyspace = "uniquemetricskeyspace1";
+ session.execute(String.format(
+ "CREATE KEYSPACE %s WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };",
+ keyspace));
+
+ assertTrue(CassandraMetricsRegistry.Metrics.getNames().stream().anyMatch(m -> m.endsWith(keyspace)));
+ ResultSet resultSet = session.execute(
+ "SELECT * FROM system_metrics.keyspace_group WHERE scope = '" + keyspace + "';");
+
+ int count = 0;
+ for (Row row : resultSet)
+ {
+ String metricName = row.getString("name");
+ if (!metricName.endsWith(keyspace))
+ continue;
+
+ Metric metric = CassandraMetricsRegistry.Metrics.getMetrics().get(metricName);
+ assertEquals(CassandraMetricsRegistry.getValueAsString(metric), row.getString("value"));
+ count++;
+ }
+
+ assertTrue("Keyspace " + keyspace + " metrics was not found", count > 0);
+
+ session.execute(String.format("DROP KEYSPACE %s;", keyspace));
+ assertFalse(CassandraMetricsRegistry.Metrics.getNames().stream().anyMatch(m -> m.endsWith(keyspace)));
+ }
+
@AfterClass
public static void tearDown()
{
diff --git a/test/unit/org/apache/cassandra/metrics/LatencyMetricsTest.java b/test/unit/org/apache/cassandra/metrics/LatencyMetricsTest.java
index 3a4dc8af98..20fc79c649 100644
--- a/test/unit/org/apache/cassandra/metrics/LatencyMetricsTest.java
+++ b/test/unit/org/apache/cassandra/metrics/LatencyMetricsTest.java
@@ -29,13 +29,13 @@ public class LatencyMetricsTest
{
private final MetricNameFactory factory = new TestMetricsNameFactory();
- private class TestMetricsNameFactory implements MetricNameFactory
+ private static class TestMetricsNameFactory implements MetricNameFactory
{
@Override
public CassandraMetricsRegistry.MetricName createMetricName(String metricName)
{
- return new CassandraMetricsRegistry.MetricName(TestMetricsNameFactory.class, metricName);
+ return new CassandraMetricsRegistry.MetricName(DefaultNameFactory.GROUP_NAME, ClientRequestMetrics.TYPE_NAME, metricName);
}
}
@@ -45,7 +45,7 @@ public class LatencyMetricsTest
@Test
public void testGetRecentLatency()
{
- final LatencyMetrics l = new LatencyMetrics("test", "test");
+ final LatencyMetrics l = new LatencyMetrics(ClientRequestMetrics.TYPE_NAME, "test");
Runnable r = () -> {
for (int i = 0; i < 10000; i++)
{
@@ -67,7 +67,7 @@ public class LatencyMetricsTest
@Test
public void testReadMerging()
{
- final LatencyMetrics parent = new LatencyMetrics("testMerge", "testMerge");
+ final LatencyMetrics parent = new LatencyMetrics(ClientRequestMetrics.TYPE_NAME, "testMerge");
final LatencyMetrics child = new LatencyMetrics(factory, "testChild", parent);
for (int i = 0; i < 100; i++)
@@ -86,7 +86,7 @@ public class LatencyMetricsTest
@Test
public void testRelease()
{
- final LatencyMetrics parent = new LatencyMetrics("testRelease", "testRelease");
+ final LatencyMetrics parent = new LatencyMetrics(ClientRequestMetrics.TYPE_NAME, "testRelease");
final LatencyMetrics child = new LatencyMetrics(factory, "testChildRelease", parent);
for (int i = 0; i < 100; i++)
diff --git a/test/unit/org/apache/cassandra/metrics/TableMetricsTest.java b/test/unit/org/apache/cassandra/metrics/TableMetricsTest.java
index bbcced43db..a0691d8ccd 100644
--- a/test/unit/org/apache/cassandra/metrics/TableMetricsTest.java
+++ b/test/unit/org/apache/cassandra/metrics/TableMetricsTest.java
@@ -40,6 +40,8 @@ import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.service.EmbeddedCassandraService;
import org.apache.cassandra.service.StorageService;
+import static org.apache.cassandra.cql3.CQLTester.assertRowsContains;
+import static org.apache.cassandra.cql3.CQLTester.row;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -140,6 +142,12 @@ public class TableMetricsTest
assertEquals(10, cfs.metric.coordinatorWriteLatency.getCount());
assertGreaterThan(cfs.metric.coordinatorWriteLatency.getMeanRate(), 0);
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.table_group"),
+ row("org.apache.cassandra.metrics.Table.CoordinatorWriteLatency.junit.tablemetricstest", "junit.tablemetricstest", "timer",
+ String.valueOf(cfs.metric.coordinatorWriteLatency.getCount())));
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.column_family_group"),
+ row("org.apache.cassandra.metrics.ColumnFamily.CoordinatorWriteLatency.junit.tablemetricstest", "junit.tablemetricstest", "timer",
+ String.valueOf(cfs.metric.coordinatorWriteLatency.getCount())));
}
@Test
@@ -156,6 +164,13 @@ public class TableMetricsTest
StorageService.instance.forceKeyspaceFlush(KEYSPACE);
assertGreaterThan(cfs.metric.maxSSTableSize.getValue().doubleValue(), 0);
+
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.table_group"),
+ row("org.apache.cassandra.metrics.Table.MaxSSTableSize.junit.tablemetricstest", "junit.tablemetricstest", "gauge",
+ String.valueOf(cfs.metric.maxSSTableSize.getValue())));
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.column_family_group"),
+ row("org.apache.cassandra.metrics.ColumnFamily.MaxSSTableSize.junit.tablemetricstest", "junit.tablemetricstest", "gauge",
+ String.valueOf(cfs.metric.maxSSTableSize.getValue())));
}
@Test
@@ -171,6 +186,12 @@ public class TableMetricsTest
StorageService.instance.forceKeyspaceFlush(KEYSPACE);
assertGreaterThan(cfs.metric.maxSSTableDuration.getValue().doubleValue(), 0);
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.table_group"),
+ row("org.apache.cassandra.metrics.Table.MaxSSTableDuration.junit.tablemetricstesttwcs", "junit.tablemetricstesttwcs", "gauge",
+ String.valueOf(cfs.metric.maxSSTableDuration.getValue())));
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.column_family_group"),
+ row("org.apache.cassandra.metrics.ColumnFamily.MaxSSTableDuration.junit.tablemetricstesttwcs", "junit.tablemetricstesttwcs", "gauge",
+ String.valueOf(cfs.metric.maxSSTableDuration.getValue())));
}
@Test
diff --git a/test/unit/org/apache/cassandra/metrics/TrieMemtableMetricsTest.java b/test/unit/org/apache/cassandra/metrics/TrieMemtableMetricsTest.java
index 24fdc8b475..f8f59d07b0 100644
--- a/test/unit/org/apache/cassandra/metrics/TrieMemtableMetricsTest.java
+++ b/test/unit/org/apache/cassandra/metrics/TrieMemtableMetricsTest.java
@@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;
import org.apache.cassandra.SchemaLoader;
+import org.apache.cassandra.ServerTestUtils;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.OverrideConfigurationLoader;
import org.apache.cassandra.db.ColumnFamilyStore;
@@ -47,7 +48,9 @@ import org.jboss.byteman.contrib.bmunit.BMRules;
import org.jboss.byteman.contrib.bmunit.BMUnitRunner;
import static org.apache.cassandra.config.CassandraRelevantProperties.MEMTABLE_SHARD_COUNT;
-import static org.hamcrest.Matchers.*;
+import static org.apache.cassandra.cql3.CQLTester.assertRowsContains;
+import static org.apache.cassandra.cql3.CQLTester.row;
+import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -59,6 +62,8 @@ public class TrieMemtableMetricsTest extends SchemaLoader
private static final Logger logger = LoggerFactory.getLogger(TrieMemtableMetricsTest.class);
private static Session session;
+ private static Cluster cluster;
+ private static EmbeddedCassandraService cassandra;
private static final String KEYSPACE = "triememtable";
private static final String TABLE = "metricstest";
@@ -78,10 +83,9 @@ public class TrieMemtableMetricsTest extends SchemaLoader
});
MEMTABLE_SHARD_COUNT.setInt(NUM_SHARDS);
- EmbeddedCassandraService cassandra = new EmbeddedCassandraService();
- cassandra.start();
+ cassandra = ServerTestUtils.startEmbeddedCassandraService();
- Cluster cluster = Cluster.builder().addContactPoint("127.0.0.1").withPort(DatabaseDescriptor.getNativeTransportPort()).build();
+ cluster = Cluster.builder().addContactPoint("127.0.0.1").withPort(DatabaseDescriptor.getNativeTransportPort()).build();
session = cluster.connect();
session.execute(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", KEYSPACE));
@@ -114,6 +118,11 @@ public class TrieMemtableMetricsTest extends SchemaLoader
long allPuts = metrics.contendedPuts.getCount() + metrics.uncontendedPuts.getCount();
assertEquals(10, allPuts);
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.trie_memtable_group"),
+ row("org.apache.cassandra.metrics.TrieMemtable.Contended memtable puts.triememtable.metricstest",
+ "triememtable.metricstest", "counter", String.valueOf(metrics.contendedPuts.getCount())),
+ row("org.apache.cassandra.metrics.TrieMemtable.Uncontended memtable puts.triememtable.metricstest",
+ "triememtable.metricstest", "counter", String.valueOf(metrics.uncontendedPuts.getCount())));
}
@Test
@@ -155,6 +164,9 @@ public class TrieMemtableMetricsTest extends SchemaLoader
assertEquals(100, metrics.contendedPuts.getCount() + metrics.uncontendedPuts.getCount());
assertThat(metrics.contendedPuts.getCount(), greaterThan(0L));
assertThat(metrics.contentionTime.totalLatency.getCount(), greaterThan(0L));
+ assertRowsContains(cluster, session.execute("SELECT * FROM system_metrics.trie_memtable_group"),
+ row("org.apache.cassandra.metrics.TrieMemtable.Contention timeTotalLatency.triememtable.metricstest",
+ "triememtable.metricstest", "counter", String.valueOf(metrics.contentionTime.totalLatency.getCount())));
}
@Test
@@ -203,6 +215,9 @@ public class TrieMemtableMetricsTest extends SchemaLoader
@AfterClass
public static void teardown()
{
- session.close();
+ if (cluster != null)
+ cluster.close();
+ if (cassandra != null)
+ cassandra.stop();
}
}
diff --git a/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java b/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java
index 17b59400e7..65dc279ec5 100644
--- a/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java
+++ b/test/unit/org/apache/cassandra/utils/FBUtilitiesTest.java
@@ -251,7 +251,8 @@ public class FBUtilitiesTest
AssertionError error = null;
for (Pair<String, String> a : Arrays.asList(Pair.create("Testing", "testing"),
Pair.create("fooBarBaz", "foo_bar_baz"),
- Pair.create("foo_bar_baz", "foo_bar_baz")
+ Pair.create("foo_bar_baz", "foo_bar_baz"),
+ Pair.create("TCM", "tcm")
))
{
try
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org