mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 01:13:45 +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,
|
utils.EthashDatasetsOnDiskFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "DASHBOARD",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DashboardEnabledFlag,
|
||||||
|
utils.DashboardAddrFlag,
|
||||||
|
utils.DashboardPortFlag,
|
||||||
|
utils.DashboardRefreshFlag,
|
||||||
|
utils.DashboardAssetsFlag,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "TRANSACTION POOL",
|
Name: "TRANSACTION POOL",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ var (
|
||||||
}
|
}
|
||||||
DashboardAssetsFlag = cli.StringFlag{
|
DashboardAssetsFlag = cli.StringFlag{
|
||||||
Name: "dashboard.assets",
|
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,
|
Value: dashboard.DefaultConfig.Assets,
|
||||||
}
|
}
|
||||||
// Ethash settings
|
// 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,
|
Refresh: time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is the config of the dashboard
|
// Config contains the configuration parameters of the dashboard.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Host is the host interface on which to start the dashboard server. If this
|
// Host is the host interface on which to start the dashboard server. If this
|
||||||
// field is empty, no dashboard will be started.
|
// field is empty, no dashboard will be started.
|
||||||
|
|
@ -40,6 +40,6 @@ type Config struct {
|
||||||
Refresh time.Duration `toml:",omitempty"`
|
Refresh time.Duration `toml:",omitempty"`
|
||||||
|
|
||||||
// Assets offers a possibility to manually set the dashboard website's location on the server side
|
// 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"`
|
Assets string `toml:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package dashboard
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -37,6 +38,7 @@ import (
|
||||||
const (
|
const (
|
||||||
processorSampleLimit = 200
|
processorSampleLimit = 200
|
||||||
memorySampleLimit = 200
|
memorySampleLimit = 200
|
||||||
|
trafficSampleLimit = 200
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -47,7 +49,6 @@ type dashboard struct {
|
||||||
config *Config
|
config *Config
|
||||||
|
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
index []byte // Index page to serve up on the web
|
|
||||||
conns []*client // Currently live websocket connections
|
conns []*client // Currently live websocket connections
|
||||||
|
|
||||||
Metrics *metricSamples `json:"metrics,omitempty"`
|
Metrics *metricSamples `json:"metrics,omitempty"`
|
||||||
|
|
@ -82,34 +83,11 @@ type status struct {
|
||||||
func New(config *Config) (*dashboard, error) {
|
func New(config *Config) (*dashboard, error) {
|
||||||
//log.Trace("NewDashboard() called")
|
//log.Trace("NewDashboard() called")
|
||||||
|
|
||||||
dashboard := &dashboard{
|
return &dashboard{
|
||||||
config: config,
|
config: config,
|
||||||
Metrics: &metricSamples{},
|
Metrics: &metricSamples{},
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protocols is a meaningless implementation of node.Service
|
// 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 {
|
func (db *dashboard) Stop() error {
|
||||||
//log.Trace("Terminating dashboard...")
|
//log.Trace("Terminating dashboard...")
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Close the connection listener
|
|
||||||
db.lock.Lock()
|
db.lock.Lock()
|
||||||
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// Close the connection listener
|
||||||
if err = db.listener.Close(); err != nil {
|
if err = db.listener.Close(); err != nil {
|
||||||
log.Warn("Failed to close listener", "err", err)
|
log.Warn("Failed to close listener", "err", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notifies collectData and apiHandler
|
close(db.quit) // Notifies collectData and apiHandler
|
||||||
close(db.quit)
|
|
||||||
|
|
||||||
for _, c := range db.conns {
|
for _, c := range db.conns {
|
||||||
if err := c.conn.Close(); err != nil {
|
if err := c.conn.Close(); err != nil {
|
||||||
|
|
@ -165,17 +143,74 @@ func (db *dashboard) Stop() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.conns = db.conns[:0]
|
db.conns = db.conns[:0]
|
||||||
db.lock.Unlock()
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// webHandler handles all non-api requests, simply flattening and returning the dashboard website.
|
// webHandler handles all non-api requests, simply flattening and returning the dashboard website.
|
||||||
func (db *dashboard) webHandler(w http.ResponseWriter, r *http.Request) {
|
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
|
path := r.URL.String()
|
||||||
w.Write(db.index)
|
|
||||||
|
// 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
|
// apiHandler handles requests for dashboard
|
||||||
|
|
@ -219,6 +254,7 @@ func (db *dashboard) apiHandler(conn *websocket.Conn) {
|
||||||
closed <- true
|
closed <- true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Ignore all messages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,11 +271,10 @@ func (db *dashboard) collectData() {
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
traffic := metrics.DefaultRegistry.Get("p2p/InboundTraffic").(metrics.Meter).Rate1()
|
traffic := metrics.DefaultRegistry.Get("p2p/InboundTraffic").(metrics.Meter).Rate1()
|
||||||
traffic = traffic * traffic
|
|
||||||
//if traffic != 0 {
|
//if traffic != 0 {
|
||||||
// traffic = math.Log(traffic)
|
// 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 {
|
//if memoryInuse != 0 {
|
||||||
// memoryInuse = math.Log(memoryInuse)
|
// memoryInuse = math.Log(memoryInuse)
|
||||||
//}
|
//}
|
||||||
|
|
@ -249,10 +284,11 @@ func (db *dashboard) collectData() {
|
||||||
}
|
}
|
||||||
memory := &data{
|
memory := &data{
|
||||||
Time: now,
|
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)
|
go db.update(traff, memory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue