You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ee...@apache.org on 2009/10/22 00:22:26 UTC

svn commit: r828238 - in /incubator/cassandra/trunk/contrib/circuit: ./ bin/ resources/ resources/java/ resources/java/images/ src/ src/org/ src/org/apache/ src/org/apache/cassandra/ src/org/apache/cassandra/contrib/ src/org/apache/cassandra/contrib/ci...

Author: eevans
Date: Wed Oct 21 22:22:26 2009
New Revision: 828238

URL: http://svn.apache.org/viewvc?rev=828238&view=rev
Log:
initial commit of visualization and diagnostics app

Patch by eevans and gdusbabek; reviewed by gdusbabek for CASSANDRA-252

Added:
    incubator/cassandra/trunk/contrib/circuit/
    incubator/cassandra/trunk/contrib/circuit/bin/
    incubator/cassandra/trunk/contrib/circuit/bin/circuit   (with props)
    incubator/cassandra/trunk/contrib/circuit/build.xml   (with props)
    incubator/cassandra/trunk/contrib/circuit/resources/
    incubator/cassandra/trunk/contrib/circuit/resources/java/
    incubator/cassandra/trunk/contrib/circuit/resources/java/images/
    incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_blue30x30.png
    incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_green30x30.png
    incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_red30x30.png
    incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_yellow30x30.png
    incubator/cassandra/trunk/contrib/circuit/resources/node.xcf
    incubator/cassandra/trunk/contrib/circuit/src/
    incubator/cassandra/trunk/contrib/circuit/src/org/
    incubator/cassandra/trunk/contrib/circuit/src/org/apache/
    incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/
    incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/
    incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/
    incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/AboutDialog.java   (with props)
    incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/CircuitFrame.java   (with props)
    incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingModel.java   (with props)
    incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingPanel.java   (with props)

Added: incubator/cassandra/trunk/contrib/circuit/bin/circuit
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/circuit/bin/circuit?rev=828238&view=auto
==============================================================================
--- incubator/cassandra/trunk/contrib/circuit/bin/circuit (added)
+++ incubator/cassandra/trunk/contrib/circuit/bin/circuit Wed Oct 21 22:22:26 2009
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+# 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.
+
+DEFAULT_PORT=8080
+
+if [ "x$CLASSPATH" = "x" ]; then
+    # Cassandra class files.
+    if [ ! -d `dirname $0`/../../../build/classes ]; then
+        echo "Unable to locate cassandra class files" >&2
+        exit 1
+    fi
+
+    # Circuit class files.
+    if [ ! -d `dirname $0`/../build/classes ]; then
+        echo "Unable to locate circuit class files" >&2
+        exit 1
+    fi
+
+    CLASSPATH=`dirname $0`/../../../build/classes:`dirname $0`/../build/classes
+    for jar in `dirname $0`/../../../lib/*.jar; do
+        CLASSPATH=$CLASSPATH:$jar
+    done
+fi
+
+if [ -x $JAVA_HOME/bin/java ]; then
+    JAVA=$JAVA_HOME/bin/java
+else
+    JAVA=`which java`
+fi
+
+if [ "x$JAVA" = "x" ]; then
+    echo "Java executable not found (hint: set JAVA_HOME)" >&2
+    exit 1
+fi
+
+if [ "x$1" != "x" ]; then
+    HOST=$1
+else
+    echo "Usage: $0 <host> [port]" >&2
+    exit 1
+fi
+
+if [ "x$2" != "x" ]; then
+    PORT=$2
+else
+    PORT=$DEFAULT_PORT
+fi
+
+$JAVA -cp $CLASSPATH org.apache.cassandra.contrib.circuit.CircuitFrame $HOST $PORT

Propchange: incubator/cassandra/trunk/contrib/circuit/bin/circuit
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/cassandra/trunk/contrib/circuit/build.xml
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/circuit/build.xml?rev=828238&view=auto
==============================================================================
--- incubator/cassandra/trunk/contrib/circuit/build.xml (added)
+++ incubator/cassandra/trunk/contrib/circuit/build.xml Wed Oct 21 22:22:26 2009
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project basedir="." default="build" name="circuit">
+    <property name="cassandra.dir" value="../.." />
+    <property name="cassandra.lib" value="${cassandra.dir}/lib" />
+    <property name="cassandra.classes" value="${cassandra.dir}/build/classes" />
+    <property name="build.src" value="${basedir}/src" />
+    <property name="build.out" value="${basedir}/build" />
+    <property name="build.classes" value="${build.out}/classes" />
+    <property name="final.name" value="circuit" />
+
+    <target name="init">
+        <mkdir dir="${build.classes}" />
+    </target>
+
+    <target depends="init" name="build">
+        <javac destdir="${build.classes}">
+            <src path="${build.src}" />
+            <classpath>
+                <path>
+                    <fileset dir="${cassandra.lib}">
+                        <include name="**/*.jar" />
+                    </fileset>
+                    <pathelement location="${cassandra.classes}" />
+                </path>
+            </classpath>
+        </javac>
+        <copy todir="${build.classes}">
+            <fileset dir="resources/java" />
+        </copy>
+    </target>
+
+    <target name="jar" depends="build">
+        <mkdir dir="${build.classes}/META-INF" />
+        <jar jarfile="${build.out}/${final.name}.jar"
+                basedir="${build.classes}" />
+    </target>
+
+    <target name="clean">
+        <delete dir="${build.out}" />
+    </target>
+</project>

