You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by st...@apache.org on 2012/04/20 13:34:43 UTC

svn commit: r1328330 - in /lucene/dev/trunk/solr/webapp/web: css/styles/cloud.css img/ico/asterisk.png js/scripts/cloud.js tpl/cloud.html

Author: steffkes
Date: Fri Apr 20 11:34:43 2012
New Revision: 1328330

URL: http://svn.apache.org/viewvc?rev=1328330&view=rev
Log:
SOLR-3174: Visualize Cluster State

Added:
    lucene/dev/trunk/solr/webapp/web/img/ico/asterisk.png   (with props)
Modified:
    lucene/dev/trunk/solr/webapp/web/css/styles/cloud.css
    lucene/dev/trunk/solr/webapp/web/js/scripts/cloud.js
    lucene/dev/trunk/solr/webapp/web/tpl/cloud.html

Modified: lucene/dev/trunk/solr/webapp/web/css/styles/cloud.css
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/webapp/web/css/styles/cloud.css?rev=1328330&r1=1328329&r2=1328330&view=diff
==============================================================================
--- lucene/dev/trunk/solr/webapp/web/css/styles/cloud.css (original)
+++ lucene/dev/trunk/solr/webapp/web/css/styles/cloud.css Fri Apr 20 11:34:43 2012
@@ -5,11 +5,12 @@
 
 #content #cloud #navigation
 {
-  width: 10%;
+  width: 11%;
 }
 
 #content #cloud #navigation .tree a { background-image: url( ../../img/ico/folder-tree.png ); }
 #content #cloud #navigation .graph a { background-image: url( ../../img/ico/molecule.png ); }
+#content #cloud #navigation .rgraph a { background-image: url( ../../img/ico/asterisk.png ); }
 #content #cloud #navigation .dump a { background-image: url( ../../img/ico/download-cloud.png ); }
 
 #content #cloud #navigation .dump
@@ -25,7 +26,7 @@
 #content #cloud #frame
 {
   float: right;
-  width: 89%;
+  width: 88%;
 }
 
 #content #cloud #frame .content
@@ -249,17 +250,98 @@
   color: #00f;
 }
 
+#content #graph-content .node
+{
+  fill: #333;
+}
 
 #content #graph-content .node circle
 {
-  fill: #f0f0f0;
+  fill: #fff;
   stroke: #c0c0c0;
   stroke-width: 1.5px;
 }
 
+#content #graph-content .node.lvl-2 text
+{
+  cursor: pointer;
+}
+
+#content #graph-content .node.lvl-2:hover circle
+{
+  stroke: #000 !important;
+}
+
+#content #graph-content .node.lvl-2:hover text
+{
+  fill: #000 !important;
+}
+
 #content #graph-content .link
 {
   fill: none;
-  stroke: #c0c0c0;
+  stroke: #e0e0e0;
   stroke-width: 1.5px;
+}
+
+#content #graph-content .node.gone circle,
+#content #graph-content .link.gone
+{
+  stroke: #f0f0f0;
+}
+
+#content #graph-content .node.gone text
+{
+  fill: #f0f0f0;
+}
+
+#content #graph-content .node.recovery_failed circle
+{
+  stroke: #C43C35;
+}
+
+#content #graph-content .node.recovery_failed text
+{
+  fill: #C43C35;
+}
+
+#content #graph-content .node.down circle
+{
+  stroke: #c48f00;
+}
+
+#content #graph-content .node.down text
+{
+  fill: #c48f00;
+}
+
+#content #graph-content .node.recovering circle
+{
+  stroke: #d5dd00;
+}
+
+#content #graph-content .node.recovering text
+{
+  fill: #d5dd00;
+}
+
+#content #graph-content .node.active circle
+{
+  stroke: #57A957;
+}
+
+#content #graph-content .node.active text
+{
+  fill: #57A957;
+}
+
+#content #graph-content .node.leader circle
+{
+  fill: #000;
+}
+
+#content #graph-content .link.lvl-1,
+#content #graph-content .link.leader
+{
+  stroke: #c0c0c0;
 }
\ No newline at end of file

Added: lucene/dev/trunk/solr/webapp/web/img/ico/asterisk.png
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/webapp/web/img/ico/asterisk.png?rev=1328330&view=auto
==============================================================================
Binary file - no diff available.

Modified: lucene/dev/trunk/solr/webapp/web/js/scripts/cloud.js
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/webapp/web/js/scripts/cloud.js?rev=1328330&r1=1328329&r2=1328330&view=diff
==============================================================================
--- lucene/dev/trunk/solr/webapp/web/js/scripts/cloud.js (original)
+++ lucene/dev/trunk/solr/webapp/web/js/scripts/cloud.js Fri Apr 20 11:34:43 2012
@@ -153,152 +153,300 @@ var init_debug = function( cloud_element
     );
 };
 
