mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-16 17:03:46 +00:00
cmd, dashboard: support serving assets from file system
This commit is contained in:
parent
f1ca5702f9
commit
e5970bc940
7 changed files with 277 additions and 440 deletions
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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
112
dashboard/assets/js/handlers.js
Normal file
112
dashboard/assets/js/handlers.js
Normal 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();
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue