/** * 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.tools; import java.io.IOException; import java.io.PrintStream; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryUsage; import java.lang.management.RuntimeMXBean; import java.net.InetAddress; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import java.util.concurrent.ExecutionException; import javax.management.*; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import org.apache.cassandra.cache.JMXInstrumentedCacheMBean; import org.apache.cassandra.concurrent.IExecutorMBean; import org.apache.cassandra.db.ColumnFamilyStoreMBean; import org.apache.cassandra.db.CompactionManager; import org.apache.cassandra.db.CompactionManagerMBean; import org.apache.cassandra.dht.Range; import org.apache.cassandra.service.StorageServiceMBean; import org.apache.cassandra.streaming.StreamingService; import org.apache.cassandra.streaming.StreamingServiceMBean; /** * JMX client operations for Cassandra. */ public class NodeProbe { private static final String fmtUrl = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi"; private static final String ssObjName = "org.apache.cassandra.service:type=StorageService"; private static final int defaultPort = 8080; private String host; private int port; private MBeanServerConnection mbeanServerConn; private StorageServiceMBean ssProxy; private MemoryMXBean memProxy; private RuntimeMXBean runtimeProxy; private CompactionManagerMBean mcmProxy; private StreamingServiceMBean streamProxy; /** * Creates a NodeProbe using the specified JMX host and port. * * @param host hostname or IP address of the JMX agent * @param port TCP port of the remote JMX agent * @throws IOException on connection failures */ public NodeProbe(String host, int port) throws IOException, InterruptedException { this.host = host; this.port = port; connect(); } /** * Creates a NodeProbe using the specified JMX host and default port. * * @param host hostname or IP address of the JMX agent * @throws IOException on connection failures */ public NodeProbe(String host) throws IOException, InterruptedException { this.host = host; this.port = defaultPort; connect(); } /** * Create a connection to the JMX agent and setup the M[X]Bean proxies. * * @throws IOException on connection failures */ private void connect() throws IOException { JMXServiceURL jmxUrl = new JMXServiceURL(String.format(fmtUrl, host, port)); JMXConnector jmxc = JMXConnectorFactory.connect(jmxUrl, null); mbeanServerConn = jmxc.getMBeanServerConnection(); try { ObjectName name = new ObjectName(ssObjName); ssProxy = JMX.newMBeanProxy(mbeanServerConn, name, StorageServiceMBean.class); name = new ObjectName(CompactionManager.MBEAN_OBJECT_NAME); mcmProxy = JMX.newMBeanProxy(mbeanServerConn, name, CompactionManagerMBean.class); name = new ObjectName(StreamingService.MBEAN_OBJECT_NAME); streamProxy = JMX.newMBeanProxy(mbeanServerConn, name, StreamingServiceMBean.class); } catch (MalformedObjectNameException e) { throw new RuntimeException( "Invalid ObjectName? Please report this as a bug.", e); } memProxy = ManagementFactory.newPlatformMXBeanProxy(mbeanServerConn, ManagementFactory.MEMORY_MXBEAN_NAME, MemoryMXBean.class); runtimeProxy = ManagementFactory.newPlatformMXBeanProxy( mbeanServerConn, ManagementFactory.RUNTIME_MXBEAN_NAME, RuntimeMXBean.class); } public void forceTableCleanup() throws IOException { ssProxy.forceTableCleanup(); } public void forceTableCompaction() throws IOException { ssProxy.forceTableCompaction(); } public void forceTableFlush(String tableName, String... columnFamilies) throws IOException { ssProxy.forceTableFlush(tableName, columnFamilies); } public void forceTableRepair(String tableName, String... columnFamilies) throws IOException { ssProxy.forceTableRepair(tableName, columnFamilies); } public void drain() throws IOException, InterruptedException, ExecutionException { ssProxy.drain(); } public Map<Range, List<String>> getRangeToEndPointMap(String tableName) { return ssProxy.getRangeToEndPointMap(tableName); } public Set<String> getLiveNodes() { return ssProxy.getLiveNodes(); } /** * Write a textual representation of the Cassandra ring. * * @param outs the stream to write to */ public void printRing(PrintStream outs) { Map<Range, List<String>> rangeMap = ssProxy.getRangeToEndPointMap(null); List<Range> ranges = new ArrayList<Range>(rangeMap.keySet()); Collections.sort(ranges); Set<String> liveNodes = ssProxy.getLiveNodes(); Set<String> deadNodes = ssProxy.getUnreachableNodes(); Map<String, String> loadMap = ssProxy.getLoadMap(); // Print range-to-endpoint mapping int counter = 0; outs.print(String.format("%-14s", "Address")); outs.print(String.format("%-11s", "Status")); outs.print(String.format("%-14s", "Load")); outs.print(String.format("%-43s", "Range")); outs.println("Ring"); // emphasize that we're showing the right part of each range if (ranges.size() > 1) { outs.println(String.format("%-14s%-11s%-14s%-43s", "", "", "", ranges.get(0).left)); } // normal range & node info for (Range range : ranges) { List<String> endpoints = rangeMap.get(range); String primaryEndpoint = endpoints.get(0); outs.print(String.format("%-14s", primaryEndpoint)); String status = liveNodes.contains(primaryEndpoint) ? "Up" : deadNodes.contains(primaryEndpoint) ? "Down" : "?"; outs.print(String.format("%-11s", status)); String load = loadMap.containsKey(primaryEndpoint) ? loadMap.get(primaryEndpoint) : "?"; outs.print(String.format("%-14s", load)); outs.print(String.format("%-43s", range.right)); String asciiRingArt; if (counter == 0) { asciiRingArt = "|<--|"; } else if (counter == (rangeMap.size() - 1)) { asciiRingArt = "|-->|"; } else { if ((rangeMap.size() > 4) && ((counter % 2) == 0)) asciiRingArt = "v |"; else if ((rangeMap.size() > 4) && ((counter % 2) != 0)) asciiRingArt = "| ^"; else asciiRingArt = "| |"; } outs.println(asciiRingArt); counter++; } } public Set<String> getUnreachableNodes() { return ssProxy.getUnreachableNodes(); } public Map<String, String> getLoadMap() { return ssProxy.getLoadMap(); } public Iterator<Map.Entry<String, ColumnFamilyStoreMBean>> getColumnFamilyStoreMBeanProxies() { try { return new ColumnFamilyStoreMBeanIterator(mbeanServerConn); } catch (MalformedObjectNameException e) { throw new RuntimeException("Invalid ObjectName? Please report this as a bug.", e); } catch (IOException e) { throw new RuntimeException("Could not retrieve list of stat mbeans.", e); } } public JMXInstrumentedCacheMBean getKeyCacheMBean(String tableName, String cfName) { String keyCachePath = "org.apache.cassandra.db:type=Caches,keyspace=" + tableName + ",cache=" + cfName + "KeyCache"; try { return JMX.newMBeanProxy(mbeanServerConn, new ObjectName(keyCachePath), JMXInstrumentedCacheMBean.class); } catch (MalformedObjectNameException e) { throw new RuntimeException(e); } } public JMXInstrumentedCacheMBean getRowCacheMBean(String tableName, String cfName) { String rowCachePath = "org.apache.cassandra.db:type=Caches,keyspace=" + tableName + ",cache=" + cfName + "RowCache"; try { return JMX.newMBeanProxy(mbeanServerConn, new ObjectName(rowCachePath), JMXInstrumentedCacheMBean.class); } catch (MalformedObjectNameException e) { throw new RuntimeException(e); } } public String getToken() { return ssProxy.getToken(); } public String getLoadString() { return ssProxy.getLoadString(); } public int getCurrentGenerationNumber() { return ssProxy.getCurrentGenerationNumber(); } public long getUptime() { return runtimeProxy.getUptime(); } public MemoryUsage getHeapMemoryUsage() { return memProxy.getHeapMemoryUsage(); } /** * Take a snapshot of all the tables. * * @param snapshotName the name of the snapshot. */ public void takeSnapshot(String snapshotName) throws IOException { ssProxy.takeAllSnapshot(snapshotName); } /** * Remove all the existing snapshots. */ public void clearSnapshot() throws IOException { ssProxy.clearSnapshot(); } public void decommission() throws InterruptedException { ssProxy.decommission(); } public void loadBalance() throws IOException, InterruptedException { ssProxy.loadBalance(); } public void move(String newToken) throws IOException, InterruptedException { ssProxy.move(newToken); } public void removeToken(String token) { ssProxy.removeToken(token); } public Iterator<Map.Entry<String, IExecutorMBean>> getThreadPoolMBeanProxies() { try { return new ThreadPoolProxyMBeanIterator(mbeanServerConn); } catch (MalformedObjectNameException e) { throw new RuntimeException("Invalid ObjectName? Please report this as a bug.", e); } catch (IOException e) { throw new RuntimeException("Could not retrieve list of stat mbeans.", e); } } /** * Get the compaction threshold * * @param outs the stream to write to */ public void getCompactionThreshold(PrintStream outs) { outs.println("Current compaction threshold: Min=" + mcmProxy.getMinimumCompactionThreshold() + ", Max=" + mcmProxy.getMaximumCompactionThreshold()); } /** * Set the compaction threshold * * @param minimumCompactionThreshold minimum compaction threshold * @param maximumCompactionThreshold maximum compaction threshold */ public void setCompactionThreshold(int minimumCompactionThreshold, int maximumCompactionThreshold) { mcmProxy.setMinimumCompactionThreshold(minimumCompactionThreshold); if (maximumCompactionThreshold >= 0) { mcmProxy.setMaximumCompactionThreshold(maximumCompactionThreshold); } } public void setCacheCapacities(String tableName, String cfName, int keyCacheCapacity, int rowCacheCapacity) { try { String keyCachePath = "org.apache.cassandra.db:type=Caches,keyspace=" + tableName + ",cache=" + cfName + "KeyCache"; JMXInstrumentedCacheMBean keyCacheMBean = JMX.newMBeanProxy(mbeanServerConn, new ObjectName(keyCachePath), JMXInstrumentedCacheMBean.class); keyCacheMBean.setCapacity(keyCacheCapacity); String rowCachePath = "org.apache.cassandra.db:type=Caches,keyspace=" + tableName + ",cache=" + cfName + "RowCache"; JMXInstrumentedCacheMBean rowCacheMBean = null; rowCacheMBean = JMX.newMBeanProxy(mbeanServerConn, new ObjectName(rowCachePath), JMXInstrumentedCacheMBean.class); rowCacheMBean.setCapacity(rowCacheCapacity); } catch (MalformedObjectNameException e) { throw new RuntimeException(e); } } public List<InetAddress> getEndPoints(String keyspace, String key) { return ssProxy.getNaturalEndpoints(keyspace, key); } public Set<InetAddress> getStreamDestinations() { return streamProxy.getStreamDestinations(); } public List<String> getFilesDestinedFor(InetAddress host) throws IOException { return streamProxy.getOutgoingFiles(host.getHostAddress()); } public Set<InetAddress> getStreamSources() { return streamProxy.getStreamSources(); } public List<String> getIncomingFiles(InetAddress host) throws IOException { return streamProxy.getIncomingFiles(host.getHostAddress()); } public String getOperationMode() { return ssProxy.getOperationMode(); } } class ColumnFamilyStoreMBeanIterator implements Iterator<Map.Entry<String, ColumnFamilyStoreMBean>> { private Iterator<ObjectName> resIter; private MBeanServerConnection mbeanServerConn; public ColumnFamilyStoreMBeanIterator(MBeanServerConnection mbeanServerConn) throws MalformedObjectNameException, NullPointerException, IOException { ObjectName query = new ObjectName("org.apache.cassandra.db:type=ColumnFamilyStores,*"); resIter = mbeanServerConn.queryNames(query, null).iterator(); this.mbeanServerConn = mbeanServerConn; } public boolean hasNext() { return resIter.hasNext(); } public Entry<String, ColumnFamilyStoreMBean> next() { ObjectName objectName = resIter.next(); String tableName = objectName.getKeyProperty("keyspace"); ColumnFamilyStoreMBean cfsProxy = JMX.newMBeanProxy(mbeanServerConn, objectName, ColumnFamilyStoreMBean.class); return new AbstractMap.SimpleImmutableEntry<String, ColumnFamilyStoreMBean>(tableName, cfsProxy); } public void remove() { throw new UnsupportedOperationException(); } } class ThreadPoolProxyMBeanIterator implements Iterator<Map.Entry<String, IExecutorMBean>> { private Iterator<ObjectName> resIter; private MBeanServerConnection mbeanServerConn; public ThreadPoolProxyMBeanIterator(MBeanServerConnection mbeanServerConn) throws MalformedObjectNameException, NullPointerException, IOException { ObjectName query = new ObjectName("org.apache.cassandra.concurrent:type=*"); resIter = mbeanServerConn.queryNames(query, null).iterator(); this.mbeanServerConn = mbeanServerConn; } public boolean hasNext() { return resIter.hasNext(); } public Map.Entry<String, IExecutorMBean> next() { ObjectName objectName = resIter.next(); String poolName = objectName.getKeyProperty("type"); IExecutorMBean threadPoolProxy = JMX.newMBeanProxy(mbeanServerConn, objectName, IExecutorMBean.class); return new AbstractMap.SimpleImmutableEntry<String, IExecutorMBean>(poolName, threadPoolProxy); } public void remove() { throw new UnsupportedOperationException(); } }