-var generate_graph = function( graph_element, graph_data )
+var helper_path_class = function( p )
 {
-  var w = 900,
-      h = 300;
+  var classes = [ 'link' ];
+  classes.push( 'lvl-' + p.target.depth );
+
+  if( p.target.data.leader )
+  {
+    classes.push( 'leader' );
+  }
+
+  if( p.target.data.state )
+  {
+    classes.push( p.target.data.state );
+  }
+
+  return classes.join( ' ' );
+};
+
+var helper_node_class = function( d )
+{
+  var classes = [ 'node' ];
+  classes.push( 'lvl-' + d.depth );
+
+  if( d.data.leader )
+  {
+    classes.push( 'leader' );
+  }
+
+  if( d.data.state )
+  {
+    classes.push( d.data.state );
+  }
+
+  return classes.join( ' ' );
+};
+
+var helper_data = {
+  protocol: [],
+  host: [],
+  hostname: [],
+  port: [],
+  pathname: []
+};
+
+var helper_node_text = function( d )
+{
+  if( !d.data.uri )
+  {
+    return d.name;
+  }
+
+  var name = d.data.uri.hostname;
+
+  if( 1 !== helper_data.protocol.length )
+  {
+    name = d.data.uri.protocol + '//' + name;
+  }
+
+  if( 1 !== helper_data.port.length )
+  {
+    name += ':' + d.data.uri.port;
+  }
+
+  if( 1 !== helper_data.pathname.length )
+  {
+    name += d.data.uri.pathname;
+  }
+
+  return name;
+};
+
+var generate_graph = function( graph_element, graph_data, leaf_count )
+{
+  var w = graph_element.width(),
+      h = leaf_count * 20;
 
   var tree = d3.layout.tree()
-      .size([h, w - 400]);
+    .size([h, w - 400]);
 
   var diagonal = d3.svg.diagonal()
-      .projection(function(d) { return [d.y, d.x]; });
+    .projection(function(d) { return [d.y, d.x]; });
 
-  var vis = d3.select("#canvas").append("svg")
-      .attr("width", w)
-      .attr("height", h)
-    .append("g")
-      .attr("transform", "translate(100, 0)");
-
-  var nodes = tree.nodes(graph_data);
-
-  var link = vis.selectAll("path.link")
-      .data(tree.links(nodes))
-    .enter().append("path")
-      .attr("class", "link")
-      .attr("d", diagonal);
-
-  var node = vis.selectAll("g.node")
-      .data(nodes)
-    .enter().append("g")
-      .attr("class", "node")
-      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
-
-  node.append("circle")
-      .attr("r", 4.5);
-
-  node.append("text")
-      .attr("dx", function(d) { return d.children ? -8 : 8; })
-      .attr("dy", 3)
-      .attr("text-anchor", function(d) { return d.children ? "end" : "start"; })
-      .text(function(d) { return d.name; });
+  var vis = d3.select( '#canvas' ).append( 'svg' )
+    .attr( 'width', w )
+    .attr( 'height', h)
+    .append( 'g' )
+      .attr( 'transform', 'translate(100, 0)' );
+
+  var nodes = tree.nodes( graph_data );
+
+  var link = vis.selectAll( 'path.link' )
+    .data( tree.links( nodes ) )
+    .enter().append( 'path' )
+      .attr( 'class', helper_path_class )
+      .attr( 'd', diagonal );
+
+  var node = vis.selectAll( 'g.node' )
+    .data( nodes )
+    .enter().append( 'g' )
+      .attr( 'class', helper_node_class )
+      .attr( 'transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; } )
+
+  node.append( 'circle' )
+    .attr( 'r', 4.5 );
+
+  node.append( 'text' )
+    .attr( 'dx', function( d ) { return 0 === d.depth ? -8 : 8; } )
+    .attr( 'dy', function( d ) { return 5; } )
+    .attr( 'text-anchor', function( d ) { return 0 === d.depth ? 'end' : 'start'; } )
+    .attr( 'data-href', function( d ) { return d.name; } )
+    .text( helper_node_text );
+
+  $( 'text[data-href*="//"]', graph_element )
+    .die( 'click' )
+    .live
+    (
+      'click',
+      function()
+      {
+        location.href = $( this ).data( 'href' );
+      }
+    );
+};
 
-  /*
-  var r = 860 / 2;
+var generate_rgraph = function( graph_element, graph_data, leaf_count )
+{
+  var max_val = Math.min( graph_element.width(), $( 'body' ).height() )
+  var r = max_val / 2;
 
-  var tree = d3.layout.tree()
-      .size([360, r - 120])
-      .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
+  var cluster = d3.layout.cluster()
+    .size([360, r - 160]);
 
   var diagonal = d3.svg.diagonal.radial()
-      .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });
-
-  var vis = d3.select("#canvas").append("svg")
-      .attr("width", r * 2)
-      .attr("height", r * 2 - 150)
-    .append("g")
-      .attr("transform", "translate(" + r + "," + r + ")");
-
-  var nodes = tree.nodes(graph_data);
-
-  var link = vis.selectAll("path.link")
-      .data(tree.links(nodes))
-    .enter().append("path")
-      .attr("class", "link")
-      .attr("d", diagonal);
-
-  var node = vis.selectAll("g.node")
-      .data(nodes)
-    .enter().append("g")
-      .attr("class", "node")
-      .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
-
-  node.append("circle")
-      .attr("r", 4.5);
-
-  node.append("text")
-      .attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
-      .attr("dy", ".31em")
-      .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
-      .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
-      .text(function(d) { return d.name; });
-  //*/
+    .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });
 
