Fix multi DC nodetool status output

Patch by Chris Lohfink; Reviewed by Brandon Williams for CASSANDRA-15305
This commit is contained in:
Chris Lohfink 2020-02-17 15:52:36 -06:00 committed by GitHub
commit d9798369dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 140 additions and 130 deletions

View File

@ -1,4 +1,5 @@
4.0-alpha4
* Fix multi DC nodetool status output (CASSANDRA-15305)
* Added documentation covering new Netty based internode messaging (CASSANDRA-15478)
* Add documentation of hints (CASSANDRA-15491)
* updateCoordinatorWriteLatencyTableMetric can produce misleading metrics (CASSANDRA-15569)

View File

@ -109,7 +109,7 @@ public class DescribeCluster extends NodeToolCmd
ArrayListMultimap<InetAddressAndPort, HostStatWithPort> hostToTokens = ArrayListMultimap.create();
for (HostStatWithPort stat : dc.getValue())
hostToTokens.put(stat.endpoint, stat);
hostToTokens.put(stat.endpointWithPort, stat);
int totalNodes = 0; // total number of nodes in a datacenter
int downNodes = 0; // number of down nodes in a datacenter

View File

@ -20,25 +20,28 @@ package org.apache.cassandra.tools.nodetool;
import org.apache.cassandra.locator.InetAddressAndPort;
public class HostStatWithPort
public class HostStatWithPort extends HostStat
{
public final InetAddressAndPort endpoint;
public final boolean resolveIp;
public final Float owns;
public final String token;
public final InetAddressAndPort endpointWithPort;
public HostStatWithPort(String token, InetAddressAndPort endpoint, boolean resolveIp, Float owns)
{
this.token = token;
this.endpoint = endpoint;
this.resolveIp = resolveIp;
this.owns = owns;
super(token, endpoint.address, resolveIp, owns);
this.endpointWithPort = endpoint;
}
public String ipOrDns()
{
return ipOrDns(true);
}
public String ipOrDns(boolean withPort)
{
if (!withPort)
return super.ipOrDns();
return resolveIp ?
endpoint.address.getHostName() + ":" + endpoint.port :
endpoint.toString();
endpointWithPort.address.getHostName() + ':' + endpointWithPort.port :
endpointWithPort.toString();
}
}

View File

