expose gossip information in system_views.gossip_info virtual table

patch by Francisco Guerrero; reviewed by Stefan Miklosovic and Yifan Cai for CASSANDRA-17002

This commit adds a new virtual table that exposes the gossip information in tabular format.

The information is the same as the information presented through the `nodetool gossipinfo`
command, but the virtual table splits the version and value from `VersionedValue` into two
different columns. This is intented to help clients reading the vtable without the need of
parsing the version:value information (as it currently stands in gossipinfo).

The token value does not have a column. This is consistent with the gossipinfo output which
always renders ":<hidden>" for the Token value. Only the token_version column is available.
This commit is contained in:
Francisco Guerrero 2022-03-16 01:31:00 +01:00 committed by Stefan Miklosovic
parent 935bcf1e87
commit 61be4d8362
7 changed files with 394 additions and 5 deletions

View File

@ -1,4 +1,5 @@
4.1
* Expose gossip information in system_views.gossip_info virtual table (CASSANDRA-17002)
* Add guardrails for collection items and size (CASSANDRA-17153)
* Improve guardrails messages (CASSANDRA-17430)
* Remove all usages of junit.framework and ban them via Checkstyle (CASSANDRA-17316)

View File

@ -74,10 +74,12 @@ recent_hit_rate_per_second, recent_request_rate_per_second, request_count, and s
|coordinator_write_latency |Records counts, keyspace_name, table_name, max, median, and per_second for coordinator writes.
|cql_metrcs |Metrics specific to CQL prepared statement caching.
|cql_metrics |Metrics specific to CQL prepared statement caching.
|disk_usage |Records disk usage including disk_space, keyspace_name, and table_name, sorted by system keyspaces.
|gossip_info |Lists the gossip information for the cluster.
|internode_inbound |Lists information about the inbound internode messaging.
|internode_outbound |Information about the outbound internode messaging.
@ -377,6 +379,24 @@ SELECT total - progress AS remaining
FROM system_views.sstable_tasks;
....
=== Gossip Information Virtual Table
The `gossip_info` virtual table lists the Gossip information for the cluster. An example query is as follows:
....
cqlsh> select address, port, generation, heartbeat, load, dc, rack from system_views.gossip_info;
address | port | generation | heartbeat | load | dc | rack
-----------+------+------------+-----------+---------+-------------+-------
127.0.0.1 | 7000 | 1645575140 | 312 | 70542.0 | datacenter1 | rack1
127.0.0.2 | 7000 | 1645575135 | 318 | 70499.0 | datacenter1 | rack1
127.0.0.3 | 7000 | 1645575140 | 312 | 70504.0 | datacenter1 | rack1
127.0.0.4 | 7000 | 1645575141 | 311 | 70502.0 | datacenter1 | rack1
127.0.0.5 | 7000 | 1645575136 | 315 | 70500.0 | datacenter1 | rack1
(5 rows)
....
=== Other Virtual Tables
Some examples of using other virtual tables are as follows.

View File

