cmd, dashboard: support serving assets from file system

This commit is contained in:
Kurkó Mihály 2017-07-11 17:37:44 +03:00
parent f1ca5702f9
commit e5970bc940
7 changed files with 277 additions and 440 deletions

View file

@ -92,6 +92,16 @@ var AppHelpFlagGroups = []flagGroup{
utils.EthashDatasetsOnDiskFlag,
},
},
{
Name: "DASHBOARD",
Flags: []cli.Flag{
utils.DashboardEnabledFlag,
utils.DashboardAddrFlag,
utils.DashboardPortFlag,
utils.DashboardRefreshFlag,
utils.DashboardAssetsFlag,
},
},
{
Name: "TRANSACTION POOL",
Flags: []cli.Flag{

View file

@ -200,7 +200,7 @@ var (
}
DashboardAssetsFlag = cli.StringFlag{
Name: "dashboard.assets",
Usage: "Directory of the dashboard assets, useful for debugging (default = assets.go binary)",
Usage: "Path of the dashboard assets, useful for debugging (default = \"\", in this case assets.go binary is used)",
Value: dashboard.DefaultConfig.Assets,
}
// Ethash settings

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,112 @@
Chart.defaults.global.legend = {
enabled: false
}
// Line chart for memory
var ctx = document.getElementById("memoryLineChart");
var memoryLineChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: "system/memory/inuse",
backgroundColor: "rgba(38, 185, 154, 0.31)",
borderColor: "rgba(38, 185, 154, 0.7)",
pointBorderColor: "rgba(38, 185, 154, 0.7)",
pointBackgroundColor: "rgba(38, 185, 154, 0.7)",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointBorderWidth: 1,
data: []
}]
},
});
// Line chart for traffic
var trafficCtx = document.getElementById("trafficLineChart");
var trafficLineChart = new Chart(trafficCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: "p2p/InboundTraffic",
backgroundColor: "rgba(3, 88, 106, 0.3)",
borderColor: "rgba(3, 88, 106, 0.70)",
pointBorderColor: "rgba(3, 88, 106, 0.70)",
pointBackgroundColor: "rgba(3, 88, 106, 0.70)",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(151,187,205,1)",
pointBorderWidth: 1,
data: []
}]
},
});
//TODO (kurkomisi): remove static values after debugging
const MEMORY_SAMPLE_LIMIT = 200;//{{.memorySampleLimit}}; // Maximum number of memory data samples
const TRAFFIC_SAMPLE_LIMIT = 200;//{{.trafficSampleLimit}}; // Maximum number of traffic data samples
const PROCESSOR_SAMPLE_LIMIT = 200;//{{.processorSampleLimit}}; // Maximum number of processor data samples
function updateCharts(msg) {
// Fill the dashboard with past data
if(msg.metrics !== undefined) {
// Clear in case the dashboard was opened before
memoryLineChart.data.labels = [];
trafficLineChart.data.labels = [];
memoryLineChart.data.datasets[0].data = [];
trafficLineChart.data.datasets[0].data = [];
var memory = msg.metrics.memory;
var processor = msg.metrics.processor;
// It is possible to get another message while filling the arrays by push(), so instead
// put the history to the beginning of the arrays
for (var i = memory.length - 1; i >= 0 && MEMORY_SAMPLE_LIMIT > memoryLineChart.data.labels.length; --i) {
memoryLineChart.data.labels.unshift(memory[i].time.substring(memory[i].time.length - 5));
trafficLineChart.data.labels.unshift(memory[i].time.substring(memory[i].time.length - 5));
memoryLineChart.data.datasets[0].data.unshift(memory[i].value);
trafficLineChart.data.datasets[0].data.unshift(processor[i].value);
}
memoryLineChart.update();
trafficLineChart.update();
return;
}
// update
if(msg.memory !== undefined) {
if(memoryLineChart.data.labels.length === MEMORY_SAMPLE_LIMIT) {
memoryLineChart.data.labels.shift();
trafficLineChart.data.labels.shift();
memoryLineChart.data.datasets[0].data.shift();
trafficLineChart.data.datasets[0].data.shift();
}
memoryLineChart.data.labels.push(msg.memory.time.substring(msg.memory.time.length - 5));
trafficLineChart.data.labels.push(msg.memory.time.substring(msg.memory.time.length - 5));
memoryLineChart.data.datasets[0].data.push(msg.memory.value);
trafficLineChart.data.datasets[0].data.push(msg.processor.value);
memoryLineChart.update();
trafficLineChart.update();
}
}
// Global variables to hold the current status of the dashboard
var server;
// Define a method to reconnect upon server loss
var reconnect = function() {
server = new WebSocket("ws://" + location.host + "/api");
server.onmessage = function(event) {
var msg = JSON.parse(event.data);
if (msg === null) {
return;
}
updateCharts(msg)
}
server.onclose = function() { setTimeout(reconnect, 3000); };
}
// Establish a websocket connection to the API server
reconnect();

View file

@ -25,7 +25,7 @@ var DefaultConfig = Config{
Refresh: time.Second,
}
// Config is the config of the dashboard
// Config contains the configuration parameters of the dashboard.
type Config struct {
// Host is the host interface on which to start the dashboard server. If this
// field is empty, no dashboard will be started.
@ -40,6 +40,6 @@ type Config struct {
Refresh time.Duration `toml:",omitempty"`
// Assets offers a possibility to manually set the dashboard website's location on the server side
// useful at debugging - avoids the repeated generation of the binary
// useful for debugging - avoids the repeated generation of the binary
Assets string `toml:",omitempty"`
}

View file

@ -16,7 +16,7 @@
package dashboard
//go:generate go-bindata -nometadata -o assets.go -prefix assets -pkg dashboard assets
//go:generate go-bindata -nometadata -o assets.go -prefix assets -pkg dashboard assets/...
import (
"bytes"
@ -27,6 +27,7 @@ import (
"github.com/rcrowley/go-metrics"
"golang.org/x/net/websocket"
"html/template"
"io/ioutil"
"net"
"net/http"
"sync"
@ -37,6 +38,7 @@ import (
const (
processorSampleLimit = 200
memorySampleLimit = 200
trafficSampleLimit = 200
)
var (
@ -47,7 +49,6 @@ type dashboard struct {
config *Config
listener net.Listener
index []byte // Index page to serve up on the web
conns []*client // Currently live websocket connections
Metrics *metricSamples `json:"metrics,omitempty"`
@ -82,34 +83,11 @@ type status struct {
func New(config *Config) (*dashboard, error) {
//log.Trace("NewDashboard() called")
dashboard := &dashboard{
return &dashboard{
config: config,
Metrics: &metricSamples{},
quit: make(chan struct{}),
}
if config.Assets == "" {
tmpl, err := Asset("dashboard.html")
if err != nil {
return nil, err
}
website := new(bytes.Buffer)
// set the sample limits for the client
if err = template.Must(template.New("").Parse(string(tmpl))).Execute(website, map[string]interface{}{
"processorSampleLimit": processorSampleLimit,
"memorySampleLimit": memorySampleLimit,
}); err != nil {
log.Crit("Failed to render the dashboard template", "err", err)
}
dashboard.index = website.Bytes()
return dashboard, nil
}
//TODO (kurkomisi): case DashboardAssetsFlag is set
//dashboard.index = ioutil.ReadFile()
return dashboard, nil
}, nil
}
// Protocols is a meaningless implementation of node.Service
@ -148,16 +126,16 @@ func (db *dashboard) Start(server *p2p.Server) error {
func (db *dashboard) Stop() error {
//log.Trace("Terminating dashboard...")
var err error
// Close the connection listener
db.lock.Lock()
defer db.lock.Unlock()
var err error
// Close the connection listener
if err = db.listener.Close(); err != nil {
log.Warn("Failed to close listener", "err", err)
}
// Notifies collectData and apiHandler
close(db.quit)
close(db.quit) // Notifies collectData and apiHandler
for _, c := range db.conns {
if err := c.conn.Close(); err != nil {
@ -165,17 +143,74 @@ func (db *dashboard) Stop() error {
}
}
db.conns = db.conns[:0]
db.lock.Unlock()
return err
}
// webHandler handles all non-api requests, simply flattening and returning the dashboard website.
func (db *dashboard) webHandler(w http.ResponseWriter, r *http.Request) {
//log.Trace("webHandler() called")
//log.Trace("webHandler() called", "r.URL", r.URL)
log.Info("Request", "URL", r.URL)
//TODO (kurkomisi): not only index
w.Write(db.index)
path := r.URL.String()
// If the path of the assets is manually set
if db.config.Assets != "" {
// Create the filename for ReadFile
var buffer bytes.Buffer
buffer.WriteString(db.config.Assets)
switch path {
case "/":
buffer.WriteString("/dashboard.html")
default:
buffer.WriteString(path)
}
file, err := ioutil.ReadFile(buffer.String())
if err != nil {
// TODO (kurkomisi): Warn or Crit?
log.Warn("Failed to read file", "err", err)
// TODO (kurkomisi): Should I inform the client?
return
}
w.Write(file)
return
}
switch path {
case "/":
index, err := Asset("dashboard.html")
if err != nil {
log.Warn("Failed to load the index", "err", err)
return
}
w.Write(index)
case "/js/handlers.js":
tmpl, err := Asset("js/handlers.js")
if err != nil {
log.Warn("Failed to load the asset", "path", path, "err", err)
return
}
handlers := new(bytes.Buffer)
// TODO (kurkomisi): Save the generated template to avoid the repeated generation?
// set the sample limits for the client
if err = template.Must(template.New("").Parse(string(tmpl))).Execute(handlers, map[string]interface{}{
"processorSampleLimit": processorSampleLimit,
"memorySampleLimit": memorySampleLimit,
"trafficSampleLimit": trafficSampleLimit,
}); err != nil {
log.Warn("Failed to render the dashboard handlers template", "err", err)
return
}
w.Write(handlers.Bytes())
default:
website, err := Asset(path[1:])
if err != nil {
log.Warn("Failed to load the asset", "path", path, "err", err)
return
}
w.Write(website)
}
}
// apiHandler handles requests for dashboard
@ -219,6 +254,7 @@ func (db *dashboard) apiHandler(conn *websocket.Conn) {
closed <- true
return
}
// Ignore all messages
}
}
@ -235,11 +271,10 @@ func (db *dashboard) collectData() {
now := time.Now()
traffic := metrics.DefaultRegistry.Get("p2p/InboundTraffic").(metrics.Meter).Rate1()
traffic = traffic * traffic
//if traffic != 0 {
// traffic = math.Log(traffic)
//}
memoryInuse := metrics.DefaultRegistry.Get("system/memory/inuse").(metrics.Meter).Rate1()
memoryInUse := metrics.DefaultRegistry.Get("system/memory/inuse").(metrics.Meter).Rate1()
//if memoryInuse != 0 {
// memoryInuse = math.Log(memoryInuse)
//}
@ -249,10 +284,11 @@ func (db *dashboard) collectData() {
}
memory := &data{
Time: now,
Value: memoryInuse,
Value: memoryInUse,
}
//TODO (kurkomisi): do I need to ensure the correct order?
// TODO (kurkomisi): do I need to ensure the correct order?
// TODO (kurkomisi): do not mix traffic with processor!
go db.update(traff, memory)
}
}