+  var vis = d3.select( '#canvas' ).append( 'svg' )
+    .attr( 'width', r * 2 )
+    .attr( 'height', r * 2 )
+    .append( 'g' )
+      .attr( 'transform', 'translate(' + r + ',' + r + ')' );
+
+  var nodes = cluster.nodes( graph_data );
+
+  var link = vis.selectAll( 'path.link' )
+    .data( cluster.links( nodes ) )
+    .enter().append( 'path' )
+      .attr( 'class', helper_path_class )
+      .attr( 'd', diagonal );
+
+  var node = vis.selectAll( 'g.node' )
+    .data( nodes )
+    .enter().append( 'g' )
+      .attr( 'class', helper_node_class )
+      .attr( 'transform', function(d) { return 'rotate(' + (d.x - 90) + ')translate(' + d.y + ')'; } )
+
+  node.append( 'circle' )
+    .attr( 'r', 4.5 );
+
+  node.append( 'text' )
+    .attr( 'dx', function(d) { return d.x < 180 ? 8 : -8; } )
+    .attr( 'dy', '.31em' )
+    .attr( 'text-anchor', function(d) { return d.x < 180 ? 'start' : 'end'; } )
+    .attr( 'transform', function(d) { return d.x < 180 ? null : 'rotate(180)'; } )
+    .attr( 'data-href', function( d ) { return d.name; } )
+    .text( helper_node_text );
+
+  $( 'text[data-href*="//"]', graph_element )
+    .die( 'click' )
+    .live
+    (
+      'click',
+      function()
+      {
+        location.href = $( this ).data( 'href' );
+      }
+    );
 };
 