Propchange: incubator/cassandra/trunk/contrib/circuit/build.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_blue30x30.png
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_blue30x30.png?rev=828238&view=auto
==============================================================================
Files incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_blue30x30.png (added) and incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_blue30x30.png Wed Oct 21 22:22:26 2009 differ

Added: incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_green30x30.png
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_green30x30.png?rev=828238&view=auto
==============================================================================
Files incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_green30x30.png (added) and incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_green30x30.png Wed Oct 21 22:22:26 2009 differ

Added: incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_red30x30.png
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_red30x30.png?rev=828238&view=auto
==============================================================================
Files incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_red30x30.png (added) and incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_red30x30.png Wed Oct 21 22:22:26 2009 differ

Added: incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_yellow30x30.png
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_yellow30x30.png?rev=828238&view=auto
==============================================================================
Files incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_yellow30x30.png (added) and incubator/cassandra/trunk/contrib/circuit/resources/java/images/node_yellow30x30.png Wed Oct 21 22:22:26 2009 differ

Added: incubator/cassandra/trunk/contrib/circuit/resources/node.xcf
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/circuit/resources/node.xcf?rev=828238&view=auto
==============================================================================
Files incubator/cassandra/trunk/contrib/circuit/resources/node.xcf (added) and incubator/cassandra/trunk/contrib/circuit/resources/node.xcf Wed Oct 21 22:22:26 2009 differ

