From da385615ded8918cff9b15f539d8665bae739ae2 Mon Sep 17 00:00:00 2001 From: lixucheng Date: Mon, 15 May 2017 11:45:08 +0800 Subject: [PATCH] [CE-44] Add analytics functions for user dashboard Include chaincode, fabric and infrastructure analytics functions. Change-Id: Iacb15c14cd373bb7e47ea77b126ca05c42700365 Signed-off-by: lixucheng --- user-dashboard/app.js | 1 + user-dashboard/modules/analytics.js | 171 ++++++++ .../public/css/dashboard/analytics.css | 25 ++ .../app/dashboard/analytics/chaincode/bar.js | 279 +++++++++++++ .../app/dashboard/analytics/chaincode/list.js | 89 +++++ .../app/dashboard/analytics/chaincode/pie.js | 184 +++++++++ .../js/app/dashboard/analytics/fabric.js | 318 +++++++++++++++ .../app/dashboard/analytics/infrastructure.js | 366 ++++++++++++++++++ .../js/app/dashboard/analytics/nochains.js | 12 + .../js/app/dashboard/analytics/overview.js | 57 +++ user-dashboard/public/js/app/kit/array.js | 12 + .../public/js/entry/analytics/chaincode.js | 6 + .../public/js/entry/analytics/fabric.js | 6 + .../js/entry/analytics/infrastructure.js | 6 + .../public/js/entry/analytics/nochains.js | 6 + .../public/js/entry/analytics/overview.js | 6 + .../analytics/chaincode/bardialog.html | 24 ++ .../analytics/chaincode/chaincode.html | 21 + .../analytics/chaincode/operation.html | 15 + .../analytics/chaincode/overlay.html | 3 + .../analytics/chaincode/overview.html | 51 +++ .../analytics/chaincode/piedialog.html | 5 + .../dashboard/analytics/fabric/block.html | 11 + .../dashboard/analytics/fabric/fabric.html | 81 ++++ .../dashboard/analytics/infrastructure.html | 59 +++ .../dashboard/analytics/overview.html | 59 +++ user-dashboard/routes/api.js | 41 ++ user-dashboard/routes/dashboard/analytics.js | 81 ++++ .../views/dashboard/analytics/chaincode.pug | 77 ++++ .../views/dashboard/analytics/fabric.pug | 21 + .../dashboard/analytics/includes/catalog.pug | 31 ++ .../analytics/includes/chainSelector.pug | 13 + .../dashboard/analytics/infrastructure.pug | 21 + .../views/dashboard/analytics/noChains.pug | 14 + .../views/dashboard/analytics/overview.pug | 73 ++++ 35 files changed, 2245 insertions(+) create mode 100644 user-dashboard/modules/analytics.js create mode 100644 user-dashboard/public/css/dashboard/analytics.css create mode 100644 user-dashboard/public/js/app/dashboard/analytics/chaincode/bar.js create mode 100644 user-dashboard/public/js/app/dashboard/analytics/chaincode/list.js create mode 100644 user-dashboard/public/js/app/dashboard/analytics/chaincode/pie.js create mode 100644 user-dashboard/public/js/app/dashboard/analytics/fabric.js create mode 100644 user-dashboard/public/js/app/dashboard/analytics/infrastructure.js create mode 100644 user-dashboard/public/js/app/dashboard/analytics/nochains.js create mode 100644 user-dashboard/public/js/app/dashboard/analytics/overview.js create mode 100644 user-dashboard/public/js/app/kit/array.js create mode 100644 user-dashboard/public/js/entry/analytics/chaincode.js create mode 100644 user-dashboard/public/js/entry/analytics/fabric.js create mode 100644 user-dashboard/public/js/entry/analytics/infrastructure.js create mode 100644 user-dashboard/public/js/entry/analytics/nochains.js create mode 100644 user-dashboard/public/js/entry/analytics/overview.js create mode 100644 user-dashboard/public/js/resources/dashboard/analytics/chaincode/bardialog.html create mode 100644 user-dashboard/public/js/resources/dashboard/analytics/chaincode/chaincode.html create mode 100644 user-dashboard/public/js/resources/dashboard/analytics/chaincode/operation.html create mode 100644 user-dashboard/public/js/resources/dashboard/analytics/chaincode/overlay.html create mode 100644 user-dashboard/public/js/resources/dashboard/analytics/chaincode/overview.html create mode 100644 user-dashboard/public/js/resources/dashboard/analytics/chaincode/piedialog.html create mode 100644 user-dashboard/public/js/resources/dashboard/analytics/fabric/block.html create mode 100644 user-dashboard/public/js/resources/dashboard/analytics/fabric/fabric.html create mode 100644 user-dashboard/public/js/resources/dashboard/analytics/infrastructure.html create mode 100644 user-dashboard/public/js/resources/dashboard/analytics/overview.html create mode 100644 user-dashboard/routes/dashboard/analytics.js create mode 100644 user-dashboard/views/dashboard/analytics/chaincode.pug create mode 100644 user-dashboard/views/dashboard/analytics/fabric.pug create mode 100644 user-dashboard/views/dashboard/analytics/includes/catalog.pug create mode 100644 user-dashboard/views/dashboard/analytics/includes/chainSelector.pug create mode 100644 user-dashboard/views/dashboard/analytics/infrastructure.pug create mode 100644 user-dashboard/views/dashboard/analytics/noChains.pug create mode 100644 user-dashboard/views/dashboard/analytics/overview.pug diff --git a/user-dashboard/app.js b/user-dashboard/app.js index 8cea64515..0beee4fe9 100644 --- a/user-dashboard/app.js +++ b/user-dashboard/app.js @@ -29,6 +29,7 @@ app.use("/dashboard", require("./routes/dashboard/filter")); app.use("/dashboard", require("./routes/dashboard/home")); app.use("/dashboard", require("./routes/dashboard/chain")); app.use("/dashboard", require("./routes/dashboard/contract")); +app.use("/dashboard", require("./routes/dashboard/analytics")); app.use(function(err, req, res, next) { res.status(err.status || 500); res.send(err.message); diff --git a/user-dashboard/modules/analytics.js b/user-dashboard/modules/analytics.js new file mode 100644 index 000000000..9eaa1f2eb --- /dev/null +++ b/user-dashboard/modules/analytics.js @@ -0,0 +1,171 @@ +/** + * Created by lixuc on 2017/5/12. + */ +var rp = require("request-promise"); +var config = require("./configuration"); +var dt = require("../kit/date-tool"); + +function analytics(chainId) { + this.chainId = chainId; +} +analytics.prototype = { + BaseURL: "http://" + config.RESTful_Server + config.RESTful_BaseURL, + overview: function() { + return new Promise(function(resolve, reject) { + rp({ + uri: this.BaseURL + "analytics/overview?cluster_id=" + this.chainId, + json: true + }).then(function(response) { + if (response.success) { + resolve({ + success: true, + statistic: { + health: response.result.health, + chaincodes: response.result.chaincode_number, + blocks: response.result.block_number, + runTime: dt.parse(response.result.run_time) + } + }); + } else { + var e = new Error(response.message); + e.status = 503; + throw e; + } + }).catch(function(err) { + reject({ + success: false, + message: (err.status == 503 && err.message) || "System maintenance, please try again later!" + }); + }); + }.bind(this)); + }, + chaincodeList: function() { + return new Promise(function(resolve, reject) { + rp({ + uri: this.BaseURL + "analytics/chaincode/list?cluster_id=" + this.chainId, + json: true + }).then(function(response) { + if (response.success) { + var chaincodes = response.result.chaincodes; + resolve({ + success: true, + chaincodes: chaincodes + }); + } else { + var e = new Error(response.message); + e.status = 503; + throw e; + } + }).catch(function(err) { + reject({ + success: false, + message: (err.status == 503 && err.message) || "System maintenance, please try again later!" + }); + }); + }.bind(this)); + }, + chaincodeOperations: function(id, timestamp) { + return new Promise(function(resolve, reject) { + var api = "analytics/chaincode/operations?cluster_id=" + this.chainId + "&chaincode_id=" + id; + if (timestamp) api += "&since_ts=" + timestamp; + rp({ + uri: this.BaseURL + api, + json: true + }).then(function(response) { + if (response.success) { + var operations = response.result.operations; + for (var i in operations) { + operations[i]["formattedTime"] = dt.format(Math.round(operations[i].timestamp * 1000), 0); + } + resolve({ + success: true, + operations: operations + }); + } else { + var e = new Error(response.message); + e.status = 503; + throw e; + } + }).catch(function(err) { + reject({ + success: false, + message: (err.status == 503 && err.message) || "System maintenance, please try again later!" + }); + }); + }.bind(this)); + }, + fabric: function(timestamp) { + return new Promise(function(resolve, reject) { + var api = "analytics/fabric?cluster_id=" + this.chainId; + if (timestamp) api += "&since_ts=" + timestamp; + rp({ + uri: this.BaseURL + api, + json: true + }).then(function(response) { + if (response.success) { + var blocks = response.result.blocks; + for (var i in blocks) { + blocks[i]["formattedTime"] = dt.format(Math.round(blocks[i].timestamp * 1000), 0); + blocks[i]["parsedTime"] = dt.parse2Str(blocks[i].block_time); + } + resolve({ + success: true, + health: response.result.health, + blocks: blocks + }); + } else { + var e = new Error(response.message); + e.status = 503; + throw e; + } + }).catch(function(err) { + reject({ + success: false, + message: (err.status == 503 && err.message) || "System maintenance, please try again later!" + }); + }); + }.bind(this)); + }, + infrastructure: function(size) { + return new Promise(function(resolve, reject) { + rp({ + uri: this.BaseURL + "analytics/infrastructure?cluster_id=" + this.chainId + "&size=" + (size || 1), + json: true + }).then(function(response) { + if (response.success) { + var statis = []; + var statistics = response.result.statistics; + for (var i in statistics) { + statis.push({ + cpu_percentage: statistics[i]["cpu_percentage"], + memory_usage: statistics[i]["memory_usage"], + memory_limit: statistics[i]["memory_limit"], + memory_percentage: statistics[i]["memory_percentage"], + block_read: statistics[i]["block_read"], + block_write: statistics[i]["block_write"], + network_rx: statistics[i]["network_rx"], + network_tx: statistics[i]["network_tx"], + avg_latency: statistics[i]["avg_latency"], + timestamp: statistics[i]["timestamp"].substr(11) + }); + } + resolve({ + success: true, + health: response.result.health, + statistics: statis + }); + } else { + var e = new Error(response.message); + e.status = 503; + throw e; + } + }).catch(function(err) { + reject({ + success: false, + message: (err.status == 503 && err.message) || "System maintenance, please try again later!" + }); + }); + }.bind(this)); + } +}; +module.exports = analytics; \ No newline at end of file diff --git a/user-dashboard/public/css/dashboard/analytics.css b/user-dashboard/public/css/dashboard/analytics.css new file mode 100644 index 000000000..d7137f89c --- /dev/null +++ b/user-dashboard/public/css/dashboard/analytics.css @@ -0,0 +1,25 @@ +@CHARSET "UTF-8"; +.ds-analytics{margin-top:20px;font-family:"Helvetica Neue",Arial,sans-serif;} +.dropdown-button{background:#fff none repeat scroll 0 0;border:1px solid rgba(0, 0, 0, 0.1);} +.warning_link{color:#2d7091;border-bottom:1px dotted;} +.warning_link:hover{color:#2d7091;} +.perfect-health-circle{width:155px;height:155px;border:8px solid rgb(76, 175, 80);border-radius:50%;} +.good-health-circle{width:155px;height:155px;border:8px solid rgb(139, 195, 74);border-radius:50%;} +.attention-health-circle{width:155px;height:155px;border:8px solid rgb(255, 152, 0);border-radius:50%;} +.perfect-health-square{width:145px;height:140px;border:1px solid rgb(76, 175, 80);border-top-width:5px;padding:5px;} +.good-health-square{width:145px;height:140px;border:1px solid rgb(139, 195, 74);border-top-width:5px;padding:5px;} +.attention-health-square{width:145px;height:140px;border:1px solid rgb(255, 152, 0);border-top-width:5px;padding:5px;} +.perfect-label{color:rgb(76, 175, 80);font-size:35px;font-style:normal;font-weight:bold;} +.good-label{color:rgb(139, 195, 74);font-size:40px;font-style:normal;font-weight:bold;} +.attention-label{color:rgb(255, 152, 0);font-size:30px;font-style:normal;font-weight:bold;} +.vertical-line{width:1px;height:130px;border-right:1px solid #ccc;} +.font-size-15{font-size:15px;} +.font-size-20{font-size:20px;} +.font-size-50{font-size:50px;} +.health-label-box{width:100%;height:100%;margin-top:-15px;} +.invokeTimes, .responseTime{position:absolute;} +.empty{color:#b2b2b2;line-height:80px;font-style:italic;font-size:25px;} +.iconrefresh{position:absolute;width:20px;height:20px;top:50%;left:50%;margin-top:-10px;margin-left:-10px;} +.td-top-border{border-bottom:0;border-top:1px solid #ddd;} +.animation-td-div{position:relative;opacity:0;bottom:36px;} +.avgBlockTime, .minBlockTime, .maxBlockTime{margin-top:15px;} \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/analytics/chaincode/bar.js b/user-dashboard/public/js/app/dashboard/analytics/chaincode/bar.js new file mode 100644 index 000000000..7a716708d --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/analytics/chaincode/bar.js @@ -0,0 +1,279 @@ +/** + * Created by lixuc on 2017/5/12. + */ +define([ + "jquery", + "echarts", + "app/kit/ui", + "plugin/text!resources/dashboard/analytics/chaincode/bardialog.html", + "plugin/text!resources/dashboard/analytics/chaincode/operation.html", + "plugin/text!resources/dashboard/analytics/chaincode/overlay.html", + "app/kit/array", + "lodash" +], function($, echarts, UI, bardialog, operation, overlay) { + var ui = new UI(); + var $dialog; + var $barDialog; + var $barPanel; + var $operationContainer; + var $canvas; + var interval = 5000; + var timer; + var sampling = 10; + //柱图数据集 + var xAxisData = [], series = []; + var responseTimes = []; + //最新的时间戳 + var latest_ts; + + function bar() { + /** + * 根据legend的颜色,创建tooltip中每个legend对应的小图标 + */ + function symbol(color) { + return ""; + } + this.option = { + tooltip: { + trigger: "axis", + formatter: function(params) { + var N = params.length; + if (N) { + var tip = params[0].seriesName + "
"; + for (var i=0; i=0; i--) { + var responseTime = operations[i]["response_time"]; + if (parseFloat(responseTime) >= 1) { + responseTime = parseFloat(responseTime.toFixed(2)) + "s"; + } else { + responseTime = (parseFloat(responseTime.toFixed(3)) * 1000) + "ms"; + } + $operationContainer.append(opeInfo({ + name: operations[i]["name"], + func: operations[i]["function"], + responseTime: responseTime, + operationTime: operations[i]["formattedTime"] + })); + } + _self.chart = echarts.init($canvas[0]); + for (var i in operations) { + _self.writeData(operations[i]); + } + _self.refreshChart(); + timer = setTimeout(function() { _self.refresh(chainId, chaincodeId); }, interval); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + $overlay.remove(); + ui.dialog.error(errorThrown); + } + }); + } + } + bar.prototype = { + show: function($label, chainId, chaincodeId) { + var _self = this; + $dialog = $(bardialog); + $dialog.on({ + "show.uk.modal": function() { + _self.init(chainId, chaincodeId); + }, + "hide.uk.modal": function() { + clearTimeout(timer); + $(this).remove(); + var avgResponseTime = _self.avg(); + avgResponseTime += "s"; + if (avgResponseTime != $label.text()) { + $label.animate({ + opacity: 0, + marginTop: 50 + }, "slow", function() { + $(this).text(avgResponseTime).css("marginTop", -50).animate({ + opacity: 1, + marginTop: 0 + }, "slow"); + }); + } + } + }); + $("body").append($dialog); + $barDialog = UIkit.modal($dialog); + $barDialog.options.center = true; + $barDialog.show(); + }, + writeData: function(operation) { + if (operation["name"] == "Invoke") { + var responseTime = parseFloat(operation["response_time"].toFixed(3)) * 1000; + xAxisData.extrude(sampling, operation["formattedTime"].substring(0, 8)); + series.extrude(sampling, { + value: responseTime, + label: operation["name"] + " - " + operation["function"] + " : " + responseTime + "ms" + }); + } + responseTimes.push(operation["response_time"]); + latest_ts = operation["timestamp"]; + }, + refreshChart: function() { + this.option.xAxis.data = xAxisData; + this.option.series.data = series; + this.chart.setOption(this.option); + }, + avg: function() { + var avg = 0; + for (var i=0; i 0) avg = avg / responseTimes.length; + return parseFloat(avg.toFixed(2)); + }, + refresh: function(chainId, chaincodeId) { + var _self = this; + clearTimeout(timer); + $.ajax({ + type: "GET", + url: "/api/chain/" + chainId + "/analytics/chaincode/" + chaincodeId + "/operations", + data: { + timestamp: latest_ts + }, + dataType: "json", + success: function(data) { + if (data.success) { + var operations = data.operations; + if (operations.length) { + for (var i=0; i= 1) { + responseTime = parseFloat(responseTime.toFixed(2)) + "s"; + } else { + responseTime = (parseFloat(responseTime.toFixed(3)) * 1000) + "ms"; + } + var operationTime = operations[i]["formattedTime"]; + + var $tr = $("").prependTo($operationContainer); + var $opTD = $("").appendTo($tr); + var $functionTD = $("").appendTo($tr); + var $responseTimeTD = $("").appendTo($tr); + var $operationTimeTD = $("").appendTo($tr); + + $("
" + op + "
") + .appendTo($opTD) + .animate({ + opacity: 1, + bottom: 0 + }, "slow"); + setTimeout((function() { + $("
" + func + "
") + .appendTo($functionTD) + .animate({ + opacity: 1, + bottom: 0 + }, "slow"); + })(func, $functionTD), 200); + setTimeout((function() { + $("
" + responseTime + "
") + .appendTo($responseTimeTD) + .animate({ + opacity: 1, + bottom: 0 + }, "slow"); + })(responseTime, $responseTimeTD), 400); + setTimeout((function() { + $("
" + operationTime + "
") + .appendTo($operationTimeTD) + .animate({ + opacity: 1, + bottom: 0 + }, "slow"); + })(operationTime, $operationTimeTD), 600); + //图表刷新 + _self.writeData(operations[i]); + } + _self.refreshChart(); + } + timer = setTimeout(function() { _self.refresh(chainId, chaincodeId); }, interval); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + ui.dialog.error(errorThrown); + } + }); + } + }; + return bar; +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/analytics/chaincode/list.js b/user-dashboard/public/js/app/dashboard/analytics/chaincode/list.js new file mode 100644 index 000000000..413f36005 --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/analytics/chaincode/list.js @@ -0,0 +1,89 @@ +/** + * Created by lixuc on 2017/5/12. + */ +define([ + "jquery", + "app/kit/ui", + "app/kit/overlayer", + "app/dashboard/profile", + "app/dashboard/analytics/chaincode/pie", + "app/dashboard/analytics/chaincode/bar", + "plugin/text!resources/dashboard/analytics/chaincode/overview.html", + "plugin/text!resources/dashboard/analytics/chaincode/chaincode.html", + "uikit", + "lodash" +], function($, UI, Overlayer, Profile, Pie, Bar, overview, chaincode) { + $(function() { + new Profile(); + + var ui = new UI(); + var pie = new Pie(); + var bar = new Bar(); + var $chain = $("#chainSelector button label"); + var $chainList = $("#chainSelector ul"); + var $analyticsPanel = $("#analyticsPanel"); + + function load(id) { + var overlayer = new Overlayer(); + overlayer.show(); + $.ajax({ + type: "GET", + url: "/api/chain/" + id + "/analytics/chaincode/list", + dataType: "json", + success: function(data) { + overlayer.hide(); + $analyticsPanel.empty(); + if (data.success) { + var chaincodes = data.chaincodes; + if (chaincodes.length == 0) { + $analyticsPanel.append(""); + } else { + var invokeTimes = 0, responseTime = 0; + for (var i in chaincodes) { + invokeTimes += parseInt(chaincodes[i].invoke_times); + responseTime += parseFloat(chaincodes[i].avg_response_time); + } + var overviewInfo = _.template(overview); + $analyticsPanel.append(overviewInfo({ + chaincodeNum: chaincodes.length, + invokeTimes: invokeTimes, + avgResponseTime: parseFloat((responseTime / chaincodes.length).toFixed(2)) + })); + var $chaincodesContainer = $analyticsPanel.find("tbody"); + var chaincodeInfo = _.template(chaincode); + for (var i in chaincodes) { + $chaincodesContainer.append(chaincodeInfo({ + id: chaincodes[i].id, + name: chaincodes[i].name, + invokeTimes: chaincodes[i].invoke_times, + responseTime: parseFloat(chaincodes[i].avg_response_time.toFixed(2)) + })); + } + } + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + overlayer.hide(); + $analyticsPanel.empty(); + ui.dialog.error(errorThrown); + } + }); + } + $chainList.find("li").on("click", function() { + $(this).parents(".uk-dropdown").hide(); + $chain.data("id", $(this).data("id")).html($(this).data("name")); + load($(this).data("id")); + }); + $analyticsPanel.on("click", "tbody button", function() { + if ($(this).data("type") == "functions") { + var $label = $(this).closest("tr").find("label.invokeTimes"); + pie.show($label, $chain.data("id"), $(this).data("id")); + } else if ($(this).data("type") == "responseTime") { + var $label = $(this).closest("tr").find("label.responseTime"); + bar.show($label, $chain.data("id"), $(this).data("id")); + } + }); + }); +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/analytics/chaincode/pie.js b/user-dashboard/public/js/app/dashboard/analytics/chaincode/pie.js new file mode 100644 index 000000000..4fbcd9040 --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/analytics/chaincode/pie.js @@ -0,0 +1,184 @@ +/** + * Created by lixuc on 2017/5/12. + */ +define([ + "jquery", + "echarts", + "app/kit/ui", + "plugin/text!resources/dashboard/analytics/chaincode/piedialog.html", + "plugin/text!resources/dashboard/analytics/chaincode/overlay.html" +], function($, echarts, UI, piedialog, overlay) { + var ui = new UI(); + var $dialog; + var $pieDialog; + var $piePanel; + var interval = 5000; + var timer; + //饼图数据集 + var legends = [], series = []; + //最新的时间戳 + var latest_ts; + + function pie() { + this.option = { + title: { + text: "Invoke Functions", + x: "center" + }, + tooltip: { + trigger: "item", + formatter: "{a}
{b}: {c} ({d}%)" + }, + legend: { + orient: "vertical", + left: "left", + data: legends + }, + series: { + name: "Function", + type: "pie", + radius: "55%", + center: ["50%", "60%"], + data: series, + itemStyle: { + emphasis: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: "rgba(0, 0, 0, 0.5)" + } + } + } + }; + this.init = function(chainId, chaincodeId) { + var _self = this; + var $overlay = $(overlay); + $piePanel = $dialog.find(".uk-panel"); + $piePanel.append($overlay); + //清空当前数据集 + legends = []; + series = []; + latest_ts = ""; + $.ajax({ + type: "GET", + url: "/api/chain/" + chainId + "/analytics/chaincode/" + chaincodeId + "/operations", + dataType: "json", + success: function(data) { + $overlay.remove(); + if (data.success) { + var operations = data.operations; + for (var i in operations) { + var ope = operations[i]["name"]; + var func = operations[i]["function"]; + if (ope == "Invoke") { + _self.writeData(func); + } + latest_ts = operations[i]["timestamp"]; + } + _self.chart = echarts.init($piePanel[0]); + _self.refreshChart(); + timer = setTimeout(function() { _self.refresh(chainId, chaincodeId); }, interval); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + $overlay.remove(); + ui.dialog.error(errorThrown); + } + }); + } + } + pie.prototype = { + show: function($label, chainId, chaincodeId) { + var _self = this; + $dialog = $(piedialog); + $dialog.on({ + "show.uk.modal": function() { + _self.init(chainId, chaincodeId); + }, + "hide.uk.modal": function() { + clearTimeout(timer); + $(this).remove(); + var invokeTimes = _self.sum(); + if (invokeTimes != $label.text()) { + $label.animate({ + opacity: 0, + marginTop: 50 + }, "slow", function() { + $(this).text(invokeTimes).css("marginTop", -50).animate({ + opacity: 1, + marginTop: 0 + }, "slow"); + }); + } + } + }); + $("body").append($dialog); + $pieDialog = UIkit.modal($dialog); + $pieDialog.options.center = true; + $pieDialog.show(); + }, + writeData: function(func) { + if (!legends.includes(func)) { + legends.push(func); + series.push({ + name: func, + value: 1 + }); + } else { + for (var i in series) { + if (series[i].name == func) { + series[i].value++; + } + } + } + }, + refreshChart: function() { + this.option.legend.data = legends.sort(); + this.option.series.data = series; + this.chart.setOption(this.option); + }, + sum: function() { + var sum = 0; + for (var i in series) { + sum += series[i].value; + } + return sum; + }, + refresh: function(chainId, chaincodeId) { + var _self = this; + clearTimeout(timer); + $.ajax({ + type: "GET", + url: "/api/chain/" + chainId + "/analytics/chaincode/" + chaincodeId + "/operations", + data: { + timestamp: latest_ts + }, + dataType: "json", + success: function(data) { + if (data.success) { + var operations = data.operations; + if (operations.length) { + for (var i in operations) { + var ope = operations[i]["name"]; + var func = operations[i]["function"]; + if (ope == "Invoke") { + _self.writeData(func); + } + latest_ts = operations[i]["timestamp"]; + } + _self.refreshChart(); + } + timer = setTimeout(function() { _self.refresh(chainId, chaincodeId); }, interval); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + ui.dialog.error(errorThrown); + } + }); + } + }; + return pie; +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/analytics/fabric.js b/user-dashboard/public/js/app/dashboard/analytics/fabric.js new file mode 100644 index 000000000..14f60dfe8 --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/analytics/fabric.js @@ -0,0 +1,318 @@ +/** + * Created by lixuc on 2017/5/15. + */ +define([ + "jquery", + "echarts", + "app/kit/ui", + "app/kit/overlayer", + "app/kit/common", + "app/dashboard/profile", + "plugin/text!resources/dashboard/analytics/fabric/fabric.html", + "plugin/text!resources/dashboard/analytics/fabric/block.html", + "app/kit/array", + "uikit", + "lodash" +], function($, echarts, UI, Overlayer, Common, Profile, Fabric, Block) { + $(function() { + new Profile(); + + var ui = new UI(); + var common = new Common(); + var $chain = $("#chainSelector button label"); + var $chainList = $("#chainSelector ul"); + var $analyticsPanel; + var $healthLabel; + var $blockNumLabel; + var $avgBlockTimeLabel; + var $minBlockTimeLabel; + var $maxBlockTimeLabel; + var $blocksContainer; + var $canvas; + var interval = 5000; + var timer; + var sampling = 15; + //线图数据集 + var xAxisData = [], series = []; + var blockTimes = []; + //最新的时间戳 + var latest_ts; + + function fabric() { + $analyticsPanel = $("#analyticsPanel"); + /** + * 根据legend的颜色,创建tooltip中每个legend对应的小图标 + */ + function symbol(color) { + return ""; + } + this.option = { + tooltip: { + trigger: "axis", + formatter: function(params) { + var N = params.length; + if (N) { + var tip = params[0].seriesName + "
"; + for (var i=0; i=0; i--) { + $blocksContainer.append(blockInfo({ + block: blocks[i]["no"], + blockTime: blocks[i]["parsedTime"], + createTime: blocks[i]["formattedTime"] + })); + } + //加载图表数据 + _self.chart = echarts.init($canvas[0]); + _self.refreshChart(); + timer = setTimeout(function() { _self.refresh(id); }, interval); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + overlayer.hide(); + $analyticsPanel.empty(); + ui.dialog.error(errorThrown); + } + }); + }, + writeData: function(block) { + //第0个block的block time是0,第1个block的block time是预存的,所以都不算 + //所以从第2个block开始计算 + if (parseInt(block["no"]) > 1) { + xAxisData.extrude(sampling, block["no"]+""); + series.extrude(sampling, { + value: block["block_time"], + label: block["parsedTime"] + }); + blockTimes.push(block["block_time"]); + } + latest_ts = block["timestamp"]; + }, + refreshChart: function() { + this.option.xAxis.data = xAxisData; + this.option.series.data = series; + this.chart.setOption(this.option); + }, + avg: function() { + var avg = 0; + for (var i=0; i 0) avg = avg / blockTimes.length; + return this.parse(avg); + }, + min: function() { + var min = blockTimes.length > 0 ? Number.MAX_VALUE : 0; + for (var i=0; i 0 ? Number.MIN_VALUE : 0; + for (var i=0; i max) max = blockTimes[i]; + } + return this.parse(max); + }, + parse: function(val) { + var parsedTime = common.parseTime(val).split(" "); + if (parsedTime.length == 1) { + return "" + parsedTime[0].substr(0, parsedTime[0].length - 1) + "" + + "\n" + parsedTime[0].substr(-1) + ""; + } else { + return "" + parsedTime[0].substr(0, parsedTime[0].length - 1) + "" + + "\n" + parsedTime[0].substr(-1) + "" + + "" + parsedTime[1].substr(0, parsedTime[1].length - 1) + "" + + "\n" + parsedTime[1].substr(-1) + ""; + } + }, + refreshOverviewLabel: function($label, newValue) { + if ($label.html() != newValue) { + var top = parseInt($label.css("marginTop")); + $label.animate({ + opacity: 0, + marginTop: 100 + }, "slow", function() { + $(this).html(newValue).css("marginTop", -100).animate({ + opacity: 1, + marginTop: top + }, "slow"); + }); + } + }, + refresh: function(id) { + var _self = this; + clearTimeout(timer); + $.ajax({ + type: "GET", + url: "/api/chain/" + id + "/analytics/fabric", + data: { + timestamp: latest_ts + }, + dataType: "json", + success: function(data) { + if (data.success) { + var blocks = data.blocks; + var blockNum = blocks.length; + if (blockNum) { + for (var i=0; i").prependTo($blocksContainer); + var $blockTD = $("").appendTo($tr); + var $blockTimeTD = $("").appendTo($tr); + var $createTimeTD = $("").appendTo($tr); + $("
" + block["no"] + "
") + .appendTo($blockTD) + .animate({ + opacity: 1, + bottom: 0 + }, "slow"); + setTimeout((function() { + $("
" + block["parsedTime"] + "
") + .appendTo($blockTimeTD) + .animate({ + opacity: 1, + bottom: 0 + }, "slow"); + })(block, $blockTimeTD), 200); + setTimeout((function() { + $("
" + block["formattedTime"] + "
") + .appendTo($createTimeTD) + .animate({ + opacity: 1, + bottom: 0 + }, "slow"); + })(block, $createTimeTD), 400); + //动态添加图表数据 + _self.writeData(block); + } + //图表刷新 + _self.refreshChart(); + //overview视图刷新 + var health = data.health; + health = health >= 90 ? "Perfect" : (health >= 80 ? "Good" : "Attention"); + _self.refreshOverviewLabel($healthLabel, health); + _self.refreshOverviewLabel($blockNumLabel, $blocksContainer.children().length); + _self.refreshOverviewLabel($avgBlockTimeLabel, _self.avg()); + _self.refreshOverviewLabel($minBlockTimeLabel, _self.min()); + _self.refreshOverviewLabel($maxBlockTimeLabel, _self.max()); + } + timer = setTimeout(function() { _self.refresh(id); }, interval); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + ui.dialog.error(errorThrown); + } + }); + }, + stopRefresh: function() { + clearTimeout(timer); + } + }; + var fab = new fabric(); + fab.load($chain.data("id")); + $chainList.find("li").on("click", function() { + fab.stopRefresh(); + $(this).parents(".uk-dropdown").hide(); + $chain.data("id", $(this).data("id")).html($(this).data("name")); + fab.load($(this).data("id")); + }); + }); +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/analytics/infrastructure.js b/user-dashboard/public/js/app/dashboard/analytics/infrastructure.js new file mode 100644 index 000000000..d120aa25d --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/analytics/infrastructure.js @@ -0,0 +1,366 @@ +/** + * Created by lixuc on 2017/5/15. + */ +define([ + "jquery", + "echarts", + "app/kit/ui", + "app/kit/overlayer", + "app/kit/common", + "app/dashboard/profile", + "plugin/text!resources/dashboard/analytics/infrastructure.html", + "app/kit/array", + "uikit", + "lodash" +], function($, echarts, UI, Overlayer, Common, Profile, Infrastructure) { + $(function() { + new Profile(); + + var ui = new UI(); + var common = new Common(); + var $chain = $("#chainSelector button label"); + var $chainList = $("#chainSelector ul"); + var $analyticsPanel; + var $healthLabel; + var $cpuUsageLabel; + var $memoryUsageLabel; + var $latencyLabel; + var $canvas; + var interval = 5000; + var timer; + var sampling = 10; + //图表数据集 + var dataSet = [ + { + xAxisData: [], + yAxisLabel: "CPU(%)", + series: { + "Percentage": [] + } + }, + { + xAxisData: [], + yAxisLabel: "Memory", + series: { + "Usage": [], + "Limit": [] + }, + originData: [] //原始数据,用来计算单位 + }, + { + xAxisData: [], + yAxisLabel: "Block", + series: { + "Read": [], + "Write": [] + }, + originData: [] //原始数据,用来计算单位 + }, + { + xAxisData: [], + yAxisLabel: "Network", + series: { + "RX": [], + "TX": [] + }, + originData: [] //原始数据,用来计算单位 + } + ]; + //最新的时间戳 + var latest_ts; + + function infrastructure() { + $analyticsPanel = $("#analyticsPanel"); + //所有坐标系的位置和大小 + var grids = [ + {left: "7%", top: "7%", width: "38%", height: "35%"}, + {left: "55%", top: "7%", width: "38%", height: "35%"}, + {left: "7%", top: "58%", width: "38%", height: "35%"}, + {left: "55%", top: "58%", width: "38%", height: "35%"} + ]; + //所有坐标系对应的legend名称 + var legends = [ + { data: ["Percentage"] }, + { data: ["Usage", "Limit"] }, + { data: ["Read", "Write"] }, + { data: ["RX", "TX"] } + ]; + //所有坐标系的x,y轴 + var xAxis = [], yAxis = []; + //所有坐标系的线图 + var series = []; + /** + * 根据legend的颜色,创建tooltip中每个legend对应的小图标 + */ + function symbol(color) { + return ""; + } + //初始化所有坐标系的标题,图例,x,y轴和线图 + echarts.util.each(grids, function(grid, idx) { + legends[idx].right = 100 - (parseFloat(grid.left) + parseFloat(grid.width)) + "%"; + legends[idx].top = parseFloat(grid.top) - 6 + "%"; + + xAxis.push({ + gridIndex: idx, + type: "category", + name: "Time", + nameGap: 5, + nameTextStyle: { fontSize: 10 }, + axisLabel: { textStyle: { fontSize: 10 } }, + data: dataSet[idx]["xAxisData"] + }); + yAxis.push({ + gridIndex: idx, + type: "value", + name: dataSet[idx]["yAxisLabel"], + nameGap: 10, + nameTextStyle: { fontSize: 10 }, + axisLabel: { textStyle: { fontSize: 10 } } + }); + var legend = legends[idx].data; + for (var i=0; i"; + for (var i=0; i= 90 ? "Perfect" : (health >= 80 ? "Good" : "Attention"); + var cpuUsage = parseFloat(statistics[0]["cpu_percentage"].toFixed(2)); + var memoryUsage = parseFloat(statistics[0]["memory_percentage"].toFixed(2)); + var latency = parseFloat(statistics[0]["avg_latency"].toFixed(2)); + _self.refreshOverviewLabel($healthLabel, health); + _self.refreshOverviewLabel($cpuUsageLabel, cpuUsage); + _self.refreshOverviewLabel($memoryUsageLabel, memoryUsage); + _self.refreshOverviewLabel($latencyLabel, latency); + + latest_ts = statistics[0]["timestamp"]; + } + timer = setTimeout(function() { _self.refresh(id); }, interval); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + ui.dialog.error(errorThrown); + } + }); + }, + stopRefresh: function() { + clearTimeout(timer); + }, + emptyDataSet: function() { + for (var i=0; i= max) this.splice(0, items.length); + for (var i in items) { + this.push(items[i]); + } + }, + enumerable: false + }); +}); \ No newline at end of file diff --git a/user-dashboard/public/js/entry/analytics/chaincode.js b/user-dashboard/public/js/entry/analytics/chaincode.js new file mode 100644 index 000000000..0c24c4529 --- /dev/null +++ b/user-dashboard/public/js/entry/analytics/chaincode.js @@ -0,0 +1,6 @@ +/** + * Created by lixuc on 2017/5/12. + */ +requirejs(["../../common"], function(common) { + requirejs(["app/dashboard/analytics/chaincode/list"]); +}); \ No newline at end of file diff --git a/user-dashboard/public/js/entry/analytics/fabric.js b/user-dashboard/public/js/entry/analytics/fabric.js new file mode 100644 index 000000000..9f04b03df --- /dev/null +++ b/user-dashboard/public/js/entry/analytics/fabric.js @@ -0,0 +1,6 @@ +/** + * Created by lixuc on 2017/5/15. + */ +requirejs(["../../common"], function(common) { + requirejs(["app/dashboard/analytics/fabric"]); +}); \ No newline at end of file diff --git a/user-dashboard/public/js/entry/analytics/infrastructure.js b/user-dashboard/public/js/entry/analytics/infrastructure.js new file mode 100644 index 000000000..306d7d96c --- /dev/null +++ b/user-dashboard/public/js/entry/analytics/infrastructure.js @@ -0,0 +1,6 @@ +/** + * Created by lixuc on 2017/5/15. + */ +requirejs(["../../common"], function(common) { + requirejs(["app/dashboard/analytics/infrastructure"]); +}); \ No newline at end of file diff --git a/user-dashboard/public/js/entry/analytics/nochains.js b/user-dashboard/public/js/entry/analytics/nochains.js new file mode 100644 index 000000000..230735156 --- /dev/null +++ b/user-dashboard/public/js/entry/analytics/nochains.js @@ -0,0 +1,6 @@ +/** + * Created by lixuc on 2017/5/12. + */ +requirejs(["../../common"], function(common) { + requirejs(["app/dashboard/analytics/nochains"]); +}); \ No newline at end of file diff --git a/user-dashboard/public/js/entry/analytics/overview.js b/user-dashboard/public/js/entry/analytics/overview.js new file mode 100644 index 000000000..ab9eb9840 --- /dev/null +++ b/user-dashboard/public/js/entry/analytics/overview.js @@ -0,0 +1,6 @@ +/** + * Created by lixuc on 2017/5/12. + */ +requirejs(["../../common"], function(common) { + requirejs(["app/dashboard/analytics/overview"]); +}); \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/analytics/chaincode/bardialog.html b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/bardialog.html new file mode 100644 index 000000000..6ce74f0d9 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/bardialog.html @@ -0,0 +1,24 @@ +
+
+
+
+
+ + + + + + + + + + +
OperationFunctionResponse TimeOperation Time
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/analytics/chaincode/chaincode.html b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/chaincode.html new file mode 100644 index 000000000..19d957382 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/chaincode.html @@ -0,0 +1,21 @@ + + ${name} + + + + + + + + + + + \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/analytics/chaincode/operation.html b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/operation.html new file mode 100644 index 000000000..4dfeb11a9 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/operation.html @@ -0,0 +1,15 @@ + + +
${name}
+ + +
${func}
+ + +
${responseTime}
+ + +
${operationTime}
+ + \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/analytics/chaincode/overlay.html b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/overlay.html new file mode 100644 index 000000000..f45e116c0 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/overlay.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/analytics/chaincode/overview.html b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/overview.html new file mode 100644 index 000000000..4a8d03486 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/overview.html @@ -0,0 +1,51 @@ +
+
+
+ +
+ +
+
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+ + +
+ +
+
+
+
+
+
+ + +
+ +
+
+ + + + + + + + + + +
Chaincode NameInvoke TimesResponse TimeInvoke History
\ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/analytics/chaincode/piedialog.html b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/piedialog.html new file mode 100644 index 000000000..a3cb7693e --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/analytics/chaincode/piedialog.html @@ -0,0 +1,5 @@ +
+
+
+
+
\ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/analytics/fabric/block.html b/user-dashboard/public/js/resources/dashboard/analytics/fabric/block.html new file mode 100644 index 000000000..fc55abbf1 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/analytics/fabric/block.html @@ -0,0 +1,11 @@ + + +
${block}
+ + +
${blockTime}
+ + +
${createTime}
+ + \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/analytics/fabric/fabric.html b/user-dashboard/public/js/resources/dashboard/analytics/fabric/fabric.html new file mode 100644 index 000000000..7115be1df --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/analytics/fabric/fabric.html @@ -0,0 +1,81 @@ +
+
+ <% if (health >= 90) { %> +
+ +
+ +
+
+ <% } else if (health >= 80) { %> +
+ +
+ +
+
+ <% } else { %> +
+ +
+ +
+
+ <% } %> + +
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+
+ + + + + + + + + +
BlockBlock TimeCreate Time
+
+
+
+
+
\ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/analytics/infrastructure.html b/user-dashboard/public/js/resources/dashboard/analytics/infrastructure.html new file mode 100644 index 000000000..29bacf287 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/analytics/infrastructure.html @@ -0,0 +1,59 @@ +
+
+ <% if (health >= 90) { %> +
+ +
+ +
+
+ <% } else if (health >= 80) { %> +
+ +
+ +
+
+ <% } else { %> +
+ +
+ +
+
+ <% } %> + +
+
+
+
+
+
+ + +
+ +
+
+
+
+
+
+ + +
+ +
+
+
+
+
+
+ + +
+ +
+
+
+
\ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/analytics/overview.html b/user-dashboard/public/js/resources/dashboard/analytics/overview.html new file mode 100644 index 000000000..a3449abc5 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/analytics/overview.html @@ -0,0 +1,59 @@ +
+
+ <% if (health >= 90) { %> +
+ +
+ <% } else if (health >= 80) { %> +
+ +
+ <% } else { %> +
+ +
+ <% } %> + +
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+ +
+
\ No newline at end of file diff --git a/user-dashboard/routes/api.js b/user-dashboard/routes/api.js index a2dc948e4..2acacd223 100644 --- a/user-dashboard/routes/api.js +++ b/user-dashboard/routes/api.js @@ -11,6 +11,7 @@ var Chain = require("../modules/chain"); var Chaincode = require("../modules/chaincode"); var mongoClient = require("../modules/mongoclient"); var Contract = require("../modules/contract"); +var Analytics = require("../modules/analytics"); var router = express.Router(); @@ -333,4 +334,44 @@ router.post("/:apikey/contract/:id/delete", function(req, res) { res.json(err); }); }); +router.get("/chain/:id/analytics", function(req, res) { + var analytics = new Analytics(req.params.id); + analytics.overview().then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); +router.get("/chain/:id/analytics/chaincode/list", function(req, res) { + var analytics = new Analytics(req.params.id); + analytics.chaincodeList().then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); +router.get("/chain/:chainId/analytics/chaincode/:chaincodeId/operations", function(req, res) { + var analytics = new Analytics(req.params.chainId); + analytics.chaincodeOperations(req.params.chaincodeId, req.query.timestamp).then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); +router.get("/chain/:id/analytics/fabric", function(req, res) { + var analytics = new Analytics(req.params.id); + analytics.fabric(req.query.timestamp).then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); +router.get("/chain/:id/analytics/infrastructure", function(req, res) { + var analytics = new Analytics(req.params.id); + analytics.infrastructure(req.query.size).then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); module.exports = router; \ No newline at end of file diff --git a/user-dashboard/routes/dashboard/analytics.js b/user-dashboard/routes/dashboard/analytics.js new file mode 100644 index 000000000..4f33804e2 --- /dev/null +++ b/user-dashboard/routes/dashboard/analytics.js @@ -0,0 +1,81 @@ +/** + * Created by lixuc on 2017/5/12. + */ +var express = require("express"); +var config = require("../../modules/configuration"); +var Chain = require("../../modules/chain"); +var Analytics = require("../../modules/analytics"); + +var router = express.Router(); + +router.get([ + "/analytics", + "/analytics/chaincode", + "/analytics/fabric", + "/analytics/infrastructure" +], function(req, res, next) { + var userInfo = JSON.parse(req.cookies[config.cookieName]); + var chain = new Chain(userInfo.apikey); + chain.list(-1).then(function(result) { + var chains = result.chains; + if (chains.length) { + chains.sort(function(c1, c2) { + if (c1.name < c2.name) return -1; + else if (c1.name > c2.name) return 1; + else return 0; + }); + res.locals.chains = chains; + next(); + } else { + res.render("dashboard/analytics/noChains"); + } + }).catch(function(err) { + var e = new Error(err.message); + e.status = 500; + next(e); + }); +}); +router.get("/analytics", function(req, res, next) { + var chains = res.locals.chains; + var analytics = new Analytics(chains[0].id); + analytics.overview().then(function(result) { + res.render("dashboard/analytics/overview", result.statistic); + }).catch(function(err) { + var e = new Error(err.message); + e.status = 500; + next(e); + }); +}); +router.get("/analytics/chaincode", function(req, res, next) { + var chains = res.locals.chains; + var analytics = new Analytics(chains[0].id); + analytics.chaincodeList().then(function(result) { + var chaincodes = result.chaincodes; + var renderer = { + chaincodes: chaincodes + }; + if (chaincodes.length) { + var invokeTimes = 0, responseTime = 0; + for (var i in chaincodes) { + invokeTimes += parseInt(chaincodes[i].invoke_times); + responseTime += parseFloat(chaincodes[i].avg_response_time); + } + Object.assign(renderer, { + invokeTimes: invokeTimes, + avgResponseTime: parseFloat((responseTime / chaincodes.length).toFixed(2)) + }); + } + res.render("dashboard/analytics/chaincode", renderer); + }).catch(function(err) { + var e = new Error(err.message); + e.status = 500; + next(e); + }); +}); +router.get("/analytics/fabric", function(req, res) { + res.render("dashboard/analytics/fabric"); +}); +router.get("/analytics/infrastructure", function(req, res) { + res.render("dashboard/analytics/infrastructure"); +}); +module.exports = router; \ No newline at end of file diff --git a/user-dashboard/views/dashboard/analytics/chaincode.pug b/user-dashboard/views/dashboard/analytics/chaincode.pug new file mode 100644 index 000000000..d5180fbcc --- /dev/null +++ b/user-dashboard/views/dashboard/analytics/chaincode.pug @@ -0,0 +1,77 @@ +extends ../../layout + +block css + link(rel="stylesheet", href="/css/addon/sticky.almost-flat.min.css") + link(rel="stylesheet", href="/css/dashboard/overlayer.css") + link(rel="stylesheet", href="/css/dashboard/navbar.css") + link(rel="stylesheet", href="/css/dashboard/analytics.css") +block js + script(data-main="/js/entry/analytics/chaincode" src="/js/lib/require.js") +block content + include ../includes/navbar.pug + +active("analytics") + section.ds-analytics.uk-container.uk-container-center.uk-padding-remove + .uk-grid + .uk-width-1-5 + include includes/catalog.pug + +active("chaincode") + .uk-width-4-5 + include includes/chainSelector.pug + #analyticsPanel.uk-margin-top + if chaincodes.length + .uk-grid.uk-margin-large-top + .uk-text-center + .uk-text-truncate.uk-margin-bottom.perfect-health-square + label.font-size-15 Chaincode + .uk-vertical-align.health-label-box + label.uk-vertical-align-middle.perfect-label Perfect + label.font-size-20 Health + .uk-vertical-align + .uk-vertical-align-middle.vertical-line(style="margin-bottom:37px;") + .uk-text-center + .uk-vertical-align.uk-margin-bottom(style="height:156px;") + label.uk-vertical-align-middle.font-size-50= chaincodes.length + label.font-size-20 Chaincodes + .uk-vertical-align + .uk-vertical-align-middle.vertical-line(style="margin-bottom:37px;") + .uk-text-center + .uk-vertical-align.uk-margin-bottom(style="height:156px;") + label.uk-vertical-align-middle.uk-margin-small-right.font-size-50= invokeTimes + label.uk-vertical-align-middle.font-size-20(style="margin-top:20px;") + = invokeTimes <= 1 ? "time" : "times" + label.font-size-20 Invoke + .uk-vertical-align + .uk-vertical-align-middle.vertical-line(style="margin-bottom:37px;") + .uk-text-center + .uk-vertical-align.uk-margin-bottom(style="height:156px;") + label.uk-vertical-align-middle.uk-margin-small-right.font-size-50= avgResponseTime + label.uk-vertical-align-middle.font-size-20(style="margin-top:20px;") s + label.font-size-20 Response Time + table.uk-table.uk-table-striped.uk-table-hover.uk-margin-large-top + thead + tr(style="border-top:1px solid #ddd;") + th Chaincode Name + th Invoke Times + th Response Time + th Invoke History + tbody + each chaincode in chaincodes + tr + td= chaincode.name + td + label.invokeTimes= chaincode.invoke_times + td + label.responseTime= parseFloat(chaincode.avg_response_time.toFixed(2)) + "s" + td + button.uk-button.uk-button-primary.uk-button-small.uk-margin-right( + type="button" data-type="functions" data-id=chaincode.id) Functions + |   + i.uk-icon-pie-chart.uk-margin-small-left + |   + button.uk-button.uk-button-primary.uk-button-small( + type="button" data-type="responseTime" data-id=chaincode.id) Response Time + |   + i.uk-icon-bar-chart.uk-margin-small-left + else + label.uk-margin-large-top.empty No result. + div(style="height:80px;") \ No newline at end of file diff --git a/user-dashboard/views/dashboard/analytics/fabric.pug b/user-dashboard/views/dashboard/analytics/fabric.pug new file mode 100644 index 000000000..2526681d9 --- /dev/null +++ b/user-dashboard/views/dashboard/analytics/fabric.pug @@ -0,0 +1,21 @@ +extends ../../layout + +block css + link(rel="stylesheet", href="/css/addon/sticky.almost-flat.min.css") + link(rel="stylesheet", href="/css/dashboard/overlayer.css") + link(rel="stylesheet", href="/css/dashboard/navbar.css") + link(rel="stylesheet", href="/css/dashboard/analytics.css") +block js + script(data-main="/js/entry/analytics/fabric" src="/js/lib/require.js") +block content + include ../includes/navbar.pug + +active("analytics") + section.ds-analytics.uk-container.uk-container-center.uk-padding-remove + .uk-grid + .uk-width-1-5 + include includes/catalog.pug + +active("fabric") + .uk-width-4-5 + include includes/chainSelector.pug + #analyticsPanel.uk-margin-top + div(style="height:80px;") \ No newline at end of file diff --git a/user-dashboard/views/dashboard/analytics/includes/catalog.pug b/user-dashboard/views/dashboard/analytics/includes/catalog.pug new file mode 100644 index 000000000..b46bb6d31 --- /dev/null +++ b/user-dashboard/views/dashboard/analytics/includes/catalog.pug @@ -0,0 +1,31 @@ +mixin active(name) + - var overview_cls, chaincode_cls, fabric_cls, infrastructure_cls + case name + when "overview" + - overview_cls = "uk-active" + when "chaincode" + - chaincode_cls = "uk-active" + when "fabric" + - fabric_cls = "uk-active" + when "infrastructure" + - infrastructure_cls = "uk-active" + .uk-panel.uk-panel-box(data-uk-sticky="{top:20}") + h3.uk-panel-title Catalog + ul.uk-nav.uk-nav-side + li.uk-nav-divider + li(class=overview_cls) + a(href="/dashboard/analytics") + i.uk-icon-globe.uk-margin-small-right + | Overview + li(class=chaincode_cls) + a(href="/dashboard/analytics/chaincode") + i.uk-icon-file-code-o.uk-margin-small-right + | Chaincode + li(class=fabric_cls) + a(href="/dashboard/analytics/fabric") + i.uk-icon-cubes.uk-margin-small-right + | Fabric + li(class=infrastructure_cls) + a(href="/dashboard/analytics/infrastructure") + i.uk-icon-server.uk-margin-small-right + | Infrastructure \ No newline at end of file diff --git a/user-dashboard/views/dashboard/analytics/includes/chainSelector.pug b/user-dashboard/views/dashboard/analytics/includes/chainSelector.pug new file mode 100644 index 000000000..21f9966d5 --- /dev/null +++ b/user-dashboard/views/dashboard/analytics/includes/chainSelector.pug @@ -0,0 +1,13 @@ +#chainSelector.uk-button-dropdown(data-uk-dropdown="{remaintime:100}") + button.uk-button.dropdown-button(data-uk-tooltip="{pos:'right'}" title="Select a chain") + i.uk-icon-chain + |   + label(data-id=chains[0].id)= chains[0].name + i.uk-icon-caret-down.uk-margin-left + .uk-dropdown.uk-dropdown-small + ul.uk-nav.uk-nav-dropdown + each chain in chains + li(data-id=chain.id data-name=chain.name) + a + i.uk-icon-chain + | #{chain.name} \ No newline at end of file diff --git a/user-dashboard/views/dashboard/analytics/infrastructure.pug b/user-dashboard/views/dashboard/analytics/infrastructure.pug new file mode 100644 index 000000000..3df0810b2 --- /dev/null +++ b/user-dashboard/views/dashboard/analytics/infrastructure.pug @@ -0,0 +1,21 @@ +extends ../../layout + +block css + link(rel="stylesheet", href="/css/addon/sticky.almost-flat.min.css") + link(rel="stylesheet", href="/css/dashboard/overlayer.css") + link(rel="stylesheet", href="/css/dashboard/navbar.css") + link(rel="stylesheet", href="/css/dashboard/analytics.css") +block js + script(data-main="/js/entry/analytics/infrastructure" src="/js/lib/require.js") +block content + include ../includes/navbar.pug + +active("analytics") + section.ds-analytics.uk-container.uk-container-center.uk-padding-remove + .uk-grid + .uk-width-1-5 + include includes/catalog.pug + +active("infrastructure") + .uk-width-4-5 + include includes/chainSelector.pug + #analyticsPanel.uk-margin-top + div(style="height:80px;") \ No newline at end of file diff --git a/user-dashboard/views/dashboard/analytics/noChains.pug b/user-dashboard/views/dashboard/analytics/noChains.pug new file mode 100644 index 000000000..4444a4949 --- /dev/null +++ b/user-dashboard/views/dashboard/analytics/noChains.pug @@ -0,0 +1,14 @@ +extends ../../layout + +block css + link(rel="stylesheet", href="/css/dashboard/navbar.css") + link(rel="stylesheet", href="/css/dashboard/analytics.css") +block js + script(data-main="/js/entry/analytics/nochains" src="/js/lib/require.js") +block content + include ../includes/navbar.pug + +active("analytics") + section.ds-analytics.uk-container.uk-container-center + .uk-alert.uk-text-large.uk-container-center.uk-animation-slide-top(style="width:26%") + i.uk-icon-warning.uk-margin-small-right + | Please #[a.warning_link(href="/dashboard/chain") apply a new chain] first. \ No newline at end of file diff --git a/user-dashboard/views/dashboard/analytics/overview.pug b/user-dashboard/views/dashboard/analytics/overview.pug new file mode 100644 index 000000000..37b6dae45 --- /dev/null +++ b/user-dashboard/views/dashboard/analytics/overview.pug @@ -0,0 +1,73 @@ +extends ../../layout + +block css + link(rel="stylesheet", href="/css/addon/sticky.almost-flat.min.css") + link(rel="stylesheet", href="/css/dashboard/overlayer.css") + link(rel="stylesheet", href="/css/dashboard/navbar.css") + link(rel="stylesheet", href="/css/dashboard/analytics.css") +block js + script(data-main="/js/entry/analytics/overview" src="/js/lib/require.js") +block content + include ../includes/navbar.pug + +active("analytics") + section.ds-analytics.uk-container.uk-container-center.uk-padding-remove + .uk-grid + .uk-width-1-5 + include includes/catalog.pug + +active("overview") + .uk-width-4-5 + include includes/chainSelector.pug + #analyticsPanel.uk-margin-top + .uk-grid.uk-margin-large-top + .uk-text-center + mixin div(val) + - var health_cls, health_label_cls, health_label + if (val >= 90) + - health_cls = "perfect-health-circle" + - health_label_cls = "perfect-label" + - health_label = "Perfect" + else if (val >= 80) + - health_cls = "good-health-circle" + - health_label_cls = "good-label" + - health_label = "Good" + else + - health_cls = "attention-health-circle" + - health_label_cls = "attention-label" + - health_label = "Attention" + .uk-vertical-align.uk-margin-bottom(class=[health_cls]) + label.uk-vertical-align-middle(class=[health_label_cls])= health_label + +div(health) + label.font-size-20 Health + .uk-vertical-align + .uk-vertical-align-middle.vertical-line(style="margin-bottom:37px;") + .uk-text-center + .uk-vertical-align.uk-margin-bottom(style="height:171px;") + label.uk-vertical-align-middle + if (runTime.length == 4) + span.font-size-50= runTime[0] + |   + span.uk-margin-right.font-size-20= runTime[1] + |   + span.font-size-50= runTime[2] + |   + span.font-size-20= runTime[3] + else + span.font-size-50= runTime[0] + |   + span.font-size-20= runTime[1] + label.font-size-20 Run Time + .uk-vertical-align + .uk-vertical-align-middle.vertical-line(style="margin-bottom:37px;") + .uk-text-center + .uk-vertical-align.uk-margin-bottom(style="height:171px;") + label.uk-vertical-align-middle + span.font-size-50= chaincodes + label.font-size-20 Chaincodes + .uk-vertical-align + .uk-vertical-align-middle.vertical-line(style="margin-bottom:37px;") + .uk-text-center + .uk-vertical-align.uk-margin-bottom(style="height:171px;") + label.uk-vertical-align-middle + span.font-size-50= blocks + label.font-size-20 Blocks + div(style="height:80px;") \ No newline at end of file