From 7b7212338c84376ef66d3ec31b0b8582100cf4ff Mon Sep 17 00:00:00 2001 From: Chris Lohfink Date: Mon, 17 Feb 2020 15:50:50 -0600 Subject: [PATCH] Fix multi DC nodetool status output Patch by Chris Lohfink; Reviewed by Brandon Williams for CASSANDRA-15305 --- CHANGES.txt | 1 + .../tools/nodetool/DescribeCluster.java | 2 +- .../tools/nodetool/HostStatWithPort.java | 25 +-- .../cassandra/tools/nodetool/Status.java | 180 ++++++------------ .../nodetool/formatter/TableBuilder.java | 62 ++++++ 5 files changed, 140 insertions(+), 130 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 129a093f04..1da82a5f55 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -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) diff --git a/src/java/org/apache/cassandra/tools/nodetool/DescribeCluster.java b/src/java/org/apache/cassandra/tools/nodetool/DescribeCluster.java index fe3355154c..6a9f023a29 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/DescribeCluster.java +++ b/src/java/org/apache/cassandra/tools/nodetool/DescribeCluster.java @@ -109,7 +109,7 @@ public class DescribeCluster extends NodeToolCmd ArrayListMultimap 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 diff --git a/src/java/org/apache/cassandra/tools/nodetool/HostStatWithPort.java b/src/java/org/apache/cassandra/tools/nodetool/HostStatWithPort.java index 54cda17c42..4849fd1ffe 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/HostStatWithPort.java +++ b/src/java/org/apache/cassandra/tools/nodetool/HostStatWithPort.java @@ -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(); } } diff --git a/src/java/org/apache/cassandra/tools/nodetool/Status.java b/src/java/org/apache/cassandra/tools/nodetool/Status.java index 8c37022f51..fe1133500e 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/Status.java +++ b/src/java/org/apache/cassandra/tools/nodetool/Status.java @@ -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 ownerships = null; + boolean hasEffectiveOwns = false; + try { - Map 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 dcs = NodeTool.getOwnershipByDcWithPort(probe, resolveIp, tokensToEndpoints, ownerships); - - // More tokens than nodes (aka vnodes)? - if (dcs.size() < tokensToEndpoints.size()) - isTokenPerNode = false; - - // Datacenters - for (Map.Entry 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 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 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 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 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 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 hostToTokens = ArrayListMultimap.create(); - for (HostStat stat : dc.getValue()) - hostToTokens.put(stat.endpoint, stat); - - for (InetAddress endpoint : hostToTokens.keySet()) - { - Float owns = ownerships.get(endpoint); - List 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 dcs = NodeTool.getOwnershipByDcWithPort(probe, resolveIp, tokensToEndpoints, ownerships); + + // More tokens than nodes (aka vnodes)? + if (dcs.size() < tokensToEndpoints.size()) + isTokenPerNode = false; + + // Datacenters + for (Map.Entry dc : dcs.entrySet()) + { + TableBuilder tableBuilder = sharedTable.next(); + addNodesHeader(hasEffectiveOwns, tableBuilder); + + ArrayListMultimap 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 tokens = hostToTokens.get(endpoint); + addNode(endpoint, owns, tokens.get(0).ipOrDns(printPort), tokens.get(0).token, tokens.size(), + hasEffectiveOwns, tableBuilder); + } + } + + Iterator results = sharedTable.complete().iterator(); + boolean first = true; + for (Map.Entry 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 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 tokens, boolean hasEffectiveOwns, - TableBuilder tableBuilder) - { - addNode(endpoint, owns, tokens.get(0).ipOrDns(), tokens.get(0).token, tokens.size(), hasEffectiveOwns, tableBuilder); - } } diff --git a/src/java/org/apache/cassandra/tools/nodetool/formatter/TableBuilder.java b/src/java/org/apache/cassandra/tools/nodetool/formatter/TableBuilder.java index bf06d99ce5..2557a7d0a2 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/formatter/TableBuilder.java +++ b/src/java/org/apache/cassandra/tools/nodetool/formatter/TableBuilder.java @@ -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 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 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()); + } + } }