@ -0,0 +1,174 @@
/*
* 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.EnumSet;
import java.util.Map;
import org.apache.cassandra.db.marshal.InetAddressType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.EndpointState;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.gms.VersionedValue;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.schema.TableMetadata;
import static org.apache.cassandra.gms.ApplicationState.TOKENS;
/**
* A {@link VirtualTable} that return the Gossip information in tabular format.
*/
final class GossipInfoTable extends AbstractVirtualTable
{
static final String TABLE_NAME = "gossip_info";
static final String TABLE_COMMENT = "lists the gossip information for the cluster";
static final String ADDRESS = "address";
static final String PORT = "port";
static final String HOSTNAME = "hostname";
static final String GENERATION = "generation";
static final String HEARTBEAT = "heartbeat";
static final ApplicationState[] STATES_FOR_VERSIONS = ApplicationState.values();
static final ApplicationState[] STATES_FOR_VALUES;
static
{
EnumSet<ApplicationState> applicationStates = EnumSet.allOf(ApplicationState.class);
// do not add a column for the ApplicationState.TOKENS value
applicationStates.remove(TOKENS);
STATES_FOR_VALUES = applicationStates.toArray(new ApplicationState[0]);
}
/**
* Construct a new {@link GossipInfoTable} for the given {@code keyspace}.
*
* @param keyspace the name of the keyspace
*/
GossipInfoTable(String keyspace)
{
super(buildTableMetadata(keyspace));
}
/**
* {@inheritDoc}
*/
@Override
public DataSet data()
{
SimpleDataSet result = new SimpleDataSet(metadata());
for (Map.Entry<InetAddressAndPort, EndpointState> entry : Gossiper.instance.endpointStateMap.entrySet())
{
InetAddressAndPort endpoint = entry.getKey();
EndpointState localState = entry.getValue();
SimpleDataSet dataSet = result.row(endpoint.getAddress(), endpoint.getPort())
.column(HOSTNAME, endpoint.getHostName())
.column(GENERATION, getGeneration(localState))
.column(HEARTBEAT, getHeartBeat(localState));
for (ApplicationState state : STATES_FOR_VALUES)
dataSet.column(state.name().toLowerCase(), getValue(localState, state));
for (ApplicationState state : STATES_FOR_VERSIONS)
dataSet.column(state.name().toLowerCase() + "_version", getVersion(localState, state));
}
return result;
}
/**
* Return the heartbeat generation of a given {@link EndpointState} or null if {@code localState} is null.
*
* @param localState a nullable endpoint state
* @return the heartbeat generation if available, null otherwise
*/
private Integer getGeneration(EndpointState localState)
{
return localState == null ? null : localState.getHeartBeatState().getGeneration();
}
/**
* Return the heartbeat version of a given {@link EndpointState} or null if {@code localState} is null.
*
* @param localState a nullable endpoint state
* @return the heartbeat version if available, null otherwise
*/
private Integer getHeartBeat(EndpointState localState)
{
return localState == null ? null : localState.getHeartBeatState().getHeartBeatVersion();
}
/**
* Returns the value from the {@link VersionedValue} of a given {@link ApplicationState key}, or null
* if {@code localState} is null or the {@link VersionedValue} does not exist in the {@link ApplicationState}.
*
* @param localState a nullable endpoint state
* @param key the key to the application state
* @return the value, or null if not available
*/
private String getValue(EndpointState localState, ApplicationState key)
{
VersionedValue value;
return localState == null || (value = localState.getApplicationState(key)) == null ? null : value.value;
}
/**
* Returns the version from the {@link VersionedValue} of a given {@link ApplicationState key}, or null
* if {@code localState} is null or the {@link VersionedValue} does not exist in the {@link ApplicationState}.
*
* @param localState a nullable endpoint state
* @param key the key to the application state
* @return the version, or null if not available
*/
private Integer getVersion(EndpointState localState, ApplicationState key)
{
VersionedValue value;
return localState == null || (value = localState.getApplicationState(key)) == null ? null : value.version;
}
/**
* Builds the {@link TableMetadata} to be provided to the superclass
*
* @param keyspace the name of the keyspace
* @return the TableMetadata class
*/
private static TableMetadata buildTableMetadata(String keyspace)
{
TableMetadata.Builder builder = TableMetadata.builder(keyspace, TABLE_NAME)
.comment(TABLE_COMMENT)
.kind(TableMetadata.Kind.VIRTUAL)
.partitioner(new LocalPartitioner(InetAddressType.instance))
.addPartitionKeyColumn(ADDRESS, InetAddressType.instance)
.addClusteringColumn(PORT, Int32Type.instance)
.addRegularColumn(HOSTNAME, UTF8Type.instance)
.addRegularColumn(GENERATION, Int32Type.instance)
.addRegularColumn(HEARTBEAT, Int32Type.instance);
for (ApplicationState state : STATES_FOR_VALUES)
builder.addRegularColumn(state.name().toLowerCase(), UTF8Type.instance);
for (ApplicationState state : STATES_FOR_VERSIONS)
builder.addRegularColumn(state.name().toLowerCase() + "_version", Int32Type.instance);
return builder.build();
}
}

View File

@ -46,6 +46,7 @@ public final class SystemViewsKeyspace extends VirtualKeyspace
.add(new CQLMetricsTable(VIRTUAL_VIEWS))
.add(new BatchMetricsTable(VIRTUAL_VIEWS))
.add(new StreamingVirtualTable(VIRTUAL_VIEWS))
.add(new GossipInfoTable(VIRTUAL_VIEWS))
.build());
}
}

View File