@ -25,6 +25,7 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
@ -67,118 +68,72 @@ public class Status extends NodeToolCmd
epSnitchInfo = probe.getEndpointSnitchInfoProxy();
StringBuilder errors = new StringBuilder();
TableBuilder tableBuilder = new TableBuilder(" ");
TableBuilder.SharedTable sharedTable = new TableBuilder.SharedTable(" ");
if (printPort)
Map<String, Float> ownerships = null;
boolean hasEffectiveOwns = false;
try
{
Map<String, Float> ownerships = null;
boolean hasEffectiveOwns = false;
try
{
ownerships = probe.effectiveOwnershipWithPort(keyspace);
hasEffectiveOwns = true;
}
catch (IllegalStateException e)
{
ownerships = probe.getOwnershipWithPort();
errors.append("Note: ").append(e.getMessage()).append("%n");
}
catch (IllegalArgumentException ex)
{
System.out.printf("%nError: %s%n", ex.getMessage());
System.exit(1);
}
SortedMap<String, SetHostStatWithPort> dcs = NodeTool.getOwnershipByDcWithPort(probe, resolveIp, tokensToEndpoints, ownerships);
// More tokens than nodes (aka vnodes)?
if (dcs.size() < tokensToEndpoints.size())
isTokenPerNode = false;
// Datacenters
for (Map.Entry<String, SetHostStatWithPort> dc : dcs.entrySet())
{
String dcHeader = String.format("Datacenter: %s%n", dc.getKey());
System.out.print(dcHeader);
for (int i = 0; i < (dcHeader.length() - 1); i++) System.out.print('=');
System.out.println();
// Legend
System.out.println("Status=Up/Down");
System.out.println("|/ State=Normal/Leaving/Joining/Moving");
addNodesHeader(hasEffectiveOwns, tableBuilder);
ArrayListMultimap<InetAddressAndPort, HostStatWithPort> hostToTokens = ArrayListMultimap.create();
for (HostStatWithPort stat : dc.getValue())
hostToTokens.put(stat.endpoint, stat);
for (InetAddressAndPort endpoint : hostToTokens.keySet())
{
Float owns = ownerships.get(endpoint.toString());
List<HostStatWithPort> tokens = hostToTokens.get(endpoint);
addNodeWithPort(endpoint.toString(), owns, tokens, hasEffectiveOwns, tableBuilder);
}
}
tableBuilder.printTo(System.out);
System.out.printf("%n" + errors);
ownerships = probe.effectiveOwnershipWithPort(keyspace);
hasEffectiveOwns = true;
}
else
catch (IllegalStateException e)
{
Map<InetAddress, Float> ownerships = null;
boolean hasEffectiveOwns = false;
try
{
ownerships = probe.effectiveOwnership(keyspace);
hasEffectiveOwns = true;
}
catch (IllegalStateException e)
{
ownerships = probe.getOwnership();
errors.append("Note: ").append(e.getMessage()).append("%n");
}
catch (IllegalArgumentException ex)
{
System.out.printf("%nError: %s%n", ex.getMessage());
System.exit(1);
}
SortedMap<String, SetHostStat> dcs = NodeTool.getOwnershipByDc(probe, resolveIp, tokensToEndpoints, ownerships);
// More tokens than nodes (aka vnodes)?
if (dcs.values().size() < tokensToEndpoints.keySet().size())
isTokenPerNode = false;
// Datacenters
for (Map.Entry<String, SetHostStat> dc : dcs.entrySet())
{
String dcHeader = String.format("Datacenter: %s%n", dc.getKey());
System.out.print(dcHeader);
for (int i = 0; i < (dcHeader.length() - 1); i++) System.out.print('=');
System.out.println();
// Legend
System.out.println("Status=Up/Down");
System.out.println("|/ State=Normal/Leaving/Joining/Moving");
addNodesHeader(hasEffectiveOwns, tableBuilder);
ArrayListMultimap<InetAddress, HostStat> hostToTokens = ArrayListMultimap.create();
for (HostStat stat : dc.getValue())
hostToTokens.put(stat.endpoint, stat);
for (InetAddress endpoint : hostToTokens.keySet())
{
Float owns = ownerships.get(endpoint);
List<HostStat> tokens = hostToTokens.get(endpoint);
addNode(endpoint.getHostAddress(), owns, tokens, hasEffectiveOwns, tableBuilder);
}
}
tableBuilder.printTo(System.out);
System.out.printf("%n" + errors);
ownerships = probe.getOwnershipWithPort();
errors.append("Note: ").append(e.getMessage()).append("%n");
}
catch (IllegalArgumentException ex)
{
System.out.printf("%nError: %s%n", ex.getMessage());
System.exit(1);
}
SortedMap<String, SetHostStatWithPort> dcs = NodeTool.getOwnershipByDcWithPort(probe, resolveIp, tokensToEndpoints, ownerships);
// More tokens than nodes (aka vnodes)?
if (dcs.size() < tokensToEndpoints.size())
isTokenPerNode = false;
// Datacenters
for (Map.Entry<String, SetHostStatWithPort> dc : dcs.entrySet())
{
TableBuilder tableBuilder = sharedTable.next();
addNodesHeader(hasEffectiveOwns, tableBuilder);
ArrayListMultimap<String, HostStatWithPort> hostToTokens = ArrayListMultimap.create();
for (HostStatWithPort stat : dc.getValue())
hostToTokens.put(stat.ipOrDns(printPort), stat);
for (String endpoint : hostToTokens.keySet())
{
Float owns = ownerships.get(endpoint);
List<HostStatWithPort> tokens = hostToTokens.get(endpoint);
addNode(endpoint, owns, tokens.get(0).ipOrDns(printPort), tokens.get(0).token, tokens.size(),
hasEffectiveOwns, tableBuilder);
}
}
Iterator<TableBuilder> results = sharedTable.complete().iterator();
boolean first = true;
for (Map.Entry<String, SetHostStatWithPort> dc : dcs.entrySet())
{
if (!first) {
System.out.println();
}
first = false;
String dcHeader = String.format("Datacenter: %s%n", dc.getKey());
System.out.print(dcHeader);
for (int i = 0; i < (dcHeader.length() - 1); i++) System.out.print('=');
System.out.println();
// Legend
System.out.println("Status=Up/Down");
System.out.println("|/ State=Normal/Leaving/Joining/Moving");
TableBuilder dcTable = results.next();
dcTable.printTo(System.out);
}
System.out.printf("%n" + errors);
}
private void addNodesHeader(boolean hasEffectiveOwns, TableBuilder tableBuilder)
@ -226,15 +181,4 @@ public class Status extends NodeToolCmd
}
}
private void addNode(String endpoint, Float owns, List<HostStat> tokens, boolean hasEffectiveOwns,
TableBuilder tableBuilder)
{
addNode(endpoint, owns, tokens.get(0).ipOrDns(), tokens.get(0).token, tokens.size(), hasEffectiveOwns, tableBuilder);
}
private void addNodeWithPort(String endpoint, Float owns, List<HostStatWithPort> tokens, boolean hasEffectiveOwns,
TableBuilder tableBuilder)
{
addNode(endpoint, owns, tokens.get(0).ipOrDns(), tokens.get(0).token, tokens.size(), hasEffectiveOwns, tableBuilder);
}
}

View File

@ -20,8 +20,11 @@ package org.apache.cassandra.tools.nodetool.formatter;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
/**
@ -62,6 +65,13 @@ public class TableBuilder
this.columnDelimiter = columnDelimiter;
}
private TableBuilder(TableBuilder base, int[] maximumColumnWidth)
{
this(base.columnDelimiter);
this.maximumColumnWidth = maximumColumnWidth;
this.rows.addAll(base.rows);
}
public void add(@Nonnull String... row)
{
Objects.requireNonNull(row);
@ -105,4 +115,56 @@ public class TableBuilder
out.println();
}
}
/**
* Share max offsets across multiple TableBuilders
*/
public static class SharedTable {
private List<TableBuilder> tables = new ArrayList<>();
private final String columnDelimiter;
public SharedTable()
{
this(' ');
}
public SharedTable(char columnDelimiter)
{
this(String.valueOf(columnDelimiter));
}
public SharedTable(String columnDelimiter)
{
this.columnDelimiter = columnDelimiter;
}
public TableBuilder next()
{
TableBuilder next = new TableBuilder(columnDelimiter);
tables.add(next);
return next;
}
public List<TableBuilder> complete()
{
if (tables.size() == 0)
return Collections.emptyList();
final int columns = tables.stream()
.max(Comparator.comparing(tb -> tb.maximumColumnWidth.length))
.get().maximumColumnWidth.length;
final int[] maximumColumnWidth = new int[columns];
for (TableBuilder tb : tables)
{
for (int i = 0; i < tb.maximumColumnWidth.length; i++)
{
maximumColumnWidth[i] = Math.max(tb.maximumColumnWidth[i], maximumColumnWidth[i]);
}
}
return tables.stream()
.map(tb -> new TableBuilder(tb, maximumColumnWidth))
.collect(Collectors.toList());
}
}
}