Added: incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/AboutDialog.java
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/AboutDialog.java?rev=828238&view=auto
==============================================================================
--- incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/AboutDialog.java (added)
+++ incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/AboutDialog.java Wed Oct 21 22:22:26 2009
@@ -0,0 +1,82 @@
+/**
+ * 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.contrib.circuit;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTextPane;
+
+public class AboutDialog extends JDialog
+{
+    private static final long serialVersionUID = 1L;
+
+    public AboutDialog(JFrame parent)
+    {
+        super(parent, "About " + parent.getTitle(), true);
+
+        Box vbox = Box.createVerticalBox();
+        vbox.add(Box.createGlue());
+        JTextPane textPane = new JTextPane();
+        textPane.setContentType("text/html");
+        textPane.setEditable(false);
+        textPane.setText(getHtmlMarkup());
+        vbox.add(textPane);
+        vbox.add(Box.createGlue());
+        getContentPane().add(vbox, "Center");
+
+        JPanel bottomPanel = new JPanel();
+        JButton closeButton = new JButton("Close");
+        bottomPanel.add(closeButton);
+        getContentPane().add(bottomPanel, "South");
+
+        closeButton.addActionListener(new ActionListener()
+        {
+          public void actionPerformed(ActionEvent evt)
+          {
+            setVisible(false);
+          }
+        });
+
+        setSize(350, 220);
+        setResizable(false);
+        setModalityType(ModalityType.APPLICATION_MODAL);
+        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+        setLocationRelativeTo(parent);
+    }
+    
+    public static String getHtmlMarkup()
+    {
+        return String.format("<html><body bgcolor=#aaaab9><center><br>" +
+                "<font size=+2><b>Circuit</b></font><br><br>" +
+                "Visualization and diagnostics for Cassandra clusters.<br><br>" +
+                "<font size=-2 color=#333355>&copy; 2009 The Apache Software Foundation</font>" +
+                "</center></body></html>");
+    }
+
+    public static void main(String[] args)
+    {
+        JDialog f = new AboutDialog(new JFrame("Phony"));
+        f.setVisible(true);
+    }
+}

Propchange: incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/AboutDialog.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/CircuitFrame.java
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/CircuitFrame.java?rev=828238&view=auto
==============================================================================
--- incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/CircuitFrame.java (added)
+++ incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/CircuitFrame.java Wed Oct 21 22:22:26 2009
@@ -0,0 +1,251 @@
+/**
+ * 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.contrib.circuit;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Event;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTextArea;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import com.google.common.collect.Sets;
+
+public class CircuitFrame extends JFrame implements ActionListener
+{
+    private static final long serialVersionUID = 1L;
+    private static final String appTitle = "CircuitFrame";
+    private static final Dimension defaultSize = new Dimension(550, 600);
+    private static final SimpleDateFormat dateFormatter;
+    private static final Lock verifyLock = new ReentrantLock();
+    
+    private RingModel ringModel;
+    private RingPanel ringPanel;
+    private JTextArea statusOutput;
+    
+    private JMenuBar menuBar;
+    private JMenuItem quitMI, verifyMI, aboutMI;
+    
+    static
+    {
+        dateFormatter = new SimpleDateFormat("HH:mm:ss");
+    }
+    
+    public CircuitFrame(String hostname, int port)
+    {
+        super(appTitle);
+        setSize(defaultSize);
+        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        
+        // The menu bar w/ items.
+        menuBar = new JMenuBar();
+        setJMenuBar(menuBar);
+        
+        JMenu fileMenu = new JMenu("File");
+        fileMenu.setMnemonic(KeyEvent.VK_F);
+        menuBar.add(fileMenu);
+        
+        quitMI = new JMenuItem("Quit");
+        quitMI.setMnemonic(KeyEvent.VK_Q);
+        quitMI.setAccelerator(
+                KeyStroke.getKeyStroke(KeyEvent.VK_Q, Event.CTRL_MASK));
+        quitMI.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) { System.exit(0); }
+        });
+        fileMenu.add(quitMI);
+        
+        JMenu toolsMenu = new JMenu("Tools");
+        toolsMenu.setMnemonic(KeyEvent.VK_T);
+        menuBar.add(toolsMenu);
+        
+        verifyMI = new JMenuItem("Verify Ring");
+        verifyMI.addActionListener(this);
+        toolsMenu.add(verifyMI);
+        
+        JMenu helpMenu = new JMenu("Help");
+        helpMenu.setMnemonic(KeyEvent.VK_H);
+        menuBar.add(helpMenu);
+        
+        aboutMI = new JMenuItem("About");
+        aboutMI.setMnemonic(KeyEvent.VK_A);
+        aboutMI.addActionListener(this);
+        helpMenu.add(aboutMI);
+        
+        // FIXME: a progress dialog should be up while instantiating RingPanel
+        ringModel = new RingModel(hostname, port);
+        ringPanel = new RingPanel(ringModel);
+
+        statusOutput = new JTextArea();
+        statusOutput.setEditable(false);
+        Component logPanel = new JScrollPane(statusOutput);
+        
+        JSplitPane contentPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, ringPanel, logPanel);
+        setContentPane(contentPane);
+
+        // Order matters here...
+        ringPanel.setPreferredSize(getSize());
+        setVisible(true);
+        contentPane.setDividerLocation(0.8);
+    }
+
+    public void actionPerformed(ActionEvent e)
+    {
+        Object src = e.getSource();
+        
+        if (src == verifyMI)
+        {
+            verifyRing();
+        }
+        else if(src == aboutMI)
+        {
+            new AboutDialog(this).setVisible(true);
+        }
+    }
+
+    /**
+     * For each node, retrieve that nodes list and compare it to ours. If the
+     * list of remote nodes doesn't match (it's long or short), then the node is
+     * flagged accordingly and an error message is written to the status display.
+     */
+    private void verifyRing()
+    {
+        new Thread("VERIFY-RING")
+        {
+            public void run()
+            {
+                verifyLock.lock();
+                ringPanel.setVerifying(true);
+                try {
+                    writeStatusOutput("Beginning ring verification...");
+                    for (Node node : ringModel.getNodes())
+                    {
+                        // Skip the node we already queried at startup
+                        if (node.isSeed())
+                            continue;
+
+                        writeStatusOutput("Verifying %s (ring) against reference node", node);
+                        node.setSelected(true);
+                        
+                        SwingUtilities.invokeLater(new Runnable() {
+                            public void run() {
+                                ringPanel.repaint();
+                            }
+                        });
+
+                        // uncomment this to simulate a slow running verification process
+//                        try {Thread.currentThread().sleep(2000L); } catch (Exception ex) { }
+
+                        Set<Node> othersSet, nodesSet;
+                        try {
+                            othersSet = new HashSet<Node>(ringModel.getRemoteNodes(node.getHost()));
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                            writeStatusOutput("Error retrieving node list from %s", node.getHost());
+                            continue;
+                        }
+
+                        nodesSet = new HashSet<Node>(ringModel.getNodes());
+
+                        for (Node upShort : Sets.difference(nodesSet, othersSet))
+                        {
+                            node.setStatus(NodeStatus.SHORT);
+                            writeStatusOutput("%s: missing node %s", node, upShort);
+                        }
+
+                        for (Node upLong : Sets.difference(othersSet, nodesSet))
+                        {
+                            node.setStatus(NodeStatus.LONG);
+                            writeStatusOutput("%s: contains node %s missing from reference list", node, upLong);
+                        }
+
+                        node.setSelected(false);
+                    }
+
+                    SwingUtilities.invokeLater(new Runnable() {
+                        public void run() {
+                            ringPanel.repaint();
+                        }
+                    });
+                    writeStatusOutput("Ring verification complete.");
+                } finally
+                {
+                    verifyLock.unlock();
+                    ringPanel.setVerifying(false);
+                }
+            }
+        }.start();
+    }
+    
+    // TODO: use StatusLevel to distinguish message priorities.
+    private void writeStatusOutput(String msg, StatusLevel level, Object...args)
+    {
+        String pref = String.format("[%s] ", dateFormatter.format(new Date()));
+        statusOutput.append(String.format(pref + msg + "\n", args));
+        statusOutput.setCaretPosition(statusOutput.getDocument().getLength());
+    }
+    
+    private void writeStatusOutput(String msg, Object...args)
+    {
+        writeStatusOutput(msg, StatusLevel.INFO, args);
+    }
+    
+    public static void main(final String[] args) throws IOException
+    {
+        if (args.length != 2)
+        {
+            System.err.println("Usage: java " + CircuitFrame.class.getName() + " <host> <port>");
+            System.exit(1);
+        }
+        try
+        {
+            SwingUtilities.invokeAndWait(new Runnable() { public void run() {
+                CircuitFrame app = new CircuitFrame(args[0], Integer.parseInt(args[1]));
+                app.setVisible(true);
+            }});
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex);
+        }
+    }
+}
+
+enum StatusLevel
+{
+    INFO,
+    WARN,
+    ERROR,
+    CRITICAL,
+    DEBUG,
+}