@ -159,8 +159,9 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean
@VisibleForTesting
final Set<InetAddressAndPort> seeds = new ConcurrentSkipListSet<>();
/* map where key is the endpoint and value is the state associated with the endpoint */
final ConcurrentMap<InetAddressAndPort, EndpointState> endpointStateMap = new ConcurrentHashMap<>();
/* map where key is the endpoint and value is the state associated with the endpoint.
* This is made public to be consumed by the GossipInfoTable virtual table */
public final ConcurrentMap<InetAddressAndPort, EndpointState> endpointStateMap = new ConcurrentHashMap<>();
/* map where key is endpoint and value is timestamp when this endpoint was removed from
* gossip. We will ignore any gossip regarding these endpoints for QUARANTINE_DELAY time

View File

@ -70,7 +70,7 @@ public class HeartBeatState
return version == EMPTY_VERSION;
}
int getGeneration()
public int getGeneration()
{
return generation;
}
@ -80,7 +80,7 @@ public class HeartBeatState
version = VersionGenerator.getNextVersion();
}
int getHeartBeatVersion()
public int getHeartBeatVersion()
{
return version;
}

View File

@ -0,0 +1,192 @@
/*
* 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.Map;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.EndpointState;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.locator.InetAddressAndPort;
import static org.assertj.core.api.Assertions.assertThat;
public class GossipInfoTableTest extends CQLTester
{
private static final String KS_NAME = "vts";
@SuppressWarnings("FieldCanBeLocal")
private GossipInfoTable table;
@BeforeClass
public static void setUpClass()
{
CQLTester.setUpClass();
}
@Before
public void config()
{
table = new GossipInfoTable(KS_NAME);
VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table)));
}
@Test
public void testSelectAllWhenGossipInfoIsEmpty() throws Throwable
{
assertEmpty(execute("SELECT * FROM vts.gossip_info"));
}
@SuppressWarnings("deprecation")
@Test
public void testSelectAllWithStateTransitions() throws Throwable
{
try
{
requireNetwork(); // triggers gossiper
UntypedResultSet resultSet = execute("SELECT * FROM vts.gossip_info");
assertThat(resultSet.size()).isEqualTo(1);
assertThat(Gossiper.instance.endpointStateMap.size()).isEqualTo(1);
Optional<Map.Entry<InetAddressAndPort, EndpointState>> entry = Gossiper.instance.endpointStateMap.entrySet()
.stream()
.findFirst();
assertThat(entry).isNotEmpty();
UntypedResultSet.Row row = resultSet.one();
assertThat(row.getColumns().size()).isEqualTo(62);
InetAddressAndPort endpoint = entry.get().getKey();
EndpointState localState = entry.get().getValue();
assertThat(endpoint).isNotNull();
assertThat(localState).isNotNull();
assertThat(row.getInetAddress("address")).isEqualTo(endpoint.getAddress());
assertThat(row.getInt("port")).isEqualTo(endpoint.getPort());
assertThat(row.getString("hostname")).isEqualTo(endpoint.getHostName());
assertThat(row.getInt("generation")).isEqualTo(localState.getHeartBeatState().getGeneration());
assertValue(row, "status", localState, ApplicationState.STATUS);
assertValue(row, "load", localState, ApplicationState.LOAD);
assertValue(row, "schema", localState, ApplicationState.SCHEMA);
assertValue(row, "dc", localState, ApplicationState.DC);
assertValue(row, "rack", localState, ApplicationState.RACK);
assertValue(row, "release_version", localState, ApplicationState.RELEASE_VERSION);
assertValue(row, "removal_coordinator", localState, ApplicationState.REMOVAL_COORDINATOR);
assertValue(row, "internal_ip", localState, ApplicationState.INTERNAL_IP);
assertValue(row, "rpc_address", localState, ApplicationState.RPC_ADDRESS);
assertValue(row, "severity", localState, ApplicationState.SEVERITY);
assertValue(row, "net_version", localState, ApplicationState.NET_VERSION);
assertValue(row, "host_id", localState, ApplicationState.HOST_ID);
assertValue(row, "rpc_ready", localState, ApplicationState.RPC_READY);
assertValue(row, "internal_address_and_port", localState, ApplicationState.INTERNAL_ADDRESS_AND_PORT);
assertValue(row, "native_address_and_port", localState, ApplicationState.NATIVE_ADDRESS_AND_PORT);
assertValue(row, "status_with_port", localState, ApplicationState.STATUS_WITH_PORT);
assertValue(row, "sstable_versions", localState, ApplicationState.SSTABLE_VERSIONS);
assertValue(row, "x_11_padding", localState, ApplicationState.X_11_PADDING);
assertValue(row, "x1", localState, ApplicationState.X1);
assertValue(row, "x2", localState, ApplicationState.X2);
assertValue(row, "x3", localState, ApplicationState.X3);
assertValue(row, "x4", localState, ApplicationState.X4);
assertValue(row, "x5", localState, ApplicationState.X5);
assertValue(row, "x6", localState, ApplicationState.X6);
assertValue(row, "x7", localState, ApplicationState.X7);
assertValue(row, "x8", localState, ApplicationState.X8);
assertValue(row, "x9", localState, ApplicationState.X9);
assertValue(row, "x10", localState, ApplicationState.X10);
assertVersion(row, "status_version", localState, ApplicationState.STATUS);
assertVersion(row, "load_version", localState, ApplicationState.LOAD);
assertVersion(row, "schema_version", localState, ApplicationState.SCHEMA);
assertVersion(row, "dc_version", localState, ApplicationState.DC);
assertVersion(row, "rack_version", localState, ApplicationState.RACK);
assertVersion(row, "release_version_version", localState, ApplicationState.RELEASE_VERSION);
assertVersion(row, "removal_coordinator_version", localState, ApplicationState.REMOVAL_COORDINATOR);
assertVersion(row, "internal_ip_version", localState, ApplicationState.INTERNAL_IP);
assertVersion(row, "rpc_address_version", localState, ApplicationState.RPC_ADDRESS);
assertVersion(row, "severity_version", localState, ApplicationState.SEVERITY);
assertVersion(row, "net_version_version", localState, ApplicationState.NET_VERSION);
assertVersion(row, "host_id_version", localState, ApplicationState.HOST_ID);
assertVersion(row, "tokens_version", localState, ApplicationState.TOKENS);
assertVersion(row, "rpc_ready_version", localState, ApplicationState.RPC_READY);
assertVersion(row, "internal_address_and_port_version", localState, ApplicationState.INTERNAL_ADDRESS_AND_PORT);
assertVersion(row, "native_address_and_port_version", localState, ApplicationState.NATIVE_ADDRESS_AND_PORT);
assertVersion(row, "status_with_port_version", localState, ApplicationState.STATUS_WITH_PORT);
assertVersion(row, "sstable_versions_version", localState, ApplicationState.SSTABLE_VERSIONS);
assertVersion(row, "x_11_padding", localState, ApplicationState.X_11_PADDING);
assertVersion(row, "x1", localState, ApplicationState.X1);
assertVersion(row, "x2", localState, ApplicationState.X2);
assertVersion(row, "x3", localState, ApplicationState.X3);
assertVersion(row, "x4", localState, ApplicationState.X4);
assertVersion(row, "x5", localState, ApplicationState.X5);
assertVersion(row, "x6", localState, ApplicationState.X6);
assertVersion(row, "x7", localState, ApplicationState.X7);
assertVersion(row, "x8", localState, ApplicationState.X8);
assertVersion(row, "x9", localState, ApplicationState.X9);
assertVersion(row, "x10", localState, ApplicationState.X10);
}
finally
{
// clean up the gossip states
Gossiper.instance.clearUnsafe();
}
}
private void assertValue(UntypedResultSet.Row row, String column, EndpointState localState, ApplicationState key)
{
if (row.has(column))
{
assertThat(localState.getApplicationState(key)).as("'%s' is expected to be not-null", key)
.isNotNull();
assertThat(row.getString(column)).as("'%s' is expected to match column '%s'", key, column)
.isEqualTo(localState.getApplicationState(key).value);
}
else
{
assertThat(localState.getApplicationState(key)).as("'%s' is expected to be null", key)
.isNull();
}
}
private void assertVersion(UntypedResultSet.Row row, String column, EndpointState localState, ApplicationState key)
{
if (row.has(column))
{
assertThat(localState.getApplicationState(key)).as("'%s' is expected to be not-null", key)
.isNotNull();
assertThat(row.getInt(column)).as("'%s' is expected to match column '%s'", key, column)
.isEqualTo(localState.getApplicationState(key).version);
}
else
{
assertThat(localState.getApplicationState(key)).as("'%s' is expected to be null", key)
.isNull();
}
}
}