You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@storm.apache.org by pt...@apache.org on 2014/06/09 21:48:06 UTC

[2/4] git commit: Merge remote-tracking branch 'community/master' into knusbaum-ui-visualizations

Merge remote-tracking branch 'community/master' into knusbaum-ui-visualizations

Conflicts:
	storm-core/src/clj/backtype/storm/ui/core.clj


Project: http://git-wip-us.apache.org/repos/asf/incubator-storm/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-storm/commit/1e0413e9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-storm/tree/1e0413e9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-storm/diff/1e0413e9

Branch: refs/heads/master
Commit: 1e0413e908300ec12fa5615edb7b704a82601757
Parents: b930e10 6dae731
Author: Kyle Nusbaum <kn...@yahoo-inc.com>
Authored: Wed Jun 4 22:14:53 2014 +0000
Committer: Kyle Nusbaum <kn...@yahoo-inc.com>
Committed: Wed Jun 4 22:14:53 2014 +0000

----------------------------------------------------------------------
 .gitignore                                      |    4 +-
 CHANGELOG.md                                    |    2 +
 README.markdown                                 |    1 +
 .../src/clj/backtype/storm/daemon/executor.clj  |   42 +-
 storm-core/src/clj/backtype/storm/ui/core.clj   | 1153 ++++++------------
 storm-core/src/ui/public/component.html         |   88 ++
 storm-core/src/ui/public/index.html             |   73 ++
 storm-core/src/ui/public/js/jquery.mustache.js  |  592 +++++++++
 storm-core/src/ui/public/js/purl.js             |  267 ++++
 storm-core/src/ui/public/js/script.js           |   51 +-
 storm-core/src/ui/public/js/visualization.js    |   12 +-
 .../templates/component-page-template.html      |  152 +++
 .../public/templates/index-page-template.html   |   62 +
 .../public/templates/json-error-template.html   |    4 +
 .../templates/topology-page-template.html       |  128 ++
 storm-core/src/ui/public/topology.html          |   90 ++
 .../test/clj/backtype/storm/metrics_test.clj    |   24 +
 17 files changed, 1937 insertions(+), 808 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-storm/blob/1e0413e9/storm-core/src/clj/backtype/storm/ui/core.clj
----------------------------------------------------------------------
diff --cc storm-core/src/clj/backtype/storm/ui/core.clj
index e038474,ba6827c..9914dff
--- a/storm-core/src/clj/backtype/storm/ui/core.clj
+++ b/storm-core/src/clj/backtype/storm/ui/core.clj
@@@ -565,94 -272,29 +280,43 @@@
         (map nil-to-zero)
         (apply max)))
  
- (defn spout-stats [summs include-sys?]
++;; Watch These
++(defn spout-streams-stats [summs include-sys?]
 +  (let [stats-seq (get-filled-stats summs)]
 +    (aggregate-spout-streams
 +     (aggregate-spout-stats
 +      stats-seq include-sys?))))
 +
- (defn spout-comp-table [top-id summ-map errors window include-sys?]
-   (sorted-table
-    [{:text "Id" :attr {:class "tip right"
-                        :title (str (:comp-id tips) " " (:comp-id-link tips))}}
-     {:text "Executors" :attr {:class "tip right"
-                        :title (:num-execs tips)}}
-     {:text "Tasks" :attr {:class "tip above"
-                    :title (:num-tasks tips)}}
-     {:text "Emitted" :attr {:class "tip above"
-                      :title (:emitted tips)}}
-     {:text "Transferred" :attr {:class "tip above"
-                          :title (:transferred tips)}}
-     {:text "Complete latency (ms)" :attr {:class "tip above"
-                                    :title (:complete-lat tips)}}
-     {:text "Acked" :attr {:class "tip above"
-                           :title (:spout-acked tips)}}
-     {:text "Failed" :attr {:class "tip above"
-                            :title (:spout-failed tips)}}
-     "Last error"]
-    (for [[id summs] summ-map
-          :let [stats (spout-stats summs include-sys?)]]
-      [(component-link top-id id)
-       (count summs)
-       (sum-tasks summs)
-       (get-in stats [:emitted window])
-       (get-in stats [:transferred window])
-       (float-str (get-in stats [:complete-latencies window]))
-       (get-in stats [:acked window])
-       (get-in stats [:failed window])
-       (most-recent-error (get errors id))
-       ]
-      )))
- 
- (defn bolt-stats [summs include-sys?]
++;; Watch These
++(defn bolt-streams-stats [summs include-sys?]
 +  (let [stats-seq (get-filled-stats summs)]
 +    (aggregate-bolt-streams
 +     (aggregate-bolt-stats
 +      stats-seq include-sys?))))
 +
- (defn bolt-comp-table [top-id summ-map errors window include-sys?]
-   (sorted-table
-    [{:text "Id" :attr {:class "tip right"
-                        :title (str (:comp-id tips) " " (:comp-id-link tips))}}
-     {:text "Executors" :attr {:class "tip right"
-                               :title (:num-execs tips)}}
-     {:text "Tasks" :attr {:class "tip above"
-                           :title (:num-tasks tips)}}
-     {:text "Emitted" :attr {:class "tip above"
-                             :title (:emitted tips)}}
-     {:text "Transferred" :attr {:class "tip above"
-                                 :title (:transferred tips)}}
-     {:text "Capacity (last 10m)" :attr {:class "tip above"
-                                         :title (:capacity tips)}}
-     {:text "Execute latency (ms)" :attr {:class "tip above"
-                                          :title (:exec-lat tips)}}
-     {:text "Executed" :attr {:class "tip above"
-                              :title (:num-executed tips)}}
-     {:text "Process latency (ms)":attr {:class "tip above"
-                                         :title (:proc-lat tips)}}
-     {:text "Acked" :attr {:class "tip above"
-                           :title (:bolt-acked tips)}}
-     {:text "Failed" :attr {:class "tip left"
-                            :title (:bolt-failed tips)}}
-     "Last error"]
-    (for [[id summs] summ-map
-          :let [stats (bolt-stats summs include-sys?)
-                ]]
-      [(component-link top-id id)
-       (count summs)
-       (sum-tasks summs)
-       (get-in stats [:emitted window])
-       (get-in stats [:transferred window])
-       (render-capacity (compute-bolt-capacity summs))
-       (float-str (get-in stats [:execute-latencies window]))
-       (get-in stats [:executed window])
-       (float-str (get-in stats [:process-latencies window]))
-       (get-in stats [:acked window])
-       (get-in stats [:failed window])
-       (most-recent-error (get errors id))
-       ]
+ (defn total-aggregate-stats [spout-summs bolt-summs include-sys?]
+   (let [spout-stats (get-filled-stats spout-summs)
+         bolt-stats (get-filled-stats bolt-summs)
+         agg-spout-stats (-> spout-stats
+                             (aggregate-spout-stats include-sys?)
+                             aggregate-spout-streams)
+         agg-bolt-stats (-> bolt-stats
+                            (aggregate-bolt-stats include-sys?)
+                            aggregate-bolt-streams)]
+     (merge-with
+      (fn [s1 s2]
+        (merge-with + s1 s2))
+      (select-keys agg-bolt-stats [:emitted :transferred :acked :failed :complete-latencies])
+      (select-keys agg-spout-stats [:emitted :transferred :acked :failed :complete-latencies])
       )))
  
+ (defn stats-times [stats-map]
+   (sort-by #(Integer/parseInt %)
+            (-> stats-map
+                clojurify-structure
+                (dissoc ":all-time")
+                keys)))
+ 
  (defn window-hint [window]
    (if (= window ":all-time")
      "All time"
@@@ -667,123 -309,146 +331,237 @@@
                           (StringEscapeUtils/escapeJavaScript name) "', '"
                           command "', " is-wait ", " default-wait ")")}])
  
- 
 +(defn sanitize-stream-name [name]
 +  (let [sym-regex #"(?![A-Za-z_\-:\.])."]
 +    (str
 +     (if (re-find #"^[A-Za-z]" name)
 +       (clojure.string/replace name sym-regex "_")
 +       (clojure.string/replace (str \s name) sym-regex "_"))
 +     (hash name))))
 +
 +(defn sanitize-transferred [transferred]
 +  (into {}
 +        (for [[time, stream-map] transferred]
 +          [time, (into {} 
 +                       (for [[stream, trans] stream-map]
 +                         [(sanitize-stream-name stream), trans]))])))
 +
 +(defn visualization-data [spout-bolt spout-comp-summs bolt-comp-summs window storm-id]
 +  (let [components (for [[id spec] spout-bolt]
 +            [id 
 +             (let [inputs (.get_inputs (.get_common spec))
 +                   bolt-summs (get bolt-comp-summs id)
 +                   spout-summs (get spout-comp-summs id)
 +                   bolt-cap (if bolt-summs
 +                              (compute-bolt-capacity bolt-summs)
 +                              0)]
 +               {
 +                :type                (if bolt-summs
 +                                       "bolt"
 +                                       "spout")
 +
 +                :capacity            bolt-cap
 +
 +                :latency             (if bolt-summs
-                                        (get-in (bolt-stats bolt-summs true) [:process-latencies window])
-                                        (get-in (spout-stats spout-summs true) [:complete-latencies window]))
++                                       (get-in (bolt-streams-stats bolt-summs true) [:process-latencies window])
++                                       (get-in (spout-streams-stats spout-summs true) [:complete-latencies window]))
 +
 +                :transferred         (or
-                                       (get-in (spout-stats spout-summs true) [:transferred window]) 
-                                       (get-in (bolt-stats bolt-summs true) [:transferred window]))
++                                      (get-in (spout-streams-stats spout-summs true) [:transferred window]) 
++                                      (get-in (bolt-streams-stats bolt-summs true) [:transferred window]))
 +                :stats               (let [mapfn (fn [dat]
 +                                                   (map (fn [^ExecutorSummary summ]
 +                                                          {:host (.get_host summ)
 +                                                           :port (.get_port summ)
 +                                                           :uptime_secs (.get_uptime_secs summ)
 +                                                           :transferred (if-let [stats (.get_stats summ)]
 +                                                                          (sanitize-transferred (.get_transferred stats)))})
 +                                                        dat))]
 +                                       (if bolt-summs
 +                                         (mapfn bolt-summs)
 +                                         (mapfn spout-summs)))
 +
 +                :link                (url-format "/topology/%s/component/%s" storm-id id)
 +
 +                :inputs              (for [[global-stream-id group] inputs]
 +                                       {:component (.get_componentId global-stream-id) 
 +                                        :stream (.get_streamId global-stream-id)
 +                                        :sani-stream (sanitize-stream-name (.get_streamId global-stream-id))
 +                                        :grouping (clojure.core/name (thrift/grouping-type group))})})])]
 +    (into {} (doall components))))
 +
- 
 +(defn stream-boxes [datmap]
 +  (let [filter-fn (mk-include-sys-fn true)
 +        streams
 +        (vec (doall (distinct
 +                     (apply concat
 +                            (for [[k v] datmap]
 +                              (for [m (get v :inputs)]
-                                 [(get m :stream) (get m :sani-stream)]))))))
-         boxes
-         (map (fn [[stream sani-stream]] 
-                   (html
-                    [:td
-                     [:input {:type "checkbox" :id sani-stream, :class "stream-box", :checked (is-ack-stream stream)}]
-                     stream]))
-                 streams)]
++                                { :stream (get m :stream) :sani-stream (get m :sani-stream) :checked (is-ack-stream (get m :stream))}))))))]
 +    (map (fn [row] 
-            [:tr row]) (partition 4 4 (repeat 4 "<td></td>") boxes))))
-     
- 
- (defn mk-visualizations [spouts bolts spout-comp-summs bolt-comp-summs window id]
-   (let [mappd (visualization-data 
-                (merge (hashmap-to-persistent spouts) 
-                       (hashmap-to-persistent bolts)) 
-                spout-comp-summs 
-                bolt-comp-summs
-                window
-                id)
-         json-data (to-json  mappd)]
-     (concat
-      [[:input {:type :button :id "show-hide-visualization" :value "Show Visualization"}]]
-      [[:p
-        [:div {:id "visualization-container" :style "display:none;"}
-          (concat
-           [[:p [:table (stream-boxes mappd)]]]
-           [[:canvas {:id "topoGraph" :width 1024 :height 768 :style "border:1px solid #000000;"}]])]]])))
++           {:row row}) (partition 4 4 nil streams))))
 +  