Propchange: incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/CircuitFrame.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingModel.java
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingModel.java?rev=828238&view=auto
==============================================================================
--- incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingModel.java (added)
+++ incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingModel.java Wed Oct 21 22:22:26 2009
@@ -0,0 +1,249 @@
+/**
+ * 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.contrib.circuit;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.management.JMX;
+import javax.management.MBeanServerConnection;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+import org.apache.cassandra.dht.Range;
+import org.apache.cassandra.service.StorageServiceMBean;
+
+/**
+ * This class provides data abstraction for the JMX instrumentation
+ * of Cassandra nodes. 
+ */
+public class RingModel
+{
+    public static final int defaultPort = 8080;
+    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 String seedName;
+    private String seedAddr;
+    private int seedPort;
+    private List<Node> nodes;
+    
+    /**
+     * Constructs a RingModel using the named reference host and port number.
+     * 
+     * @param seedName the hostname or IP of the startup node
+     * @param seedPort JMX port number
+     * @throws IOException if unable to setup the JMX connection
+     */
+    public RingModel(String seedName, int seedPort)
+    {
+        this.seedName = seedName;
+        this.seedPort = seedPort;
+        
+        try
+        {
+            seedAddr = InetAddress.getByName(seedName).getHostAddress();
+        }
+        catch (UnknownHostException e)
+        {
+            System.err.println("Error unknown host: " + seedName);
+            seedAddr = seedName;
+        }
+    }
+    
+    /**
+     * Constructs a RingModel using the named reference host .
+     * 
+     * @param seedName the hostname or IP of the startup node
+     */
+    public RingModel(String seedName) throws IOException
+    {
+        this(seedName, defaultPort);
+    }
+
+    // @throws IOException if unable to setup the JMX connection
+    private static List<Node> retrieveRingData(String seedAddress, String remoteHost, int port) throws IOException
+    {
+        JMXServiceURL jmxUrl = new JMXServiceURL(String.format(fmtUrl, remoteHost, port));
+        JMXConnector jmxc = JMXConnectorFactory.connect(jmxUrl, null);
+        StorageServiceMBean ssProxy;
+        MBeanServerConnection mbeanServerConn = jmxc.getMBeanServerConnection();
+
+        try
+        {
+            ObjectName name = new ObjectName(ssObjName);
+            ssProxy = JMX.newMBeanProxy(mbeanServerConn, name, StorageServiceMBean.class);
+        } catch (MalformedObjectNameException e)
+        {
+            throw new RuntimeException(
+                    "Invalid ObjectName? Please report this as a bug.", e);
+        }
+
+        Map<Range, List<String>> rangeMap = ssProxy.getRangeToEndPointMap();
+        List<Range> ranges = new ArrayList<Range>(rangeMap.keySet());
+        Collections.sort(ranges);
+        
+        List<Node> nodes = new ArrayList<Node>();
+        
+        for (Range r : ranges)
+        {
+            String host = rangeMap.get(r).get(0);
+            
+            NodeStatus status;
+            if (host.equals(seedAddress))
+                status = NodeStatus.ISSEED;
+            else
+                status = NodeStatus.OK;
+            
+            String token = r.left().toString();
+            nodes.add(new Node(host, status, token));
+        }
+        
+        return nodes;
+    }
+    
+    private List<Node> retrieveRingData(String remoteHost) throws IOException
+    {
+        return retrieveRingData(seedAddr, remoteHost, seedPort);
+    }
+    
+    /**
+     * Retrieves the nodes that are known to the reference host.
+     * @return the list of nodes seen by the reference host.
+     */
+    public List<Node> getNodes()
+    {
+        if (this.nodes == null)
+        {
+            List<Node> nodes = new ArrayList<Node>();
+            
+            try
+            {
+                nodes = retrieveRingData(seedAddr, seedName, seedPort);
+            }
+            catch (IOException ex)
+            {
+                ex.printStackTrace();
+                nodes.add(new Node(seedName, NodeStatus.UNKNOWN, null));
+            }
+            
+            this.nodes = nodes;
+        }
+        return nodes;
+    }
+    
+    /**
+     * Query the specified host for a list of nodes.
+     * 
+     * @param remoteHost the hostname or IP address of a node
+     * @return the list of nodes seen by the specified host
+     * @throws IOException if an error is encountered communicating with the node
+     */
+    public List<Node> getRemoteNodes(String remoteHost) throws IOException
+    {
+        return retrieveRingData(remoteHost);
+    }
+}
+
+/**
+ * Represents a node in the cluster.
+ */
+class Node
+{
+    public String host;
+    public volatile NodeStatus nodeStatus;
+    public String startToken;
+    public volatile boolean isSelected;
+
+    public Node(String host, NodeStatus status, String startToken)
+    {
+        this.host = host;
+        this.nodeStatus = status;
+        this.startToken = startToken;
+    }
+    
+    public String getStartToken()
+    {
+        return startToken;
+    }
+
+    public String getHost()
+    {
+        return host;
+    }
+
+    public NodeStatus getStatus()
+    {
+        return nodeStatus;
+    }
+    
+    public String toString()
+    {
+        return host;
+    }
+    
+    public boolean isSelected()
+    {
+        return isSelected;
+    }
+
+    public void setSelected(boolean isSelected)
+    {
+        this.isSelected = isSelected;
+    }
+    
+    public boolean isSeed()
+    {
+        return nodeStatus == NodeStatus.ISSEED ? true : false;
+    }
+    
+    public void setStatus(NodeStatus status)
+    {
+        nodeStatus = status;
+    }
+    
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof Node))
+            return false;
+        
+        Node other = (Node)o;
+        return other.getHost().equals(host);
+    }
+    
+    public int hashCode()
+    {
+        return (startToken + host).hashCode();
+    }
+}
+
+enum NodeStatus
+{
+    OK,
+    ISSEED,
+    SHORT,
+    LONG,
+    UNKNOWN,
+}

