Author: brandonwilliams Date: Fri Nov 19 22:23:06 2010 New Revision: 1037056
URL: http://svn.apache.org/viewvc?rev=1037056&view=rev Log: nodetool ring shows the percentage each node owns. Patch by Jon Hermes, reviewed by brandonwilliams for CASSANDRA-1553 Modified: cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/CollatingOrderPreservingPartitioner.java cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/IPartitioner.java cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/LocalPartitioner.java cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/RandomPartitioner.java cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/service/StorageService.java cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/service/StorageServiceMBean.java cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/tools/NodeCmd.java cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/tools/NodeProbe.java Modified: cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java?rev=1037056&r1=1037055&r2=1037056&view=diff ============================================================================== --- cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java (original) +++ cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java Fri Nov 19 22:23:06 2010 @@ -19,6 +19,9 @@ package org.apache.cassandra.dht; import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class ByteOrderedPartitioner extends AbstractByteOrderedPartitioner { @@ -28,4 +31,6 @@ public class ByteOrderedPartitioner exte return MINIMUM; return new BytesToken(key); } + + public Map<Token, Float> describeOwnership(List<Token> sortedTokens){ throw new UnsupportedOperationException(); } } Modified: cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/CollatingOrderPreservingPartitioner.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/CollatingOrderPreservingPartitioner.java?rev=1037056&r1=1037055&r2=1037056&view=diff ============================================================================== --- cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/CollatingOrderPreservingPartitioner.java (original) +++ cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/CollatingOrderPreservingPartitioner.java Fri Nov 19 22:23:06 2010 @@ -21,7 +21,10 @@ package org.apache.cassandra.dht; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.text.Collator; +import java.util.HashMap; +import java.util.List; import java.util.Locale; +import java.util.Map; import org.apache.cassandra.utils.FBUtilities; @@ -45,4 +48,6 @@ public class CollatingOrderPreservingPar } return new BytesToken(ByteBuffer.wrap(collator.getCollationKey(skey).toByteArray())); } + + public Map<Token, Float> describeOwnership(List<Token> sortedTokens){ throw new UnsupportedOperationException(); } } Modified: cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/IPartitioner.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/IPartitioner.java?rev=1037056&r1=1037055&r2=1037056&view=diff ============================================================================== --- cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/IPartitioner.java (original) +++ cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/IPartitioner.java Fri Nov 19 22:23:06 2010 @@ -19,6 +19,8 @@ package org.apache.cassandra.dht; import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; import org.apache.cassandra.db.DecoratedKey; @@ -73,4 +75,13 @@ public interface IPartitioner<T extends * it generates. */ public boolean preservesOrder(); + + /** + * Calculate the deltas between tokens in the ring in order to compare + * relative sizes. + * + * @param sortedTokens a sorted List of Tokens + * @return the mapping from 'token' to 'percentage of the ring owned by that token'. + */ + public Map<Token, Float> describeOwnership(List<Token> sortedTokens); } Modified: cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/LocalPartitioner.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/LocalPartitioner.java?rev=1037056&r1=1037055&r2=1037056&view=diff ============================================================================== --- cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/LocalPartitioner.java (original) +++ cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/LocalPartitioner.java Fri Nov 19 22:23:06 2010 @@ -20,6 +20,7 @@ package org.apache.cassandra.dht; import java.nio.ByteBuffer; +import java.util.*; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.marshal.AbstractType; @@ -74,4 +75,9 @@ public class LocalPartitioner implements { return true; } + + public Map<Token, Float> describeOwnership(List<Token> sortedTokens) + { + return Collections.singletonMap((Token)getMinimumToken(), new Float(1.0)); + } } Modified: cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java?rev=1037056&r1=1037055&r2=1037056&view=diff ============================================================================== --- cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java (original) +++ cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java Fri Nov 19 22:23:06 2010 @@ -21,11 +21,14 @@ package org.apache.cassandra.dht; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; -import java.util.Random; +import java.util.*; import com.google.common.base.Charsets; +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.DecoratedKey; +import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.Pair; @@ -157,4 +160,41 @@ public class OrderPreservingPartitioner } return new StringToken(skey); } + + public Map<Token, Float> describeOwnership(List<Token> sortedTokens) + { + // alltokens will contain the count and be returned, sorted_ranges is shorthand for token<->token math. + Map<Token, Float> alltokens = new HashMap<Token, Float>(); + List<Range> sorted_ranges = new ArrayList<Range>(); + + // this initializes the counts to 0 and calcs the ranges in order. + Token last_t = sortedTokens.get(sortedTokens.size()-1); + for (Token node : sortedTokens) + { + alltokens.put(node, new Float(0.0)); + sorted_ranges.add(new Range(last_t, node)); + last_t = node; + } + + for(String ks : DatabaseDescriptor.getTables()) + { + for (CFMetaData cfmd : DatabaseDescriptor.getKSMetaData(ks).cfMetaData().values()) + { + for (Range r : sorted_ranges) + { + // Looping over every KS:CF:Range, get the splits size and add it to the count + alltokens.put(r.right, alltokens.get(r.right) + StorageService.instance.getSplits(ks, cfmd.cfName, r, 1).size()); + } + } + } + + // Sum every count up and divide count/total for the fractional ownership. + Float total = new Float(0.0); + for (Float f : alltokens.values()) { total += f; } + for (Map.Entry<Token, Float> row : alltokens.entrySet()) { + alltokens.put(row.getKey(), row.getValue() / total); + } + + return alltokens; + } } Modified: cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/RandomPartitioner.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/RandomPartitioner.java?rev=1037056&r1=1037055&r2=1037056&view=diff ============================================================================== --- cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/RandomPartitioner.java (original) +++ cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/dht/RandomPartitioner.java Fri Nov 19 22:23:06 2010 @@ -20,9 +20,10 @@ package org.apache.cassandra.dht; import static com.google.common.base.Charsets.UTF_8; +import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; -import java.util.Arrays; +import java.util.*; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.utils.FBUtilities; @@ -127,4 +128,36 @@ public class RandomPartitioner implement return MINIMUM; return new BigIntegerToken(FBUtilities.md5hash(key)); } + + public Map<Token, Float> describeOwnership(List<Token> sortedTokens) + { + Map<Token, Float> ownerships = new HashMap<Token, Float>(); + Iterator i = sortedTokens.iterator(); + + // 0-case + if (!i.hasNext()) { throw new RuntimeException("No nodes present in the cluster. How did you call this?"); } + // 1-case + if (sortedTokens.size() == 1) { + ownerships.put((Token)i.next(), new Float(1.0)); + } + // n-case + else { + // NOTE: All divisions must take place in BigDecimals, and all modulo operators must take place in BigIntegers. + final BigInteger ri = new BigInteger("2").pow(127); // (used for addition later) + final BigDecimal r = new BigDecimal(ri); // The entire range, 2**127 + Token start = (Token)i.next(); BigInteger ti = ((BigIntegerToken)start).token; // The first token and its value + Token t; BigInteger tim1 = ti; // The last token and its value (after loop) + while (i.hasNext()) { + t = (Token)i.next(); ti = ((BigIntegerToken)t).token; // The next token and its value + float x = new BigDecimal(ti.subtract(tim1)).divide(r).floatValue(); // %age = T(i) - T(i-1) / R + ownerships.put(t, x); // save (T(i) -> %age) + tim1 = ti; // -> advance loop + } + // The start token's range extends backward to the last token, which is why both were saved + // above. The simple calculation for this is: T(start) - T(end) + r % r / r. + // (In the 1-case, this produces 0% instead of 100%.) + ownerships.put(start, new BigDecimal(((BigIntegerToken)start).token.subtract(ti).add(ri).mod(ri)).divide(r).floatValue()); + } + return ownerships; + } } Modified: cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/service/StorageService.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/service/StorageService.java?rev=1037056&r1=1037055&r2=1037056&view=diff ============================================================================== --- cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/service/StorageService.java (original) +++ cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/service/StorageService.java Fri Nov 19 22:23:06 2010 @@ -2058,4 +2058,11 @@ public class StorageService implements I logger_.debug("cache saves completed"); } + public Map<Token, Float> getOwnership() + { + List<Token> sortedTokens = new ArrayList<Token>(getTokenToEndpointMap().keySet()); + Collections.sort(sortedTokens); + return partitioner_.describeOwnership(sortedTokens); + } + } Modified: cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/service/StorageServiceMBean.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/service/StorageServiceMBean.java?rev=1037056&r1=1037055&r2=1037056&view=diff ============================================================================== --- cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/service/StorageServiceMBean.java (original) +++ cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/service/StorageServiceMBean.java Fri Nov 19 22:23:06 2010 @@ -29,6 +29,7 @@ import java.util.concurrent.ExecutionExc import java.util.concurrent.TimeoutException; import org.apache.cassandra.config.ConfigurationException; +import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.Range; import org.apache.cassandra.dht.Token; import org.apache.cassandra.thrift.UnavailableException; @@ -263,4 +264,10 @@ public interface StorageServiceMBean /** save row and key caches */ public void saveCaches() throws ExecutionException, InterruptedException; + + /** + * given a list of tokens (representing the nodes in the cluster), returns + * a mapping from "token -> %age of cluster owned by that token" + */ + public Map<Token, Float> getOwnership(); } Modified: cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/tools/NodeCmd.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/tools/NodeCmd.java?rev=1037056&r1=1037055&r2=1037056&view=diff ============================================================================== --- cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/tools/NodeCmd.java (original) +++ cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/tools/NodeCmd.java Fri Nov 19 22:23:06 2010 @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.PrintStream; import java.lang.management.MemoryUsage; import java.net.InetAddress; +import java.text.DecimalFormat; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ExecutionException; @@ -34,6 +35,7 @@ import org.apache.commons.cli.*; import org.apache.cassandra.cache.JMXInstrumentedCacheMBean; import org.apache.cassandra.concurrent.IExecutorMBean; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ColumnFamilyStoreMBean; import org.apache.cassandra.dht.Token; import org.apache.cassandra.net.MessagingServiceMBean; @@ -95,23 +97,33 @@ public class NodeCmd { Collection<String> leavingNodes = probe.getLeavingNodes(); Map<String, String> loadMap = probe.getLoadMap(); - outs.printf("%-16s%-7s%-8s%-16s%-44s\n", "Address", "Status", "State", "Load", "Token"); + outs.printf("%-16s%-7s%-8s%-16s%-8s%-44s\n", "Address", "Status", "State", "Load", "Owns", "Token"); // show pre-wrap token twice so you can always read a node's range as // (previous line token, current line token] if (sortedTokens.size() > 1) - outs.printf("%-14s%-11s%-14s%-43s\n", "", "", "", sortedTokens.get(sortedTokens.size() - 1)); + outs.printf("%-16s%-7s%-8s%-16s%-8s%-44s\n", "", "", "", "", "", sortedTokens.get(sortedTokens.size() - 1)); + + // Calculate per-token ownership of the ring + Map<Token, Float> ownerships = probe.getOwnership(); for (Token token : sortedTokens) { String primaryEndpoint = tokenToEndpoint.get(token); String status = liveNodes.contains(primaryEndpoint) ? "Up" - : deadNodes.contains(primaryEndpoint) ? "Down" : "?"; + : deadNodes.contains(primaryEndpoint) + ? "Down" + : "?"; String state = joiningNodes.contains(primaryEndpoint) ? "Joining" - : leavingNodes.contains(primaryEndpoint) ? "Leaving" : "Normal"; - String load = loadMap.containsKey(primaryEndpoint) ? loadMap.get(primaryEndpoint) : "?"; - outs.printf("%-16s%-7s%-8s%-16s%-44s\n", primaryEndpoint, status, state, load, token); + : leavingNodes.contains(primaryEndpoint) + ? "Leaving" + : "Normal"; + String load = loadMap.containsKey(primaryEndpoint) + ? loadMap.get(primaryEndpoint) + : "?"; + String owns = new DecimalFormat("##0.00%").format(ownerships.get(token)); + outs.printf("%-16s%-7s%-8s%-16s%-8s%-44s\n", primaryEndpoint, status, state, load, owns, token); } } Modified: cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/tools/NodeProbe.java URL: http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/tools/NodeProbe.java?rev=1037056&r1=1037055&r2=1037056&view=diff ============================================================================== --- cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/tools/NodeProbe.java (original) +++ cassandra/branches/cassandra-0.7/src/java/org/apache/cassandra/tools/NodeProbe.java Fri Nov 19 22:23:06 2010 @@ -24,6 +24,7 @@ import java.lang.management.ManagementFa import java.lang.management.MemoryMXBean; import java.lang.management.MemoryUsage; import java.lang.management.RuntimeMXBean; +import java.lang.reflect.Constructor; import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.*; @@ -44,12 +45,17 @@ import org.apache.cassandra.cache.JMXIns import org.apache.cassandra.concurrent.IExecutorMBean; import org.apache.cassandra.config.ConfigurationException; import org.apache.cassandra.db.ColumnFamilyStoreMBean; +import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.Token; +import org.apache.cassandra.locator.IEndpointSnitch; +import org.apache.cassandra.locator.TokenMetadata; import org.apache.cassandra.net.MessagingServiceMBean; +import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageServiceMBean; import org.apache.cassandra.streaming.StreamingService; import org.apache.cassandra.streaming.StreamingServiceMBean; import org.apache.cassandra.thrift.UnavailableException; +import org.apache.cassandra.utils.FBUtilities; import static com.google.common.base.Charsets.UTF_8; @@ -197,6 +203,11 @@ public class NodeProbe return ssProxy.getLoadMap(); } + public Map<Token, Float> getOwnership() + { + return ssProxy.getOwnership(); + } + public Iterator<Map.Entry<String, ColumnFamilyStoreMBean>> getColumnFamilyStoreMBeanProxies() { try