- (defn mk-visualization-json [id window include-sys?]
++(defn mk-visualization-data [id window include-sys?]
 +  (with-nimbus nimbus
 +    (let [window (if window window ":all-time")
 +          topology (.getTopology ^Nimbus$Client nimbus id)
 +          spouts (.get_spouts topology)
 +          bolts (.get_bolts topology)
 +          summ (.getTopologyInfo ^Nimbus$Client nimbus id)
 +          spout-summs (filter (partial spout-summary? topology) (.get_executors summ))
 +          bolt-summs (filter (partial bolt-summary? topology) (.get_executors summ))
 +          spout-comp-summs (group-by-comp spout-summs)
 +          bolt-comp-summs (group-by-comp bolt-summs)
 +          bolt-comp-summs (filter-key (mk-include-sys-fn include-sys?) bolt-comp-summs)
 +          topology-conf (from-json (.getTopologyConf ^Nimbus$Client nimbus id))]
-       (to-json (visualization-data 
-                   (merge (hashmap-to-persistent spouts)
-                          (hashmap-to-persistent bolts))
-                   spout-comp-summs 
-                   bolt-comp-summs 
-                   window 
-                   id)))))
++      (visualization-data 
++       (merge (hashmap-to-persistent spouts)
++              (hashmap-to-persistent bolts))
++       spout-comp-summs 
++       bolt-comp-summs 
++       window 
++       id))))
++
+ (defn cluster-configuration []
+   (with-nimbus nimbus
+     (.getNimbusConf ^Nimbus$Client nimbus)))
+ 
+ (defn cluster-summary
+   ([]
+      (with-nimbus nimbus
+         (cluster-summary (.getClusterInfo ^Nimbus$Client nimbus))))
+   ([^ClusterSummary summ]
+      (let [sups (.get_supervisors summ)
+         used-slots (reduce + (map #(.get_num_used_workers ^SupervisorSummary %) sups))
+         total-slots (reduce + (map #(.get_num_workers ^SupervisorSummary %) sups))
+         free-slots (- total-slots used-slots)
+         total-tasks (->> (.get_topologies summ)
+                          (map #(.get_num_tasks ^TopologySummary %))
+                          (reduce +))
+         total-executors (->> (.get_topologies summ)
+                              (map #(.get_num_executors ^TopologySummary %))
+                              (reduce +))]
+        { "stormVersion" (read-storm-version)
+          "nimbusUptime" (pretty-uptime-sec (.get_nimbus_uptime_secs summ))
+          "supervisors" (count sups)
+          "slotsTotal" total-slots
+          "slotsUsed"  used-slots
+          "slotsFree" free-slots
+          "executorsTotal" total-executors
+          "tasksTotal" total-tasks })))
+ 
+ (defn supervisor-summary
+   ([]
+      (with-nimbus nimbus
+        (supervisor-summary (.get_supervisors (.getClusterInfo ^Nimbus$Client nimbus)))
+        ))
+   ([summs]
+      {"supervisors"
+       (for [^SupervisorSummary s summs]
+             {"id" (.get_supervisor_id s)
+              "host" (.get_host s)
+              "uptime" (pretty-uptime-sec (.get_uptime_secs s))
+              "slotsTotal" (.get_num_workers s)
+              "slotsUsed" (.get_num_used_workers s)})}))
+ 
+ (defn all-topologies-summary
+   ([]
+      (with-nimbus nimbus
+        (all-topologies-summary (.get_topologies (.getClusterInfo ^Nimbus$Client nimbus)))))
+   ([summs]
+      {"topologies"
+       (for [^TopologySummary t summs]
+         {"id" (.get_id t)
+          "name" (.get_name t)
+          "status" (.get_status t)
+          "uptime" (pretty-uptime-sec (.get_uptime_secs t))
+          "tasksTotal" (.get_num_tasks t)
+          "workersTotal" (.get_num_workers t)
+          "executorsTotal" (.get_num_executors t)})
+       }))
+ 
+ (defn topology-stats [id window stats]
+   (let [times (stats-times (:emitted stats))
+         display-map (into {} (for [t times] [t pretty-uptime-sec]))
+         display-map (assoc display-map ":all-time" (fn [_] "All time"))]
+     (for [k (concat times [":all-time"])
+            :let [disp ((display-map k) k)]]
+       { "windowPretty" disp
+         "window" k
+         "emitted" (get-in stats [:emitted k])
+         "transferred" (get-in stats [:transferred k])
+         "completeLatency" (float-str (get-in stats [:complete-latencies k]))
+         "acked" (get-in stats [:acked k])
+         "failed" (get-in stats [:failed k])
+         }
+       )))
+ 
+ (defn spout-comp [top-id summ-map errors window include-sys?]
+   (for [[id summs] summ-map
+         :let [stats-seq (get-filled-stats summs)
+               stats (aggregate-spout-streams
+                      (aggregate-spout-stats
+                       stats-seq include-sys?))]]
+     {"spoutId" id
+      "executors" (count summs)
+      "tasks" (sum-tasks summs)
+      "emitted" (get-in stats [:emitted window])
+      "transferred" (get-in stats [:transferred window])
+      "completeLatency" (float-str (get-in stats [:complete-latencies window]))
+      "acked" (get-in stats [:acked window])
+      "failed" (get-in stats [:failed window])
+      "lastError" (most-recent-error (get errors id))
+      }))
+ 
+ (defn bolt-comp [top-id summ-map errors window include-sys?]
+   (for [[id summs] summ-map
+         :let [stats-seq (get-filled-stats summs)
+               stats (aggregate-bolt-streams
+                      (aggregate-bolt-stats
+                       stats-seq include-sys?))
+               ]]
+     {"boltId" id
+      "executors" (count summs)
+      "tasks" (sum-tasks summs)
+      "emitted" (get-in stats [:emitted window])
+      "transferred" (get-in stats [:transferred window])
+      "capacity" (float-str (nil-to-zero (compute-bolt-capacity summs)))
+      "executeLatency" (float-str (get-in stats [:execute-latencies window]))
+      "executed" (get-in stats [:executed window])
+      "processLatency" (float-str (get-in stats [:process-latencies window]))
+      "acked" (get-in stats [:acked window])
+      "failed" (get-in stats [:failed window])
+      "lastError" (most-recent-error (get errors id))
+      }
+     ))
+ 
+ (defn topology-summary [^TopologyInfo summ]
+   (let [executors (.get_executors summ)
+         workers (set (for [^ExecutorSummary e executors] [(.get_host e) (.get_port e)]))]
+       {"id" (.get_id summ)
+        "name" (.get_name summ)
+        "status" (.get_status summ)
+        "uptime" (pretty-uptime-sec (.get_uptime_secs summ))
+        "tasksTotal" (sum-tasks executors)
+        "workersTotal" (count workers)
+        "executorsTotal" (count executors)}
+       ))
+ 
+ (defn spout-summary-json [topology-id id stats window]
+   (let [times (stats-times (:emitted stats))
+         display-map (into {} (for [t times] [t pretty-uptime-sec]))
+         display-map (assoc display-map ":all-time" (fn [_] "All time"))]
+      (for [k (concat times [":all-time"])
+            :let [disp ((display-map k) k)]]
+        {"windowPretty" disp
+         "window" k
+         "emitted" (get-in stats [:emitted k])
+         "transferred" (get-in stats [:transferred k])
+         "completeLatency" (float-str (get-in stats [:complete-latencies k]))
+         "acked" (get-in stats [:acked k])
+         "failed" (get-in stats [:failed k])
+         }
+        )))
  
  (defn topology-page [id window include-sys?]
    (with-nimbus nimbus
@@@ -800,139 -465,63 +578,74 @@@
            name (.get_name summ)
            status (.get_status summ)
            msg-timeout (topology-conf TOPOLOGY-MESSAGE-TIMEOUT-SECS)
 +          spouts (.get_spouts topology)
 +          bolts (.get_bolts topology)
++          visualizer-data (visualization-data (merge (hashmap-to-persistent spouts)
++                                                     (hashmap-to-persistent bolts))
++                                              spout-comp-summs
++                                              bolt-comp-summs
++                                              window
++                                              id)
            ]
-       (concat
-        [[:h2 "Topology summary"]]
-        [(topology-summary-table summ)]
-        [[:h2 {:class "js-only"} "Topology actions"]]
-        [[:p {:class "js-only"} (concat
-          [(topology-action-button id name "Activate" "activate" false 0 (= "INACTIVE" status))]
-          [(topology-action-button id name "Deactivate" "deactivate" false 0 (= "ACTIVE" status))]
-          [(topology-action-button id name "Rebalance" "rebalance" true msg-timeout (or (= "ACTIVE" status) (= "INACTIVE" status)))]
-          [(topology-action-button id name "Kill" "kill" true msg-timeout (not= "KILLED" status))]
-        )]]
-        [[:h2 "Topology stats"]]
-        (topology-stats-table id window (total-aggregate-stats spout-summs bolt-summs include-sys?))
-        [[:h2 "Spouts (" window-hint ")"]]
-        (spout-comp-table id spout-comp-summs (.get_errors summ) window include-sys?)
-        [[:h2 "Bolts (" window-hint ")"]]
-        (bolt-comp-table id bolt-comp-summs (.get_errors summ) window include-sys?)
-        [[:h2 "Visualization"]]
-        (mk-visualizations spouts bolts spout-comp-summs bolt-comp-summs window id)
-        [[:h2 "Topology Configuration"]]
-        (configuration-table topology-conf)
-        ))))
+       (merge
+        (topology-summary summ)
+        {"window" window
+         "windowHint" window-hint
+         "msgTimeout" msg-timeout
+         "topologyStats" (topology-stats id window (total-aggregate-stats spout-summs bolt-summs include-sys?))
+         "spouts" (spout-comp id spout-comp-summs (.get_errors summ) window include-sys?)
+         "bolts" (bolt-comp id bolt-comp-summs (.get_errors summ) window include-sys?)
 -        "configuration" topology-conf})
++        "configuration" topology-conf
++        "visualizationTable" (stream-boxes visualizer-data)
++        })
+       )))
  
