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 "tree" 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 "trees" 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 "trees" 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 "tree" 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 "trees" 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 "trees" 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 "tree" 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 "trees" 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 "trees" 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 "tree" 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 "trees" 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 "trees" 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>