-var init_graph = function( graph_element )
+var prepare_graph = function( graph_element, callback )
 {
   $.ajax
   (
     {
-      url : app.config.solr_path + '/zookeeper?wt=json&detail=true&path=%2Fclusterstate.json',
+      url : app.config.solr_path + '/zookeeper?wt=json&path=%2Flive_nodes',
       dataType : 'json',
-      context : graph_element,
-      beforeSend : function( xhr, settings )
-      {
-        this
-          .show();
-      },
       success : function( response, text_status, xhr )
       {
-        var state = null;
-        eval( 'state = ' + response.znode.data + ';' );
-        
-        var collections = [];
-        for( var c in state )
+        var live_nodes = {};
+        for( var c in response.tree[0].children )
         {
-          var shards = [];
-          for( var s in state[c] )
+          live_nodes[response.tree[0].children[c].data.title] = true;
+        }
+
+        $.ajax
+        (
           {
-            var nodes = [];
-            for( var n in state[c][s] )
+            url : app.config.solr_path + '/zookeeper?wt=json&detail=true&path=%2Fclusterstate.json',
+            dataType : 'json',
+            context : graph_element,
+            beforeSend : function( xhr, settings )
+            {
+              this
+                .show();
+            },
+            success : function( response, text_status, xhr )
             {
-              var node = {
-                id: state[c][s][n].node_name,
-                name: state[c][s][n].base_url,
-                data: {
-                  type : 'node',
-                  state : state[c][s][n].state,
-                  leader : 'true' === state[c][s][n].leader
+              var state = null;
+              eval( 'state = ' + response.znode.data + ';' );
+              
+              var leaf_count = 0;
+              var collections = [];
+              for( var c in state )
+              {
+                var shards = [];
+                for( var s in state[c] )
+                {
+                  var nodes = [];
+                  for( var n in state[c][s] )
+                  {
+                    leaf_count++;
+
+                    var uri = state[c][s][n].base_url;
+                    var parts = uri.match( /^(\w+:)\/\/(([\w\d\.-]+)(:(\d+))?)(.+)$/ );
+                    var uri_parts = {
+                      protocol: parts[1],
+                      host: parts[2],
+                      hostname: parts[3],
+                      port: parseInt( parts[5] || 80, 10 ),
+                      pathname: parts[6]
+                    };
+                    
+                    helper_data.protocol.push( uri_parts.protocol );
+                    helper_data.host.push( uri_parts.host );
+                    helper_data.hostname.push( uri_parts.hostname );
+                    helper_data.port.push( uri_parts.port );
+                    helper_data.pathname.push( uri_parts.pathname );
+
+                    var status = state[c][s][n].state;
+
+                    if( !live_nodes[state[c][s][n].node_name] )
+                    {
+                      status = 'gone';
+                    }
+
+                    var node = {
+                      name: uri,
+                      data: {
+                        type : 'node',
+                        state : status,
+                        leader : 'true' === state[c][s][n].leader,
+                        uri : uri_parts
+                      }
+                    };
+                    nodes.push( node );
+                  }
+
+                  var shard = {
+                    name: s,
+                    data: {
+                      type : 'shard',
+                    },
+                    children: nodes
+                  };
+                  shards.push( shard );
                 }
-              };
-              nodes.push( node );
-            }
 
-            var shard = {
-              id: s,
-              name: s,
-              data: {
-                type : 'shard',
-              },
-              children: nodes
-            };
-            shards.push( shard );
-          }
+                var collection = {
+                  name: c,
+                  data: {
+                    type : 'collection',
+                  },
+                  children: shards
+                };
+                collections.push( collection );
+              }
 
-          var collection = {
-            id: c,
-            name: c,
-            data: {
-              type : 'collection',
-            },
-            children: shards
-          };
-          collections.push( collection );
-        }
+              var graph_data = collections.shift();
+              
+              helper_data.protocol = $.unique( helper_data.protocol );
+              helper_data.host = $.unique( helper_data.host );
+              helper_data.hostname = $.unique( helper_data.hostname );
+              helper_data.port = $.unique( helper_data.port );
+              helper_data.pathname = $.unique( helper_data.pathname );
 
-        var graph_data = collections.shift();
-        generate_graph( graph_element, graph_data );
+              callback( graph_element, graph_data, leaf_count );
+            },
+            error : function( xhr, text_status, error_thrown)
+            {
+            },
+            complete : function( xhr, text_status )
+            {
+            }
+          }
+        );
       },
       error : function( xhr, text_status, error_thrown)
       {
@@ -311,6 +459,30 @@ var init_graph = function( graph_element
 
 };
 
+var init_graph = function( graph_element )
+{
+  prepare_graph
+  (
+    graph_element,
+    function( graph_element, graph_data, leaf_count )
+    {
+      generate_graph( graph_element, graph_data, leaf_count );
+    }
+  );
+}
+
+var init_rgraph = function( graph_element )
+{
+  prepare_graph
+  (
+    graph_element,
+    function( graph_element, graph_data, leaf_count )
+    {
+      generate_rgraph( graph_element, graph_data, leaf_count );
+    }
+  );
+}
+
 var init_tree = function( tree_element )
 {
   $.ajax
@@ -531,6 +703,18 @@ sammy.get
             }
           );
 
+        $( '.rgraph', navigation_element )
+          .die( 'activate' )
+          .live
+          (
+            'activate',
+            function( event )
+            {
+              $( this ).addClass( 'current' );
+              init_rgraph( $( '#graph-content', cloud_element ) );
+            }
+          );
+
         $( 'a[href="' + context.path + '"]', navigation_element )
           .trigger( 'activate' );
         

Modified: lucene/dev/trunk/solr/webapp/web/tpl/cloud.html
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/webapp/web/tpl/cloud.html?rev=1328330&r1=1328329&r2=1328330&view=diff
==============================================================================
--- lucene/dev/trunk/solr/webapp/web/tpl/cloud.html (original)
+++ lucene/dev/trunk/solr/webapp/web/tpl/cloud.html Fri Apr 20 11:34:43 2012
@@ -47,6 +47,8 @@ limitations under the License.
 
     <ul>
       <li class="tree"><a href="#/~cloud">Tree</a></li>
+      <li class="graph"><a href="#/~cloud?view=graph">Graph</a></li>
+      <li class="rgraph"><a href="#/~cloud?view=rgraph">Graph (Radial)</a></li>
       <li class="dump"><a href="#/~cloud">Dump</a></li>
     </ul>