- (defn component-task-summs [^TopologyInfo summ topology id]
-   (let [spout-summs (filter (partial spout-summary? topology) (.get_executors summ))
-         bolt-summs (filter (partial bolt-summary? topology) (.get_executors summ))
-         spout-comp-summs (group-by-comp spout-summs)
-         bolt-comp-summs (group-by-comp bolt-summs)
-         ret (if (contains? spout-comp-summs id)
-               (spout-comp-summs id)
-               (bolt-comp-summs id))]
-     (sort-by #(-> ^ExecutorSummary % .get_executor_info .get_task_start) ret)
-     ))
 +
- (defn spout-summary-table [topology-id id stats window]
-   (let [times (stats-times (:emitted stats))
-         display-map (into {} (for [t times] [t pretty-uptime-sec]))
-         display-map (assoc display-map ":all-time" (fn [_] "All time"))]
-     (sorted-table
-      [{:text "Window" :attr {:class "tip right"
-                              :title (:window tips)}}
-       {:text "Emitted" :attr {:class "tip above"
-                               :title (:emitted tips)}}
-       {:text "Transferred" :attr {:class "tip above"
-                                   :title (:transferred tips)}}
-       {:text "Complete latency (ms)" :attr {:class "tip above"
-                                             :title (:complete-lat tips)}}
-       {:text "Acked" :attr {:class "tip above"
-                             :title (:spout-acked tips)}}
-       {:text "Failed" :attr {:class "tip left"
-                             :title (:spout-failed tips)}}]
-      (for [k (concat times [":all-time"])
-            :let [disp ((display-map k) k)]]
-        [(link-to (if (= k window) {:class "red"} {})
-                  (url-format "/topology/%s/component/%s?window=%s" topology-id id k)
-                  (escape-html disp))
-         (get-in stats [:emitted k])
-         (get-in stats [:transferred k])
-         (float-str (get-in stats [:complete-latencies k]))
-         (get-in stats [:acked k])
-         (get-in stats [:failed k])
-         ])
-      :time-cols [0])))
- 
- (defn spout-output-summary-table [stream-summary window]
+ (defn spout-output-stats [stream-summary window]
    (let [stream-summary (map-val swap-map-order (swap-map-order stream-summary))]
-     (sorted-table
-      [{:text "Stream" :attr {:class "tip right"
-                              :title (:stream tips)}}
-       {:text "Emitted" :attr {:class "tip above"
-                               :title (:emitted tips)}}
-       {:text "Transferred" :attr {:class "tip above"
-                                   :title (:transferred tips)}}
-       {:text "Complete latency (ms)" :attr {:class "tip above"
-                                             :title (:complete-lat tips)}}
-       {:text "Acked" :attr {:class "tip above"
-                             :title (:spout-acked tips)}}
-       {:text "Failed" :attr {:class "tip left"
-                             :title (:spout-failed tips)}}]
-      (for [[s stats] (stream-summary window)]
-        [s
-         (nil-to-zero (:emitted stats))
-         (nil-to-zero (:transferred stats))
-         (float-str (:complete-latencies stats))
-         (nil-to-zero (:acked stats))
-         (nil-to-zero (:failed stats))])
-      )))
+     (for [[s stats] (stream-summary window)]
+       {"stream" s
+        "emitted" (nil-to-zero (:emitted stats))
+        "transferred" (nil-to-zero (:transferred stats))
+        "completeLatency" (float-str (:complete-latencies stats))
+        "acked" (nil-to-zero (:acked stats))
+        "failed" (nil-to-zero (:failed stats))
+        }
+       )))
  
