etl-status.html 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <!doctype html>
  2. <meta charset="utf-8">
  3. <title>Dagre D3 Renderer Demo</title>
  4. <script src="../../node_modules/graphlibrary/dist/graphlib.js"></script>
  5. <script src="../../node_modules/d3/build/d3.js"></script>
  6. <script src="../dagre-d3.js"></script>
  7. <style>
  8. body {
  9. position: fixed;
  10. top: 0;
  11. bottom: 0;
  12. left: 0;
  13. right: 0;
  14. margin: 0;
  15. padding: 0;
  16. font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
  17. background: #333;
  18. }
  19. @-webkit-keyframes flash {
  20. 0%, 50%, 100% {
  21. opacity: 1;
  22. }
  23. 25%, 75% {
  24. opacity: 0.2;
  25. }
  26. }
  27. @keyframes flash {
  28. 0%, 50%, 100% {
  29. opacity: 1;
  30. }
  31. 25%, 75% {
  32. opacity: 0.2;
  33. }
  34. }
  35. .warn {
  36. -webkit-animation-duration: 5s;
  37. animation-duration: 5s;
  38. -webkit-animation-fill-mode: both;
  39. animation-fill-mode: both;
  40. -webkit-animation-iteration-count: 1;
  41. animation-iteration-count: 1;
  42. }
  43. .live.map {
  44. width: 100%;
  45. height: 100%;
  46. }
  47. svg {
  48. width: 100%;
  49. height: 100%;
  50. overflow: hidden;
  51. }
  52. .live.map text {
  53. font-weight: 300;
  54. font-size: 14px;
  55. }
  56. .live.map .node rect {
  57. stroke-width: 1.5px;
  58. stroke: #bbb;
  59. fill: #666;
  60. }
  61. .live.map .status {
  62. height: 100%;
  63. width: 15px;
  64. display: block;
  65. float: left;
  66. border-top-left-radius: 5px;
  67. border-bottom-left-radius: 5px;
  68. margin-right: 4px;
  69. }
  70. .live.map .running .status {
  71. background-color: #7f7;
  72. }
  73. .live.map .running.warn .status {
  74. background-color: #ffed68;
  75. }
  76. .live.map .stopped .status {
  77. background-color: #f77;
  78. }
  79. .live.map .warn .queue {
  80. color: #f77;
  81. }
  82. .warn {
  83. -webkit-animation-name: flash;
  84. animation-name: flash;
  85. }
  86. .live.map .consumers {
  87. margin-right: 2px;
  88. }
  89. .live.map .consumers,
  90. .live.map .name {
  91. margin-top: 4px;
  92. }
  93. .live.map .consumers:after {
  94. content: "x";
  95. }
  96. .live.map .queue {
  97. display: block;
  98. float: left;
  99. width: 130px;
  100. height: 20px;
  101. font-size: 12px;
  102. margin-top: 2px;
  103. }
  104. .live.map .node g div {
  105. width: 200px;
  106. height: 40px;
  107. color: #fff;
  108. }
  109. .live.map .node g div span.consumers {
  110. display: inline-block;
  111. width: 20px;
  112. }
  113. .live.map .edgeLabel text {
  114. width: 50px;
  115. fill: #fff;
  116. }
  117. .live.map .edgePath path {
  118. stroke: #999;
  119. stroke-width: 1.5px;
  120. fill: #999;
  121. }
  122. </style>
  123. <body>
  124. <div class="live map">
  125. <svg><g/></svg>
  126. </div>
  127. <script>
  128. var workers = {
  129. "identifier": {
  130. "consumers": 2,
  131. "count": 20
  132. },
  133. "lost-and-found": {
  134. "consumers": 1,
  135. "count": 1,
  136. "inputQueue": "identifier",
  137. "inputThroughput": 50
  138. },
  139. "monitor": {
  140. "consumers": 1,
  141. "count": 0,
  142. "inputQueue": "identifier",
  143. "inputThroughput": 50
  144. },
  145. "meta-enricher": {
  146. "consumers": 4,
  147. "count": 9900,
  148. "inputQueue": "identifier",
  149. "inputThroughput": 50
  150. },
  151. "geo-enricher": {
  152. "consumers": 2,
  153. "count": 1,
  154. "inputQueue": "meta-enricher",
  155. "inputThroughput": 50
  156. },
  157. "elasticsearch-writer": {
  158. "consumers": 0,
  159. "count": 9900,
  160. "inputQueue": "geo-enricher",
  161. "inputThroughput": 50
  162. }
  163. };
  164. // Set up zoom support
  165. var svg = d3.select("svg"),
  166. inner = svg.select("g"),
  167. zoom = d3.zoom().on("zoom", function() {
  168. inner.attr("transform", d3.event.transform);
  169. });
  170. svg.call(zoom);
  171. var render = new dagreD3.render();
  172. // Left-to-right layout
  173. var g = new graphlib.Graph();
  174. g.setGraph({
  175. nodesep: 70,
  176. ranksep: 50,
  177. rankdir: "LR",
  178. marginx: 20,
  179. marginy: 20
  180. });
  181. function draw(isUpdate) {
  182. for (var id in workers) {
  183. var worker = workers[id];
  184. var className = worker.consumers ? "running" : "stopped";
  185. if (worker.count > 10000) {
  186. className += " warn";
  187. }
  188. var html = "<div>";
  189. html += "<span class=status></span>";
  190. html += "<span class=consumers>"+worker.consumers+"</span>";
  191. html += "<span class=name>"+id+"</span>";
  192. html += "<span class=queue><span class=counter>"+worker.count+"</span></span>";
  193. html += "</div>";
  194. g.setNode(id, {
  195. labelType: "html",
  196. label: html,
  197. rx: 5,
  198. ry: 5,
  199. padding: 0,
  200. class: className
  201. });
  202. if (worker.inputQueue) {
  203. g.setEdge(worker.inputQueue, id, {
  204. label: worker.inputThroughput + "/s",
  205. width: 40
  206. });
  207. }
  208. }
  209. inner.call(render, g);
  210. // Zoom and scale to fit
  211. var graphWidth = g.graph().width + 80;
  212. var graphHeight = g.graph().height + 40;
  213. var width = parseInt(svg.style("width").replace(/px/, ""));
  214. var height = parseInt(svg.style("height").replace(/px/, ""));
  215. var zoomScale = Math.min(width / graphWidth, height / graphHeight);
  216. var translateX = (width / 2) - ((graphWidth * zoomScale) / 2)
  217. var translateY = (height / 2) - ((graphHeight * zoomScale) / 2);
  218. var svgZoom = isUpdate ? svg.transition().duration(500) : svg;
  219. svgZoom.call(zoom.transform, d3.zoomIdentity.translate(translateX, translateY).scale(zoomScale));
  220. }
  221. // Do some mock queue status updates
  222. setInterval(function() {
  223. var stoppedWorker1Count = workers["elasticsearch-writer"].count;
  224. var stoppedWorker2Count = workers["meta-enricher"].count;
  225. for (var id in workers) {
  226. workers[id].count = Math.ceil(Math.random() * 3);
  227. if (workers[id].inputThroughput) workers[id].inputThroughput = Math.ceil(Math.random() * 250);
  228. }
  229. workers["elasticsearch-writer"].count = stoppedWorker1Count + Math.ceil(Math.random() * 100);
  230. workers["meta-enricher"].count = stoppedWorker2Count + Math.ceil(Math.random() * 100);
  231. draw(true);
  232. }, 1000);
  233. // Do a mock change of worker configuration
  234. setInterval(function() {
  235. workers["elasticsearch-monitor"] = {
  236. "consumers": 0,
  237. "count": 0,
  238. "inputQueue": "elasticsearch-writer",
  239. "inputThroughput": 50
  240. }
  241. }, 5000);
  242. document.addEventListener("DOMContentLoaded", draw);
  243. </script>