Propchange: incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingModel.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingPanel.java
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingPanel.java?rev=828238&view=auto
==============================================================================
--- incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingPanel.java (added)
+++ incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingPanel.java Wed Oct 21 22:22:26 2009
@@ -0,0 +1,168 @@
+/**
+ * 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.contrib.circuit;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.RenderingHints;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.RoundRectangle2D;
+import javax.swing.*;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.List;
+
+public class RingPanel extends JPanel
+{
+    private static final long serialVersionUID = 1L;
+    private static final Color bgColor = Color.white;
+    private static final String nodeImageFileOk = "images/node_green30x30.png";
+    private static final String nodeImageFileSeed = "images/node_blue30x30.png";
+    private static final String nodeImageFileShort = "images/node_red30x30.png";
+    private static final String nodeImageFileUnknown = "images/node_yellow30x30.png";
+    private static final Color ringColor = Color.blue;
+    private static final Color fontColor = Color.black;
+    private static final String fontName = "Helvetica";
+    private static final int fontSize = 12;
+    private static final int padding = 70;
+    private static final int nodeDiameter = 30;
+    private static final char[] verificationMessage = "Verifying ring...".toCharArray();
+    
+    private static RenderingHints defaultHints;
+    private static Image nodeImageOk, nodeImageSeed, nodeImageShort, nodeImageUnknown;
+    private RingModel ringModel;
+    private boolean isVerifying = false;
+    
+    static
+    {
+        defaultHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_ON);
+        defaultHints.put(RenderingHints.KEY_RENDERING,
+                RenderingHints.VALUE_RENDER_QUALITY);
+    }
+    
+    public RingPanel(RingModel ringModel)
+    {
+        setBackground(bgColor);
+        setFont(new Font(fontName, Font.PLAIN, fontSize));
+        this.ringModel = ringModel;
+
+        nodeImageOk = new ImageIcon(getClass().getClassLoader().getResource(nodeImageFileOk)).getImage();
+        nodeImageSeed = new ImageIcon(getClass().getClassLoader().getResource(nodeImageFileSeed)).getImage();
+        nodeImageShort = new ImageIcon(getClass().getClassLoader().getResource(nodeImageFileShort)).getImage();
+        nodeImageUnknown = new ImageIcon(getClass().getClassLoader().getResource(nodeImageFileUnknown)).getImage();
+    }
+
+    public void paintComponent(Graphics g)
+    {
+        clear(g);
+        Graphics2D g2d = (Graphics2D)g;
+        g2d.setRenderingHints(defaultHints);
+
+        // Draw the ring
+        g2d.setPaint(ringColor);
+        g2d.setStroke(new BasicStroke(1));
+        int dia = getWidth() > getHeight() ? getHeight() - padding : getWidth() - padding;
+        int radius = dia / 2;
+        double ringX = (getWidth() / 2) - radius;
+        double ringY = (getHeight() / 2) - radius;
+        Ellipse2D.Double ring = new Ellipse2D.Double(ringX, ringY, dia, dia);
+        g2d.draw(ring);
+
+        // Obtain the ring according to our seed
+        List<Node> nodes = ringModel.getNodes();
+        
+        // Place the nodes around the ring
+        double current = 0;
+        double increment = (2 * Math.PI) / nodes.size();
+        
+        // Draw each node
+        for (Node node : nodes)
+        {
+            Image nodeImage = null;
+            switch (node.nodeStatus)
+            {
+                case ISSEED:
+                    nodeImage = nodeImageSeed;
+                    break;
+                case OK:
+                    nodeImage = nodeImageOk;
+                    break;
+                case SHORT: case LONG:
+                    nodeImage = nodeImageShort;
+                    break;
+                case UNKNOWN:
+                    nodeImage = nodeImageUnknown;
+                    break;
+            }
+            
+            double x = Math.cos(current) * radius + (ring.getCenterX() - (nodeDiameter / 2));
+            double y = Math.sin(current) * radius + (ring.getCenterY() - (nodeDiameter / 2));
+            current = current + increment;
+            
+            g2d.drawImage(nodeImage, (int)x, (int)y, null);
+            
+            // Draw a square with rounded corners around the node to indicate
+            // when it is "selected", or active.
+            if (node.isSelected())
+            {
+                g2d.setPaint(ringColor);
+                RoundRectangle2D.Double outline = new RoundRectangle2D.Double(
+                        (x - 2), (y - 2), (nodeDiameter + 4), (nodeDiameter + 4), 10, 10);
+                g2d.draw(outline);
+            }
+            
+            // Write the label;
+            g2d.setPaint(fontColor);
+            g2d.drawString(node.getHost(), (int)x - (nodeDiameter / 2), (int)y - 5);
+        }
+
+        // if we're in the middle of verifying, draw something to indicate we are doing that.
+        if (isVerifying)
+        {
+            int msgWidth = g2d.getFontMetrics().charsWidth(verificationMessage, 0, verificationMessage.length);
+            g2d.setColor(new Color(128, 128, 128, 128));
+            g2d.fillRect(0, 0, getWidth(), getHeight());
+            g2d.setColor(fontColor);
+            g2d.drawChars(verificationMessage, 0, verificationMessage.length, getWidth()/2 - msgWidth/2, getHeight()/2);
+        }
+    }
+
+    public void setVerifying(boolean b)
+    {
+        if (b != isVerifying) {
+            isVerifying = b;
+            SwingUtilities.invokeLater(new Runnable() {public void run() {
+                repaint();
+            }});
+        }
+    }
+
+    // super.paintComponent clears offscreen pixmap,
+    // since we're using double buffering by default.
+    protected void clear(Graphics g)
+    {
+        super.paintComponent(g);
+    }
+}

Propchange: incubator/cassandra/trunk/contrib/circuit/src/org/apache/cassandra/contrib/circuit/RingPanel.java
------------------------------------------------------------------------------
    svn:eol-style = native