- (defn spout-executor-table [topology-id executors window include-sys?]
-   (sorted-table
-    [{:text "Id" :attr {:class "tip right"
-                        :title (:exec-id tips)}}
-     {:text "Uptime" :attr {:class "tip right"
-                            :title (:exec-uptime tips)}}
-     {:text "Host" :attr {:class "tip above"
-                          :title (:sup-host tips)}}
-     {:text "Port" :attr {:class "tip above"
-                          :title (:port tips)}}
-     {:text "Emitted" :attr {:class "tip above"
-                             :title (:emitted tips)}}
-     {:text "Transferred" :attr {:class "tip above"
-                                 :title (:transferred tips)}}
-     {:text "Complete latency (ms)" :attr {:class "tip above"
-                                           :title (:complete-lat tips)}}
-     {:text "Acked" :attr {:class "tip above"
-                           :title (:spout-acked tips)}}
-     {:text "Failed" :attr {:class "tip left"
-                           :title (:spout-failed tips)}}]
-    (for [^ExecutorSummary e executors
-          :let [stats (.get_stats e)
-                stats (if stats
-                        (-> stats
-                            (aggregate-spout-stats include-sys?)
-                            aggregate-spout-streams
-                            swap-map-order
-                            (get window)))]]
-      [(pretty-executor-info (.get_executor_info e))
-       (pretty-uptime-sec (.get_uptime_secs e))
-       (.get_host e)
-       (worker-log-link (.get_host e) (.get_port e))
-       (nil-to-zero (:emitted stats))
-       (nil-to-zero (:transferred stats))
-       (float-str (:complete-latencies stats))
-       (nil-to-zero (:acked stats))
-       (nil-to-zero (:failed stats))
-       ]
-      )
-    :time-cols [1]
-    ))
- 
- (defn spout-page [window ^TopologyInfo topology-info component executors include-sys?]
+ (defn spout-executor-stats [topology-id executors window include-sys?]
+   (for [^ExecutorSummary e executors
+         :let [stats (.get_stats e)
+               stats (if stats
+                       (-> stats
+                           (aggregate-spout-stats include-sys?)
+                           aggregate-spout-streams
+                           swap-map-order
+                           (get window)))]]
+     {"id" (pretty-executor-info (.get_executor_info e))
+      "uptime" (pretty-uptime-sec (.get_uptime_secs e))
+      "host" (.get_host e)
+      "port" (.get_port e)
+      "emitted" (nil-to-zero (:emitted stats))
+      "transferred" (nil-to-zero (:transferred stats))
+      "completeLatency" (float-str (:complete-latencies stats))
+      "acked" (nil-to-zero (:acked stats))
+      "failed" (nil-to-zero (:failed stats))
+      "workerLogLink" (worker-log-link (.get_host e) (.get_port e))
+      }
+   ))
+ 
+ (defn component-errors [errors-list]
+   (let [errors (->> errors-list
+                     (sort-by #(.get_error_time_secs ^ErrorInfo %))
+                     reverse)]
+     {"componentErrors"
+      (for [^ErrorInfo e errors]
+        {"time" (date-str (.get_error_time_secs e))
+          "error" (.get_error e)
+          })}))
+ 
+ (defn spout-stats [window ^TopologyInfo topology-info component executors include-sys?]
    (let [window-hint (str " (" (window-hint window) ")")
          stats (get-filled-stats executors)
          stream-summary (-> stats (aggregate-spout-stats include-sys?))
@@@ -1124,58 -617,46 +741,48 @@@
            topology (.getTopology ^Nimbus$Client nimbus topology-id)
            type (component-type topology component)
            summs (component-task-summs summ topology component)
-           spec (cond (= type :spout) (spout-page window summ component summs include-sys?)
-                      (= type :bolt) (bolt-page window summ component summs include-sys?))]
-       (concat
-        [[:h2 "Component summary"]
-         (table [{:text "Id" :attr {:class "tip right"
-                                    :title (:comp-id tips)}}
-                 {:text "Topology" :attr {:class "tip above"
-                                    :title (str (:name tips) " " (:name-link tips))}}
-                 {:text "Executors" :attr {:class "tip above"
-                                    :title (:num-execs tips)}}
-                 {:text "Tasks" :attr {:class "tip above"
-                                :title (:num-tasks tips)}}]
-                [[(escape-html component)
-                  (topology-link (.get_id summ) (.get_name summ))
-                  (count summs)
-                  (sum-tasks summs)
-                  ]])]
-        spec
-        [[:h2 "Errors"]
-         (errors-table (get (.get_errors summ) component))]
-        ))))
- 
- (defn get-include-sys? [cookies]
-   (let [sys? (get cookies "sys")
-         sys? (if (or (nil? sys?) (= "false" (:value sys?))) false true)]
-     sys?))
+           spec (cond (= type :spout) (spout-stats window summ component summs include-sys?)
+                      (= type :bolt) (bolt-stats window summ component summs include-sys?))
+           errors (component-errors (get (.get_errors summ) component))]
+       (merge
+        {"id" component
+          "name" (.get_name summ)
+          "executors" (count summs)
+          "tasks" (sum-tasks summs)
+          "topologyId" topology-id
+          "window" window
+          "componentType" (name type)
+          "windowHint" (window-hint window)
+          } spec errors))))
+ 
+ (defn check-include-sys? [sys?]
+   (if (or (nil? sys?) (= "false" sys?)) false true))
+ 
+ (defn json-response [data & [status]]
+   {:status (or status 200)
+    :headers {"Content-Type" "application/json"}
+    :body (to-json data)
+    })
  
  (defroutes main-routes
-   (GET "/" [:as {cookies :cookies}]
-        (-> (main-page)
-            ui-template))
-   (GET "/topology/:id" [:as {cookies :cookies} id & m]
-        (let [include-sys? (get-include-sys? cookies)
-             id (url-decode id)]
-          (try
-            (-> (topology-page (url-decode id) (:window m) include-sys?)
-              (concat [(mk-system-toggle-button include-sys?)])
-              ui-template)
-            (catch Exception e (resp/redirect "/")))))
-   (GET "/topology/:id/visualization" [:as {:keys [cookies servlet-request]} id & m]
-          (let [include-sys? (get-include-sys? cookies)]
-            {:status 200
-             :headers {"Content-Type" "application/json"}
-             :body (mk-visualization-json id (:window m) include-sys?)}))
-   (GET "/topology/:id/component/:component" [:as {cookies :cookies} id component & m]
-        (let [include-sys? (get-include-sys? cookies)
-             id (url-decode id)
-             component (url-decode component)]
-          (-> (component-page id component (:window m) include-sys?)
-              (concat [(mk-system-toggle-button include-sys?)])
-              ui-template)))
-   (POST "/topology/:id/activate" [id]
+   (GET "/api/v1/cluster/configuration" []
+        (cluster-configuration))
+   (GET "/api/v1/cluster/summary" []
+        (json-response (cluster-summary)))
+   (GET "/api/v1/supervisor/summary" []
+        (json-response (supervisor-summary)))
+   (GET "/api/v1/topology/summary" []
+        (json-response (all-topologies-summary)))
+   (GET  "/api/v1/topology/:id" [id & m]
+         (let [id (url-decode id)]
+           (json-response (topology-page id (:window m) (check-include-sys? (:sys m))))))
++  (GET "/api/v1/topology/:id/visualization" [:as {:keys [cookies servlet-request]} id & m]
++       (json-response (mk-visualization-data id (:window m) (check-include-sys? (:sys m)))))
+   (GET "/api/v1/topology/:id/component/:component" [id component & m]
+        (let [id (url-decode id)
+              component (url-decode component)]
+          (json-response (component-page id component (:window m) (check-include-sys? (:sys m))))))
+   (POST "/api/v1/topology/:id/activate" [id]
      (with-nimbus nimbus
        (let [id (url-decode id)
              tplg (.getTopologyInfo ^Nimbus$Client nimbus id)

http://git-wip-us.apache.org/repos/asf/incubator-storm/blob/1e0413e9/storm-core/src/ui/public/js/visualization.js
----------------------------------------------------------------------
diff --cc storm-core/src/ui/public/js/visualization.js
index 33ba12b,0000000..87596b8
mode 100644,000000..100644
--- a/storm-core/src/ui/public/js/visualization.js
+++ b/storm-core/src/ui/public/js/visualization.js
@@@ -1,413 -1,0 +1,403 @@@
 +// Inspired by 
 +// https://github.com/samizdatco/arbor/blob/master/docs/sample-project/main.js
 +
 +function renderGraph(elem) {
 +
 +    var canvas = $(elem).get(0);
 +    canvas.width = $(window).width();
 +    canvas.height = $(window).height();
 +    var ctx = canvas.getContext("2d");
 +    var gfx = arbor.Graphics(canvas);
 +    var psys;
 +
 +    var totaltrans = 0;
 +    var weights = {};
 +    var texts = {};
 +    var update = false;
 +
 +    var myRenderer = {
 +        init: function(system){ 
 +            psys = system;
 +            psys.screenSize(canvas.width, canvas.height)
 +            psys.screenPadding(20);
 +            myRenderer.initMouseHandling();
 +        },
 +
 +        signal_update: function() {
 +            update = true;
 +        },
 +
 +        redraw: function() { 
 +            
 +            if(!psys)
 +                return;
 +            
 +            if(update) {
 +                totaltrans = calculate_total_transmitted(psys);
 +                weights = calculate_weights(psys, totaltrans);
 +                texts = calculate_texts(psys, totaltrans);
 +                update = false;
 +            }
 +
 +
 +
 +            ctx.fillStyle = "white";
 +            ctx.fillRect(0, 0, canvas.width, canvas.height);
 +            var x = 0;
 +            
 +
 +            psys.eachEdge(function(edge, pt1, pt2) {
 +
 +                var len = Math.sqrt(Math.pow(pt2.x - pt1.x,2) + Math.pow(pt2.y - pt1.y,2));
 +                var sublen = len - (Math.max(50, 20 + gfx.textWidth(edge.target.name)) / 2);
 +                var thirdlen = len/3;
 +                var theta = Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x);
 +                
 +                var newpt2 = {
 +                    x : pt1.x + (Math.cos(theta) * sublen),
 +                    y : pt1.y + (Math.sin(theta) * sublen)
 +                };
 +
 +                var thirdpt = {
 +                    x: pt1.x + (Math.cos(theta) * thirdlen),
 +                    y: pt1.y + (Math.sin(theta) * thirdlen)
 +                }
 +
 +                weight = weights[edge.source.name + edge.target.name];
 +                
 +                if(!weights[edge.source.name + edge.target.name])
 +                {
 +                    totaltrans = calculate_total_transmitted(psys);
 +                    weights = calculate_weights(psys, totaltrans);
 +                }
 +
 +                ctx.strokeStyle = "rgba(0,0,0, .333)";
 +                ctx.lineWidth = 25 * weight + 5;
 +                ctx.beginPath();
 +
 +                var arrlen = 15;
 +                ctx.moveTo(pt1.x, pt1.y);
 +                ctx.lineTo(newpt2.x, newpt2.y);
 +                ctx.lineTo(newpt2.x - arrlen * Math.cos(theta-Math.PI/6), newpt2.y - arrlen * Math.sin(theta - Math.PI/6));
 +                ctx.moveTo(newpt2.x, newpt2.y);
 +                ctx.lineTo(newpt2.x - arrlen * Math.cos(theta+Math.PI/6), newpt2.y - arrlen * Math.sin(theta + Math.PI/6));
 +
 +                
 +                if (texts[edge.source.name + edge.target.name] == null)
 +                {
 +                    totaltrans = calculate_total_transmitted(psys);
 +                    texts = calculate_texts(psys, totaltrans);
 +                }
 +
 +                gfx.text(texts[edge.source.name + edge.target.name], thirdpt.x, thirdpt.y + 10, {color:"black", align:"center", font:"Arial", size:10})
 +                ctx.stroke();
 +            });
 +
 +            psys.eachNode(function(node, pt) {
 +                var col;
 +
 +                var real_trans = gather_stream_count(node.data[":stats"], "default", "600");
 +                
 +                if(node.data[":type"] === "bolt") {
 +                    var cap = Math.min(node.data[":capacity"], 1);
 +                    var red = Math.floor(cap * 225) + 30;
 +                    var green = Math.floor(255 - red);
 +                    var blue = Math.floor(green/5);
 +                    col = arbor.colors.encode({r:red,g:green,b:blue,a:1});
 +                } else {
 +                    col = "#0000FF";
 +                }
 +                
 +                var w = Math.max(55, 25 + gfx.textWidth(node.name));
 +                
 +                gfx.oval(pt.x - w/2, pt.y - w/2, w, w, {fill: col});
 +                gfx.text(node.name, pt.x, pt.y+3, {color:"white", align:"center", font:"Arial", size:12});
 +                gfx.text(node.name, pt.x, pt.y+3, {color:"white", align:"center", font:"Arial", size:12});
 +                
 +                gfx.text(parseFloat(node.data[":latency"]).toFixed(2) + " ms", pt.x, pt.y + 17, {color:"white", align:"center", font:"Arial", size:12});
 +                
 +            });
 +
 +            // Draw gradient sidebar
 +            ctx.rect(0,0,50,canvas.height);
 +            var grd = ctx.createLinearGradient(0,0,50,canvas.height);
 +            grd.addColorStop(0, '#1ee12d');
 +            grd.addColorStop(1, '#ff0000');
 +            ctx.fillStyle=grd;
 +            ctx.fillRect(0,0,50,canvas.height);
 +            
 +            
 +        },
 +        
 +        initMouseHandling:function() {
 +            var dragged = null;
 +
 +            var clicked = false;
 +            
 +            var handler = {
 +                clicked:function(e){
 +                    var pos = $(canvas).offset();
 +                    _mouseP = arbor.Point(e.pageX-pos.left, e.pageY - pos.top);
 +                    dragged = psys.nearest(_mouseP);
 +                    
 +                    if(dragged && dragged.node !== null) {
 +                        dragged.node.fixed = true;
 +                    }
 +                    
 +                    clicked = true;
 +                    setTimeout(function(){clicked = false;}, 50);
 +
 +                    $(canvas).bind('mousemove', handler.dragged);
 +                    $(window).bind('mouseup', handler.dropped);
 +                    
 +                    return false;
 +                },
 +                
 +                dragged:function(e) {
 +
 +                    var pos = $(canvas).offset();
 +                    var s = arbor.Point(e.pageX-pos.left, e.pageY-pos.top);
 +                    
 +                    if(dragged && dragged.node != null) {
 +                        var p = psys.fromScreen(s);
 +                        dragged.node.p = p;
 +                    }
 +                    
 +                    return false;
 +                    
 +                },
 +
 +                dropped:function(e) {
 +                    if(clicked) {
 +                        if(dragged.distance < 50) {
 +                            if(dragged && dragged.node != null) { 
 +                                window.location = dragged.node.data[":link"];
 +                            }
 +                        }
 +                    }
 +
 +                    if(dragged === null || dragged.node === undefined) return;
 +                    if(dragged.node !== null) dragged.node.fixed = false;
 +                    dragged.node.tempMass = 1000;
 +                    dragged = null;
 +                    $(canvas).unbind('mousemove', handler.dragged);
 +                    $(window).unbind('mouseup', handler.dropped);
 +                    _mouseP = null;
 +                    return false;
 +                }
 +                
 +            }
 +            
 +            $(canvas).mousedown(handler.clicked);
 +        }
 +    }
 +    
 +    return myRenderer;
 +}
 +
 +function calculate_texts(psys, totaltrans) {
 +    var texts = {};
 +    psys.eachEdge(function(edge, pt1, pt2) {
 +        var text = "";
 +        for(var i = 0; i < edge.target.data[":inputs"].length; i++) {
 +            var stream = edge.target.data[":inputs"][i][":stream"];
 +            var sani_stream = edge.target.data[":inputs"][i][":sani-stream"];
 +            if(stream_checked(sani_stream) 
 +               && edge.target.data[":inputs"][i][":component"] === edge.source.name) {
 +                stream_transfered = gather_stream_count(edge.source.data[":stats"], sani_stream, "600");
 +                text += stream + ": " 
 +                    + stream_transfered + ": " 
 +                    + (totaltrans > 0  ? Math.round((stream_transfered/totaltrans) * 100) : 0) + "%\n";
 +                
 +            }
 +        }
 +        
 +        texts[edge.source.name + edge.target.name] = text;
 +    });
 +
 +    return texts;
 +}
 +
 +function calculate_weights(psys, totaltrans) {
 +    var weights = {};
 + 
 +    psys.eachEdge(function(edge, pt1, pt2) {
 +        var trans = 0;
 +        for(var i = 0; i < edge.target.data[":inputs"].length; i++) {
 +            var stream = edge.target.data[":inputs"][i][":sani-stream"];
 +            if(stream_checked(stream) && edge.target.data[":inputs"][i][":component"] === edge.source.name)
 +                trans += gather_stream_count(edge.source.data[":stats"], stream, "600");
 +        }
 +        weights[edge.source.name + edge.target.name] = (totaltrans > 0 ? trans/totaltrans : 0);
 +    });
 +    return weights;
 +}
 +
 +function calculate_total_transmitted(psys) {
 +    var totaltrans = 0;
 +    var countedmap = {}
 +    psys.eachEdge(function(node, pt, pt2) {
 +        if(!countedmap[node.source.name])
 +            countedmap[node.source.name] = {};
 +
 +        for(var i = 0; i < node.target.data[":inputs"].length; i++) {
 +            var stream = node.target.data[":inputs"][i][":stream"];
 +            if(stream_checked(node.target.data[":inputs"][i][":sani-stream"]))
 +            {
 +                if(!countedmap[node.source.name][stream]) {
 +                    if(node.source.data[":stats"])
 +                    {
 +                        var toadd = gather_stream_count(node.source.data[":stats"], node.target.data[":inputs"][i][":sani-stream"], "600");
 +                        totaltrans += toadd;
 +                    }
 +                    countedmap[node.source.name][stream] = true;
 +                }
 +            }
 +        }
 +        
 +    });
 +
 +    return totaltrans;
 +}
 +
 +function has_checked_stream_input(inputs) {
 +    
 +    for(var i = 0; i < inputs.length; i++) {
 +        var x = stream_checked(inputs[i][":sani-stream"]);
 +        if(x) 
 +            return true;
 +    }
 +    return false;
 +}
 +
 +function stream_checked(stream) {
 +    var checked = $("#" + stream).is(":checked");
 +    return checked;
 +}
 +
 +function has_checked_stream_output(jdat, component) {
 +    var ret = false;
 +    $.each(jdat, function(k, v) {
 +        for(var i = 0; i < v[":inputs"].length; i++) {
 +            if(stream_checked(v[":inputs"][i][":sani-stream"]) 
 +               && v[":inputs"][i][":component"] == component)
 +                ret = true;
 +        }
 +    });
 +    return ret;
 +}
 +
 +function gather_stream_count(stats, stream, time) {
 +    var transferred = 0;
 +    if(stats)
 +        for(var i = 0; i < stats.length; i++) {
 +            if(stats[i][":transferred"] != null)
 +            {
 +                var stream_trans = stats[i][":transferred"][time][stream];
 +                if(stream_trans != null)
 +                    transferred += stream_trans;
 +            }
 +        }
 +    return transferred;
 +}
 +
 +
 +function rechoose(jdat, sys, box) {
 +    var id = box.id;
 +    if($(box).is(':checked'))
 +    {
 +        //Check each node in our json data to see if it has inputs from or outputs to selected streams. If it does, add a node for it.
 +        $.each(jdat,function(k,v) {
 +            if( has_checked_stream_input(v[":inputs"]) || has_checked_stream_output(jdat, k))
 +                sys.addNode(k,v);
 +        });
 +           
 +        //Check each node in our json data and add necessary edges based on selected components.
 +        $.each(jdat, function(k, v) {
 +            for(var i = 0; i < v[":inputs"].length; i++)
 +                if(v[":inputs"][i][":sani-stream"] === id) {
 +                    
 +                    sys.addEdge(v[":inputs"][i][":component"], k, v);
 +                }
 +        });
 +    }
 +    else {
 +        //Check each node to see if it should be pruned.
 +        sys.prune(function(node, from, to) {
 +            return !has_checked_stream_input(node.data[":inputs"]) && !has_checked_stream_output(jdat, node.name);
 +        });
 +        
 +        //Check each edge to see if it represents any selected streams. If not, prune it.
 +        sys.eachEdge(function(edge, pt1, pt2) {
 +            var inputs = edge.target.data[":inputs"];
 +            
 +            if($.grep(inputs, function(input) {
 +                
 +                return input[":component"] === edge.source.name 
 +                    && stream_checked(input[":sani-stream"]);
 +            
 +            }).length == 0)
 +            {
 +                sys.pruneEdge(edge);
 +            }
 +        });
 +    }
 +
 +    //Tell the particle system's renderer that it needs to update its labels, colors, widths, etc.
 +    sys.renderer.signal_update();
 +    sys.renderer.redraw();
 +
 +}
 +
 +var topology_data;
 +function update_data(jdat, sys) {
 +    $.each(jdat, function(k,v) {
 +        if(sys.getNode(k))
 +            sys.getNode(k).data = v;
 +    });
 +}
 +
 +var should_update;
 +function show_visualization(sys) {
 +
 +    if(sys == null)
 +    {
 +        sys = arbor.ParticleSystem(20, 1000, 0.15, true, 55, 0.02, 0.6);
 +        sys.renderer = renderGraph("#topoGraph");
 +        sys.stop();
 +
 +        $(".stream-box").click(function () { rechoose(topology_data, sys, this) });    
 +    }
 +
 +    should_update = true;
 +    var update_freq_ms = 10000;
 +    var update = function(should_rechoose){
 +        $.ajax({
-             url: document.URL.split('?')[0] + "/visualization",
++            url: "/api/v1/topology/"+$.url().param("id")+"/visualization",
 +            success: function(data, status, jqXHR) {
 +                topology_data = data;
 +                update_data(topology_data, sys);
 +                sys.renderer.signal_update();
 +                sys.renderer.redraw();
 +                if(should_update)
 +                    setTimeout(update, update_freq_ms);
 +                if(should_rechoose)
 +                    $(".stream-box").each(function () { rechoose(topology_data, sys, this) });
 +            }
 +        });
 +    };
 +    
 +    update(true);
 +    $("#visualization-container").show(500);
 +    $("#show-hide-visualization").attr('value', 'Hide Visualization');
 +    $("#show-hide-visualization").unbind("click");
 +    $("#show-hide-visualization").click(function () { hide_visualization(sys) });
 +}
 +
 +function hide_visualization(sys) {
 +    should_update = false;
 +    $("#visualization-container").hide(500);
 +    $("#show-hide-visualization").attr('value', 'Show Visualization');
 +    $("#show-hide-visualization").unbind("click");
 +    $("#show-hide-visualization").click(function () { show_visualization(sys) });
 +}
- 
- $(document).ready(function() {
- 
-     if($("#visualization-container"))
-     {
-         var sys = null;
-         $("#show-hide-visualization").click(function () { show_visualization(sys) });
-     
-     }
- })

http://git-wip-us.apache.org/repos/asf/incubator-storm/blob/1e0413e9/storm-core/src/ui/public/templates/topology-page-template.html
----------------------------------------------------------------------
diff --cc storm-core/src/ui/public/templates/topology-page-template.html
index 0000000,cbeb8fd..da58f9d
mode 000000,100644..100644
--- a/storm-core/src/ui/public/templates/topology-page-template.html
+++ b/storm-core/src/ui/public/templates/topology-page-template.html
@@@ -1,0 -1,97 +1,128 @@@
+ <script id="topology-summary-template" type="text/html">
 -<table id="topology-summary-table">
 -<thead><tr><th><span class="tip right" title="The name given to the topology by when it was submitted.">Name</span></th><th><span class="tip right" title="The unique ID given to a Topology each time it is launched.">Id</span></th><th><span class="tip above" title="The status can be one of ACTIVE, INACTIVE, KILLED, or REBALANCING.">Status</span></th><th><span class="tip above" title="The time since the Topology was submitted.">Uptime</span></th><th><span class="tip above" title="The number of Workers (processes).">Num workers</span></th><th><span class="tip above" title="Executors are threads in a Worker process.">Num executors</span></th><th><span class="tip above" title="A Task is an instance of a Bolt or Spout. The number of Tasks is almost always equal to the number of Executors.">Num tasks</span></th></tr></thead>
 -<tbody>
 -<tr>
 -  <td>{{name}}</td>
 -  <td>{{id}}</td>
 -  <td>{{status}}</td>
 -  <td>{{uptime}}</td>
 -  <td>{{tasksTotal}}</td>
 -  <td>{{workersTotal}}</td>
 -  <td>{{executorsTotal}}</td>
 -</tr>
 -</tbody>
 -</table>
++  <table id="topology-summary-table">
++    <thead><tr><th><span class="tip right" title="The name given to the topology by when it was submitted.">Name</span></th><th><span class="tip right" title="The unique ID given to a Topology each time it is launched.">Id</span></th><th><span class="tip above" title="The status can be one of ACTIVE, INACTIVE, KILLED, or REBALANCING.">Status</span></th><th><span class="tip above" title="The time since the Topology was submitted.">Uptime</span></th><th><span class="tip above" title="The number of Workers (processes).">Num workers</span></th><th><span class="tip above" title="Executors are threads in a Worker process.">Num executors</span></th><th><span class="tip above" title="A Task is an instance of a Bolt or Spout. The number of Tasks is almost always equal to the number of Executors.">Num tasks</span></th></tr></thead>
++    <tbody>
++      <tr>
++        <td>{{name}}</td>
++        <td>{{id}}</td>
++        <td>{{status}}</td>
++        <td>{{uptime}}</td>
++        <td>{{tasksTotal}}</td>
++        <td>{{workersTotal}}</td>
++        <td>{{executorsTotal}}</td>
++      </tr>
++    </tbody>
++  </table>
+ </script>
+ <script id="topology-stats-template" type="text/html">
 -<h2>Topology stats</h2>
 -<table class="zebra-striped" id="topology-stats-table">
 -<thead><tr><th><span class="tip right" title="The past period of time for which the statistics apply. Click on a value to set the window for this page.">Window</span></th><th><span class="tip above" title="The number of Tuples emitted.">Emitted</span></th><th><span class="tip above" title="The number of Tuples emitted that sent to one or more bolts.">Transferred</span></th><th><span class="tip above" title="The average time a Tuple &quot;tree&quot; takes to be completely processed by the Topology. A value of 0 is expected if no acking is done.">Complete latency (ms)</span></th><th><span class="tip above" title="The number of Tuple &quot;trees&quot; successfully processed. A value of 0 is expected if no acking is done.">Acked</span></th><th><span class="tip left" title="The number of Tuple &quot;trees&quot; that were explicitly failed or timed out before acking was completed. A value of 0 is expected if no acking is done.">Failed</span></th></tr></thead>
 -<tbody>
 -{{#topologyStats}}
 -<tr>
 -  <td><a href="/topology.html?id={{id}}&window={{window}}">{{windowPretty}}</td>
 -  <td>{{emitted}}</td>
 -  <td>{{transferred}}</td>
 -  <td>{{completeLatency}}</td>
 -  <td>{{acked}}</td>
 -  <td>{{failed}}</td>
 -</tr>
 -{{/topologyStats}}
 -</tbody>
 -</table>
++  <h2>Topology stats</h2>
++  <table class="zebra-striped" id="topology-stats-table">
++    <thead><tr><th><span class="tip right" title="The past period of time for which the statistics apply. Click on a value to set the window for this page.">Window</span></th><th><span class="tip above" title="The number of Tuples emitted.">Emitted</span></th><th><span class="tip above" title="The number of Tuples emitted that sent to one or more bolts.">Transferred</span></th><th><span class="tip above" title="The average time a Tuple &quot;tree&quot; takes to be completely processed by the Topology. A value of 0 is expected if no acking is done.">Complete latency (ms)</span></th><th><span class="tip above" title="The number of Tuple &quot;trees&quot; successfully processed. A value of 0 is expected if no acking is done.">Acked</span></th><th><span class="tip left" title="The number of Tuple &quot;trees&quot; that were explicitly failed or timed out before acking was completed. A value of 0 is expected if no acking is done.">Failed</span></th></tr></thead>
++    <tbody>
++      {{#topologyStats}}
++      <tr>
++        <td><a href="/topology.html?id={{id}}&window={{window}}">{{windowPretty}}</td>
++        <td>{{emitted}}</td>
++        <td>{{transferred}}</td>
++        <td>{{completeLatency}}</td>
++        <td>{{acked}}</td>
++        <td>{{failed}}</td>
++      </tr>
++      {{/topologyStats}}
++    </tbody>
++  </table>
+ </script>
++<script id="topology-visualization-template" type="text/html">
++  <h2>Topology Visualization</h2>
++  <input type="button" id="show-hide-visualization" value="Show Visualization"/>
++  <p>
++    <div id="visualization-container" style="display:none;">
++      <p>
++        <table class="zebra-striped">
++          <thead>
++            <tr>
++              <th class="header" colspan=4>
++                Streams
++              </th>
++            </tr>
++          </thead>
++          {{#visualizationTable}}
++          <tr>
++            {{#:row}}
++            <td>
++              <input type="checkbox" id={{:sani-stream}} class="stream-box" {{#:checked}}checked{{/:checked}}/>
++              {{:stream}}
++            </td>
++            {{/:row}}
++          </tr>
++          {{/visualizationTable}}
++        </table>
++      </p>
++      <canvas id="topoGraph" width=1024 height=768 style="border:1px solid #000000;">
++    </div>
++  </p>
++</script>
++
+ <script id="topology-configuration-template" type="text/html">
 -<h2>Topology Configuration</h2>
 -<table class="zebra-striped" id="topology-configuration-table"><thead><tr><th>Key</th><th>Value</th></tr></thead>
 -<tbody>
 -{{#config}}
 -<tr>
 -<td>{{key}}</td>
 -<td>{{value}}</td>
 -</tr>
 -{{/config}}
 -</tbody>
 -</table>
++  <h2>Topology Configuration</h2>
++  <table class="zebra-striped" id="topology-configuration-table"><thead><tr><th>Key</th><th>Value</th></tr></thead>
++    <tbody>
++      {{#config}}
++      <tr>
++        <td>{{key}}</td>
++        <td>{{value}}</td>
++      </tr>
++      {{/config}}
++    </tbody>
++  </table>
+ </script>
+ <script id="spout-stats-template" type="text/html">
 -<h2>Spouts ({{windowHint}})</h2>
 -<table class="zebra-striped" id="spout-stats-table">
 -<thead>
 -<tr><th class="header headerSortDown"><span data-original-title="The ID assigned to a the Component by the Topology. Click on the name to view the Component's page." class="tip right">Id</span></th><th class="header"><span data-original-title="Executors are threads in a Worker process." class="tip right">Executors</span></th><th class="header"><span class="tip above" title="A Task is an instance of a Bolt or Spout. The number of Tasks is almost always equal to the number of Executors.">Tasks</span></th><th class="header"><span class="tip above" title="The number of Tuples emitted.">Emitted</span></th><th class="header"><span class="tip above" title="The number of Tuples emitted that sent to one or more bolts.">Transferred</span></th><th class="header"><span class="tip above" title="The average time a Tuple &quot;tree&quot; takes to be completely processed by the Topology. A value of 0 is expected if no acking is done.">Complete latency (ms)</span></th><th class="header"><span class
 ="tip above" title="The number of Tuple &quot;trees&quot; successfully processed. A value of 0 is expected if no acking is done.">Acked</span></th><th class="header"><span class="tip above" title="The number of Tuple &quot;trees&quot; that were explicitly failed or timed out before acking was completed. A value of 0 is expected if no acking is done.">Failed</span></th><th class="header">Last error</th>
 -</tr>
 -</thead>
 -<tbody>
 -{{#spouts}}
 -<tr>
 -<td><a href="/component.html?id={{spoutId}}&topology_id={{id}}">{{spoutId}}</a></td>
 -<td>{{executors}}</td>
 -<td>{{tasks}}</td>
 -<td>{{emitted}}</td>
 -<td>{{transferred}}</td>
 -<td>{{completeLatency}}</td>
 -<td>{{acked}}</td>
 -<td>{{failed}}</td>
 -<td>{{lastError}}</td>
 -{{/spouts}}
 -</tbody>
 -</table>
++  <h2>Spouts ({{windowHint}})</h2>
++  <table class="zebra-striped" id="spout-stats-table">
++    <thead>
++      <tr><th class="header headerSortDown"><span data-original-title="The ID assigned to a the Component by the Topology. Click on the name to view the Component's page." class="tip right">Id</span></th><th class="header"><span data-original-title="Executors are threads in a Worker process." class="tip right">Executors</span></th><th class="header"><span class="tip above" title="A Task is an instance of a Bolt or Spout. The number of Tasks is almost always equal to the number of Executors.">Tasks</span></th><th class="header"><span class="tip above" title="The number of Tuples emitted.">Emitted</span></th><th class="header"><span class="tip above" title="The number of Tuples emitted that sent to one or more bolts.">Transferred</span></th><th class="header"><span class="tip above" title="The average time a Tuple &quot;tree&quot; takes to be completely processed by the Topology. A value of 0 is expected if no acking is done.">Complete latency (ms)</span></th><th class="header"><span
  class="tip above" title="The number of Tuple &quot;trees&quot; successfully processed. A value of 0 is expected if no acking is done.">Acked</span></th><th class="header"><span class="tip above" title="The number of Tuple &quot;trees&quot; that were explicitly failed or timed out before acking was completed. A value of 0 is expected if no acking is done.">Failed</span></th><th class="header">Last error</th>
++      </tr>
++    </thead>
++    <tbody>
++      {{#spouts}}
++      <tr>
++        <td><a href="/component.html?id={{spoutId}}&topology_id={{id}}">{{spoutId}}</a></td>
++        <td>{{executors}}</td>
++        <td>{{tasks}}</td>
++        <td>{{emitted}}</td>
++        <td>{{transferred}}</td>
++        <td>{{completeLatency}}</td>
++        <td>{{acked}}</td>
++        <td>{{failed}}</td>
++        <td>{{lastError}}</td>
++        {{/spouts}}
++    </tbody>
++  </table>
+ </script>
+ <script id="bolt-stats-template" type="text/html">
 -<h2>Bolts ({{windowHint}})</h2>
 -<table class="zebra-striped" id="bolt-stats-table"><thead>
 -<tr><th class="header headerSortDown"><span class="tip right" title="The ID assigned to a the Component by the Topology. Click on the name to view the Component's page.">Id</span></th><th class="header"><span data-original-title="Executors are threads in a Worker process." class="tip right">Executors</span></th><th class="header"><span class="tip above" title="A Task is an instance of a Bolt or Spout. The number of Tasks is almost always equal to the number of Executors.">Tasks</span></th><th class="header"><span class="tip above" title="The number of Tuples emitted.">Emitted</span></th><th class="header"><span class="tip above" title="The number of Tuples emitted that sent to one or more bolts.">Transferred</span></th><th class="header"><span data-original-title="If this is around 1.0, the corresponding Bolt is running as fast as it can, so you may want to increase the Bolt's parallelism. This is (number executed * average execute latency) / measurement time." class="tip above">Ca
 pacity (last 10m)</span></th><th class="header"><span class="tip above" title="The average time a Tuple spends in the execute method. The execute method may complete without sending an Ack for the tuple.">Execute latency (ms)</span></th><th class="header"><span class="tip above" title="The number of incoming Tuples processed.">Executed</span></th><th class="header"><span class="tip above" title="The average time it takes to Ack a Tuple after it is first received.  Bolts that join, aggregate or batch may not Ack a tuple until a number of other Tuples have been received.">Process latency (ms)</span></th><th class="header"><span class="tip above" title="The number of Tuples acknowledged by this Bolt.">Acked</span></th><th class="header"><span class="tip left" title="The number of tuples Failed by this Bolt.">Failed</span></th><th class="header">Last error</th>
 -</tr></thead>
 -<tbody>
 -{{#bolts}}
 -<tr>
 -<td><a href="/component.html?id={{boltId}}&topology_id={{id}}">{{boltId}}</a></td>
 -<td>{{executors}}</td>
 -<td>{{tasks}}</td>
 -<td>{{emitted}}</td>
 -<td>{{transferred}}</td>
 -<td>{{capacity}}</td>
 -<td>{{executeLatency}}</td>
 -<td>{{executed}}</td>
 -<td>{{processLatency}}</td>
 -<td>{{acked}}</td>
 -<td>{{failed}}</td>
 -<td>{{lastError}}</td>
 -{{/bolts}}
 -</tbody>
++  <h2>Bolts ({{windowHint}})</h2>
++  <table class="zebra-striped" id="bolt-stats-table"><thead>
++      <tr><th class="header headerSortDown"><span class="tip right" title="The ID assigned to a the Component by the Topology. Click on the name to view the Component's page.">Id</span></th><th class="header"><span data-original-title="Executors are threads in a Worker process." class="tip right">Executors</span></th><th class="header"><span class="tip above" title="A Task is an instance of a Bolt or Spout. The number of Tasks is almost always equal to the number of Executors.">Tasks</span></th><th class="header"><span class="tip above" title="The number of Tuples emitted.">Emitted</span></th><th class="header"><span class="tip above" title="The number of Tuples emitted that sent to one or more bolts.">Transferred</span></th><th class="header"><span data-original-title="If this is around 1.0, the corresponding Bolt is running as fast as it can, so you may want to increase the Bolt's parallelism. This is (number executed * average execute latency) / measurement time." class="tip abo
 ve">Capacity (last 10m)</span></th><th class="header"><span class="tip above" title="The average time a Tuple spends in the execute method. The execute method may complete without sending an Ack for the tuple.">Execute latency (ms)</span></th><th class="header"><span class="tip above" title="The number of incoming Tuples processed.">Executed</span></th><th class="header"><span class="tip above" title="The average time it takes to Ack a Tuple after it is first received.  Bolts that join, aggregate or batch may not Ack a tuple until a number of other Tuples have been received.">Process latency (ms)</span></th><th class="header"><span class="tip above" title="The number of Tuples acknowledged by this Bolt.">Acked</span></th><th class="header"><span class="tip left" title="The number of tuples Failed by this Bolt.">Failed</span></th><th class="header">Last error</th>
++    </tr></thead>
++    <tbody>
++      {{#bolts}}
++      <tr>
++        <td><a href="/component.html?id={{boltId}}&topology_id={{id}}">{{boltId}}</a></td>
++        <td>{{executors}}</td>
++        <td>{{tasks}}</td>
++        <td>{{emitted}}</td>
++        <td>{{transferred}}</td>
++        <td>{{capacity}}</td>
++        <td>{{executeLatency}}</td>
++        <td>{{executed}}</td>
++        <td>{{processLatency}}</td>
++        <td>{{acked}}</td>
++        <td>{{failed}}</td>
++        <td>{{lastError}}</td>
++        {{/bolts}}
++    </tbody>
+ </script>
+ 
+ <script id="topology-actions-template" type="text/html">
 -<input {{activateStatus}} onclick="confirmAction('{{id}}', '{{name}}', 'activate', false, 0)" type="button" value="Activate"><input {{deactivateStatus}} onclick="confirmAction('{{id}}', '{{name}}', 'deactivate', false, 0)" type="button" value="Deactivate"><input {{rebalanceStatus}} onclick="confirmAction('{{id}}', '{{name}}', 'rebalance', true, {{msgTimeout}})" type="button" value="Rebalance"><input {{killStatus}} onclick="confirmAction('{{id}}', '{{name}}', 'kill', true, 30)" type="button" value="Kill">
++  <input {{activateStatus}} onclick="confirmAction('{{id}}', '{{name}}', 'activate', false, 0)" type="button" value="Activate"><input {{deactivateStatus}} onclick="confirmAction('{{id}}', '{{name}}', 'deactivate', false, 0)" type="button" value="Deactivate"><input {{rebalanceStatus}} onclick="confirmAction('{{id}}', '{{name}}', 'rebalance', true, {{msgTimeout}})" type="button" value="Rebalance"><input {{killStatus}} onclick="confirmAction('{{id}}', '{{name}}', 'kill', true, 30)" type="button" value="Kill">
+ </script>

http://git-wip-us.apache.org/repos/asf/incubator-storm/blob/1e0413e9/storm-core/src/ui/public/topology.html
----------------------------------------------------------------------
diff --cc storm-core/src/ui/public/topology.html
index 0000000,69c09cb..df095ad
mode 000000,100644..100644
--- a/storm-core/src/ui/public/topology.html
+++ b/storm-core/src/ui/public/topology.html
@@@ -1,0 -1,80 +1,90 @@@
+ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+ <html><head>
+ <title>Storm UI</title>
+ <link href="/css/bootstrap-1.4.0.css" rel="stylesheet" type="text/css">
+ <link href="/css/style.css" rel="stylesheet" type="text/css">
+ <script src="/js/jquery-1.6.2.min.js" type="text/javascript"></script>
+ <script src="/js/jquery.tablesorter.min.js" type="text/javascript"></script>
+ <script src="/js/jquery.cookies.2.2.0.min.js" type="text/javascript"></script>
+ <script src="/js/jquery.mustache.js" type="text/javascript"></script>
+ <script src="/js/purl.js" type="text/javascript"></script>
+ <script src="/js/bootstrap-twipsy.js" type="text/javascript"></script>
+ <script src="/js/script.js" type="text/javascript"></script>
++<script src="/js/visualization.js" type="text/javascript"></script>
++<script src="/js/arbor.js" type="text/javascript"></script>
++<script src="/js/arbor-graphics.js" type="text/javascript"></script>
+ </head>
+ <body>
+ <h1><a href="/">Storm UI</a></h1>
+ <h2>Topology summary</h2>
+ <div id="topology-summary">
+ </div>
+ <div id="topology-actions">
+ <h2 class="js-only">Topology actions</h2>
+ <p id="topology-actions" class="js-only">
+ </p>
+ </div>
+ <div id="topology-stats"></div>
+ <div id="spout-stats">
+ </div>
+ <div id="bolt-stats">
+ </div>
++<div id="topology-visualization">
++</div>
+ <div id="topology-configuration">
+ </div>
+ <p id="toggle-switch" style="display: block;" class="js-only"></p>
+ <div id="json-response-error">
+ </div>
+ </body>
+ <script>
+ $(document).ready(function() {
+     var topologyId = $.url().param("id");
+     var window = $.url().param("window");
+     var sys = $.cookies.get("sys") || "false";
+     var url = "/api/v1/topology/"+topologyId+"?sys="+sys;
+     if(window) url += "&window="+window;
+     renderToggleSys($("#toggle-switch"));
+     $.ajaxSetup({
+         "error":function(jqXHR,textStatus,response) {
+             var errorJson = jQuery.parseJSON(jqXHR.responseText);
+             $.get("/templates/json-error-template.html", function(template) {
+                 $("#json-response-error").append(Mustache.render($(template).filter("#json-error-template").html(),errorJson));
+             });
+         }
+     });
+ 
+     $.getJSON(url,function(response,status,jqXHR) {
+         var topologySummary = $("#topology-summary");
+         var topologyStats = $("#topology-stats");
+         var spoutStats = $("#spout-stats");
+         var boltStats = $("#bolt-stats");
+         var config = $("#topology-configuration");
+         var topologyActions = $("#topology-actions");
++        var topologyVisualization = $("#topology-visualization")
+         var formattedConfig = formatConfigData(response["configuration"]);
+         var buttonJsonData = topologyActionJson(response["id"],response["name"],response["status"],response["msgTimeout"]);
+         $.get("/templates/topology-page-template.html", function(template) {
+             topologySummary.append(Mustache.render($(template).filter("#topology-summary-template").html(),response));
+             topologyActions.append(Mustache.render($(template).filter("#topology-actions-template").html(),buttonJsonData));
+             topologyStats.append(Mustache.render($(template).filter("#topology-stats-template").html(),response));
+             $("#topology-stats-table").tablesorter({ sortList: [[0,0]], headers: {0: { sorter: "stormtimestr"}}});
+             spoutStats.append(Mustache.render($(template).filter("#spout-stats-template").html(),response));
+             if(response["spouts"].length > 0) {
+                 $("#spout-stats-table").tablesorter({sortList: [[0,0]], headers:{}});
+             }
+             boltStats.append(Mustache.render($(template).filter("#bolt-stats-template").html(),response));
+             if(response["bolts"].length > 0) {
+                 $("#bolt-stats-table").tablesorter({sortList: [[0,0]], headers:{}});
+             }
++
++            topologyVisualization.append(Mustache.render($(template).filter("#topology-visualization-template").html(), response));
++            $("#show-hide-visualization").click(function () { show_visualization(null) });
++
+             config.append(Mustache.render($(template).filter("#topology-configuration-template").html(),formattedConfig));
+             $("#topology-configuration-table").tablesorter({ sortList: [[0,0]], headers: {}});
+         });
+     });
+  });
+ </script>
+ </html>